文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Flutter Add to App 问题记录

2023-08-17 08:13

关注

在这里插入图片描述
前一阵应用中接入了Flutter,使用的是官方的Multiple FlutterEngine管理方案,目前线上运行良好,这里整理一下遇到的问题。

将 Flutter 集成到现有应用整体来说没有什么问题,按照文档的说明结合demo操作就行。接入后多语言,深色模式也可以和原生部分一样正常运行。但还是遇到了一些实际开发中的细节问题。

首屏展示优化

在官方文档中有提到,即使使用了预热的FlutterEngine,第一次展示 Flutter 的内容仍然需要一些时间。为了更进一步提升用户体验,Flutter 支持在第一帧渲染完成之前展示闪屏页。

我这里遇到的问题是这样,首页有四个Tab,其中第三个Tab是Flutter页面。所以当切换到它时,第一次加载会先白屏一下。

我这里提供两种优化方法。第一种可以使用 CachedEngineFragmentBuilder 中的 shouldDelayFirstAndroidViewDraw() 方法。它表示是否延迟 Android 绘制过程,直到 Flutter UI 显示完毕。

FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")    .shouldDelayFirstAndroidViewDraw(true)    .build();

使用这个方法有两点需要注意:

第二种方法可以使用SplashScreen来显示一个闪屏页。我们让承载FlutterFragment的Activity实现SplashScreenProvider接口的provideSplashScreen方法。

@Overridepublic SplashScreen provideSplashScreen() {    return new DrawableSplashScreen(this.getResources().getDrawable(R.drawable.xxx),     ImageView.ScaleType.CENTER_CROP, 300);}

这两个方法各有适用场景,大多数情况下可以使用第一种。由于我们这个页面背景是一张图片,所以可以使用第二种方法。先展示图片,再用动画过渡显示页面内容。可以避免使用第一种方法首次切换时的等待时间。

另外,使用RenderMode.surface渲染模式目前(Flutter 3.10)有个bug,就是在承载页面onResume时。FlutterFragment会突然显示,因为 SurfaceView 在视图层级最顶层。所以覆盖了其他的Fragment页面。目前使用RenderMode.texture无此问题。

问题跟进具体可以看这里

Activity与Fragment基本一致,默认使用RenderMode.surface渲染模式。所以等到页面第一帧渲染完成后才会打开Activity。如果flutter页面背景是张图片,那么首次进入页面时,因为图片的加载有一定时间,所以会闪一下(release相对好一些)。所以也可以考虑闪屏的方案。

以上问题对应源码位置FlutterActivityAndFragmentDelegate onCreateView:

 View onCreateView(      LayoutInflater inflater,      @Nullable ViewGroup container,      @Nullable Bundle savedInstanceState,      int flutterViewId,      boolean shouldDelayFirstAndroidViewDraw) {    Log.v(TAG, "Creating FlutterView.");    ...    SplashScreen splashScreen = host.provideSplashScreen();    if (splashScreen != null) {      FlutterSplashView flutterSplashView = new FlutterSplashView(host.getContext());      flutterSplashView.setId(ViewUtils.generateViewId(FLUTTER_SPLASH_VIEW_FALLBACK_ID));      flutterSplashView.displayFlutterViewWithSplash(flutterView, splashScreen);      return flutterSplashView;    }    if (shouldDelayFirstAndroidViewDraw) {      delayFirstAndroidViewDraw(flutterView);    }    return flutterView;  }    private void delayFirstAndroidViewDraw(FlutterView flutterView) {    if (host.getRenderMode() != RenderMode.surface) {      throw new IllegalArgumentException(          "Cannot delay the first Android view draw when the render mode is not set to"              + " `RenderMode.surface`.");    }    if (activePreDrawListener != null) {      flutterView.getViewTreeObserver().removeOnPreDrawListener(activePreDrawListener);    }    activePreDrawListener =        new OnPreDrawListener() {          @Override          public boolean onPreDraw() {            if (isFlutterUiDisplayed && activePreDrawListener != null) {              flutterView.getViewTreeObserver().removeOnPreDrawListener(this);              activePreDrawListener = null;            }            return isFlutterUiDisplayed;          }        };    flutterView.getViewTreeObserver().addOnPreDrawListener(activePreDrawListener);  }

异常处理

  1. FlutterFragment, IllegalStateException
IllegalStateException: The requested cached FlutterEngine did not exist in the FlutterEngineCache: 'my_engine_id'       at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.setupFlutterEngine (FlutterActivityAndFragmentDelegate.java:280)       at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onAttach (FlutterActivityAndFragmentDelegate.java:189)       at io.flutter.embedding.android.FlutterFragment.onAttach (FlutterFragment.java:1046)       at androidx.fragment.app.Fragment.performAttach (Fragment.java:2922)       at androidx.fragment.app.FragmentStateManager.attach (FragmentStateManager.java:464)       at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:275)       at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:112)       at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1647)       at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3128)       at androidx.fragment.app.FragmentManager.dispatchCreate (FragmentManager.java:3061)       at androidx.fragment.app.FragmentController.dispatchCreate (FragmentController.java)       at androidx.fragment.app.FragmentActivity.onCreate (FragmentActivity.java:276)

异常位置:

  void setupFlutterEngine() {    Log.v(TAG, "Setting up FlutterEngine.");    // First, check if the host wants to use a cached FlutterEngine.    String cachedEngineId = host.getCachedEngineId();    if (cachedEngineId != null) {      flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);      isFlutterEngineFromHost = true;      if (flutterEngine == null) {        throw new IllegalStateException(            "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"                + cachedEngineId                + "'");      }      return;    }    ...  }

分析原因应该是页面在后台被回收后,重新打开页面时,在获取FlutterEngine时发现不存在于FlutterEngineCache中。因为cachedEngineId是通过getArguments()获取的,而FlutterEngineonDetach已经移除。

  @Override  public String getCachedEngineId() {    return getArguments().getString(ARG_CACHED_ENGINE_ID, null);  }

所以处理方法就是在FragmentActivity.onCreate前就判断FlutterEngine是否存在,不存在时创建。

    @Override    protected void onCreate(Bundle savedInstanceState) {        if (!FlutterEngineCache.getInstance().contains("my_engine_id")) {            FlutterEngine flutterEngine = new FlutterEngine(this);            flutterEngine.getDartExecutor().executeDartEntrypoint(                    DartEntrypoint.createDefault()            );            FlutterEngineCache                    .getInstance()                    .put("my_engine_id", flutterEngine);        }        super.onCreate(savedInstanceState);        ...    }
  1. 个别设备出现UnsatisfiedLinkError
Caused by java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: No implementation found for void io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(float) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate and Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate__F)       at java.util.concurrent.FutureTask.report(FutureTask.java:123)       at java.util.concurrent.FutureTask.get(FutureTask.java:193)   at io.flutter.embedding.engine.loader.FlutterLoader.ensureInitializationComplete(FlutterLoader.java:221)Caused by java.lang.UnsatisfiedLinkError: No implementation found for void io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(float) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate and Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate__F)       at io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(FlutterJNI.java)       at io.flutter.embedding.engine.FlutterJNI.updateRefreshRate(FlutterJNI.java:7)       at io.flutter.embedding.engine.loader.FlutterLoader$1.call(FlutterLoader.java:27)       at io.flutter.embedding.engine.loader.FlutterLoader$1.call(FlutterLoader.java)       at java.util.concurrent.FutureTask.run(FutureTask.java:266)       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)       at java.lang.Thread.run(Thread.java:923)

或:

Caused by java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xx/base.apk"],nativeLibraryDirectories=[/data/app/xx/lib/x86, /system/lib, /vendor/lib]]] couldn't find "libflutter.so"

一般是在创建FlutterEngineFlutterEngineGroup时出现的,目前也没有较好的方法,但是可以捕获一下此类异常,做一些兜底操作。避免直接崩溃影响用户使用其他功能,这类问题占比很少,目前仅有两个用户上报了此异常。

此问题跟进可以看这里

热重载

在调试混合开发模块时(Flutter版本3.10.x),发现当存在多个Flutter页面时(使用FlutterEngineGroup创建),热重载会使App卡死。我找到了相关问题,我尝试使用beta 3.13.0版本发现此问题已解决。等待stable的发布。

打包

众所周知Flutter的debug模式性能表现一般,所以在交给测试时,为了避免一些体验问题。我们可以将Flutter模块打包成release。

如果使用依赖 Android Archive方式集成,可以直接使用flutter_release包。如果直接依赖模块的源码,可以在直接修改flutter/packages/flutter_tools/gradle/flutter.gradle的源码:

       private static String buildModeFor(buildType) {        if (buildType.name == "profile") {            return "profile"        } else if (buildType.debuggable) {            return "debug"        }        return "release"    }

将上面的"debug"改为"release"就好。iOS在flutter/packages/flutter_tools/bin/xcode_backend.dart中修改。

当然直接修改显得不是很优雅,所以可以写个打包脚本处理这一操作。例如用Dart实现如下:

// 读取文件内容File file = File('xxx\flutter\packages\flutter_tools\gradle\flutter.gradle');String content = file.readAsStringSync();// 修改文件内容String newContent = content.replaceAll('return "debug"', 'return "release"// weilu');// 将修改后的内容写回文件file.writeAsStringSync(newContent);

执行完后,还原即可。

其他


后面如果有遇到新的问题,也会同步记录到这里。

参考

来源地址:https://blog.csdn.net/qq_17766199/article/details/131992775

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯