写点什么

Flutter 启动流程分析之插件化升级探索

作者:得物技术
  • 2022 年 1 月 26 日
  • 本文字数:5042 字

    阅读完需:约 17 分钟

Flutter 是 Google 推出的一款跨平台框架。与 Weex 等其他跨端框架不同的是,Flutter 的界面布局绘制是由自己完成的,而不是转换成对应平台的原生组件。那么各个平台是如何启动它的呢?从 Flutter 官方提供的架构图上看,Flutter Embedder 层提供了底层操作系统到 Flutter 的程序入口,平台采用适合当前系统特性的方式去各自实现。本文基于 flutter 2.0.6 版本源码,来探索 Android 平台上 flutter Embedder 层对应的启动流程,看看这个过程中做了些什么事情,有什么问题是需要我们在项目中注意的。


这部分源码位于 engine 源码中的/engine/shell/platform/android/ 目录下。


1.主流程

先来看看整体的流程:



Android 以 FlutterActivity/FlutterFragment/FlutterView 的形式承载 flutter 界面。当我们使用 AndroidStudio 创建一个新的 flutter 工程时,生成的 MainActivity 是直接继承了 FlutterActivity,那么很明显,主要的逻辑都在这个 FlutterActivity 里面了。从流程图看到,flutter 的启动流程也是从 FlutterActivity 的 onCreate 方法开始的:


1.FlutterActivity 将 onCreate 主要的操作委托给 delegate 对象去实现。


2.delegate 中调用 setupFlutterEngine 创建 FlutterEngine。


3.FlutterEngine 初始化各种 channel 之后,再创建 FlutterLoader 去加载资源文件和 apk 里的打包产物,之后初始化 JNI 的几个线程和 DartVM。


4.delegate 之后再通过 FlutterEngine 注册各个插件。


5.FlutterActivity 调用 delegate 的 onCreateView 创建 FlutterView。


6.最后,onStart 生命周期中通过 delegate 的 onStart 方法执行 DartExecutor.executeDartEntrypoint,这个方法会在 jni 层执行 Dart 代码的入口函数。至此启动完成。


1.1.FlutterActivity

FlutterActivity 也是继承的 Activity,但是它把主要的功能都委托给了 FlutterActivityAndFragmentDelegate 类去实现,实现的 Host 接口主要是支持在 delegate 中获取 FlutterActivity 的一些参数,比如 configureFlutterEngine,这些方法可以由子类去重写,实现自定义配置。



接下来,我们看看 FlutterActivity 的 onCreate(),主要的两个步骤是:


1.delegate.onAttach(this): 初始化 FlutterEngine、注册各个插件。(注意,这里传的 this 即是 delegate 中的 host 对象)


2.setContentView(createFlutterView() ): 创建 FlutterView 并绑定到 FlutterEngine。


这两个步骤都是委托给 FlutterActivityAndFragmentDelegate 去实现的。



\

1.2.FlutterActivityAndFragmentDelegate

1.2.1.onAttach


总结一下,onAttach 中主要做了一下几件事情:


1.设置 flutterEngine:


1.1.判断是否从缓存中获取;


1.2.判断是否有自定义 flutterEngine;


1.3.new 一个新的 flutterEngine 对象;


  1. 将插件 attach 到 host activity,最终会调用各个插件的 onAttachedToActivity 方法。


3.创建 PlatformPlugin


4.注册插件。

1.2.2.configureFlutterEngine

这里说一下 configureFlutterEngine(flutterEngine)主要是干什么的,这个方法是在 FlutterActivity 中实现的,代码如下:



它通过反射找到了 GeneratedPluginRegistrant 类,并调用了其 registerWith 方法。这个类我们可以在工程中的 /android/java/目录下找到,是 flutter tool 自动生成的,当我们在 pubspec.yaml 中添加一个插件,并执行 pub get 命令后即会生成。




系统默认使用反射实现,我们也可以在 MainActivity 中重写这个方法,直接调用 registerWith 方法。

1.3.FlutterEngine

再来看看 FlutterEngine 的构造函数。FlutterEngine 是一个独立的 flutter 运行环境,通过它能使用 DartExecutor 执行 Dart 代码。


DartExecutor 可以跟 FlutterRenderer 配合渲染 UI,也可以在只在后台运行 Dart 代码,不渲染 UI。


当初始化第一个 FlutterEngine 时,DartVM 会被创建,之后可以继续创建多个 FlutterEngine, 每个 FlutterEngine 对应的 DartExecutor 执行在不同的 DartIsolate 中,但同一个 Native 进程只有一个 DartVM。



可以看到,这里面做的事情还是很多的:


1.初始化 AssetsManager。


2.创建 DartExecutor 并设置对应 PlatformMessageHandler


3.初始化一系列的系统 channel。


4.初始化 FlutterLoader,加载 Resource 资源和 libflutter.so、libapp.so 等 apk 产物。


5.创建 FlutterRenderer、FlutterEngineConnectionRegistry。


6.如果需要,自动注册 pubspec.yaml 中声明的插件。


接下来看一下 FlutterLoader 相关的内容。

1.4.FlutterLoader

FlutterLoader 以单例的形式存在,一个进程只用初始化一次。用来加载 apk 安装包中的资源文件和代码产物,必须在主线程中进行。



startInitialization()方法中主要做了以下几件事情:


1.加载传给 activity 的 meta 配置信息;


2.提取 apk 安装包中的 assets 资源,主要是在 DEBUG 和 JIT_RELEASE 模式下的产物 ,比如 vmSnapshotData、isolateSnapshotData 等;


3.加载 flutter engine C++部分源码,即在 flutterJNI 执行 System.loadLibrary("flutter")


public void ensureInitializationComplete(    @NonNull Context applicationContext, @Nullable String[] args) {  //多次调用无效  if (initialized) {    return;  }  ...  try {    //startInitializatioz中得到的几个资源文件目录    InitResult result = initResultFuture.get();    //这个列表中动态配置了flutter启动需要加载的一些资源的路径    List<String> shellArgs = new ArrayList<>();    shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");    //libflutter.so的路径    shellArgs.add(        "--icu-native-lib-path="            + flutterApplicationInfo.nativeLibraryDir            + File.separator            + DEFAULT_LIBRARY);    if (args != null) {      //方法参数中传来的,可以在重写FltterActivity::getFlutterShellArgs()来自定义参数      Collections.addAll(shellArgs, args);    }    String kernelPath = null;    //DEBUG和JIT_RELEASE模式下只加载snapshot数据    if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {      ...      shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);      shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);      shellArgs.add(          "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);    } else {    //RELEASE模式下加载libapp.so文件,这是Dart代码编译后的产物    //默认是相对路径      shellArgs.add(          "--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);      //同一个key可以存多个值,当根据前面的相对路径找不到文件时,再尝试用绝对路径加载      shellArgs.add(          "--"              + AOT_SHARED_LIBRARY_NAME              + "="              + flutterApplicationInfo.nativeLibraryDir              + File.separator              + flutterApplicationInfo.aotSharedLibraryName);    }    ...    //到jni层去初始化Dart VM和Flutter engine,该方法只可以被调用一次    flutterJNI.init(        applicationContext,        shellArgs.toArray(new String[0]),        kernelPath,        result.appStoragePath,        result.engineCachesPath,        initTimeMillis);
initialized = true; } catch (Exception e) { throw new RuntimeException(e); }}
复制代码


这个方法的作用是动态配置 flutter 引擎启动前的各种资源路径和其他配置,以 --key=value 的方式统一添加到 shellArgs 中,然后调用 flutterJNI.init 到 C++层去处理,C++层会将传入的配置保存到一个 setting 对象中,之后根据 setting 创建 FlutterMain 对象,保存为一个全局静态变量 g_flutter_main。之后初始化 DartVM 等步骤就可以用到这里保存的配置信息了。

1.5.onStart

根据 Android 中 Activity 的生命周期,onCreate 执行完之后就是 onStart 了。同样的,FlutterView 还是将 onStart 中的操作委托给了 delegate 对象去完成。



可以看到,onStart 生命周期就做了一件事情:执行 Dart 代码的入口函数。这里有一些需要注意的地方:


  1. DartExecutor 只会执行一次,这意味着一个 FlutterEngine 对应的 DartExecutor 不支持重启或者重载


2.Dart Navigator 的初始路由默认是"/"。我们可以重写 getInitialRoute 来自定义。


3.Dart 入口函数 默认是 main(),重写 getDartEntrypointFunctionName 方法可以自定义。


  1. executeDartEntrypoint 最终会通过 FlutterJNI 的方法来调用 JNI 方法来执行。在 UI Thread 中执行 DartIsolate.Run(config),根据 entrypoint_name 找到 Dart 入口的句柄后运行_startIsolate 执行入口函数,之后执行 main 函数的 runApp()。


至此,Flutter 项目成功在 Android 平台上启动完成。

2.应用-热更新

其实我这次探索 Flutter 启动流程的一个主要目的是寻找 Flutter 在 Android 侧的热更新方案。那么看完了整个流程之后,我们要如何做到热更新呢?


flutter app 的 apk 安装包的几个主要产物是,flutter_assets、libflutter.so 和 libapp.so:


flutter_assets:包含 flutter 应用项目中的资源文件,font、images、audio 等;


libflutter.so:flutter embedder 层相关的 C++代码。


libapp.so:我们写的 Dart 代码编译后的产物


只要可以在加载之前动态替换掉 libapp.so 这个文件,即可实现 flutter 代码的热更新。

2.1.方法一:反射修改 FlutterLoader

那么 libapp.so 是在哪里加载的呢?其实上面 1.4.FlutterLoader 已经提到了,在 ensureInitializationComplete()方法中,有一个 shellArgs 列表存储了资源路径配置信息。libapp.so 对应的 key 是 "aot-shared-library-name"



那么,只要替换掉这一块代码,将路径设置成自定义的路径即可让框架去加载新的 libapp_fix.so 文件。具体步骤是:


1.继承 FlutterLoader,重写 ensureInitializationComplete(),将 "aot-shared-library-name" 对应的路径设置成自定义的路径。


2.我们看看 flutterEngine 中是怎么创建的 FlutterLoader 实例的:


flutterLoader = FlutterInjector.instance().flutterLoader();
复制代码


那么,我们只要实例化自定义的 FlutterLoader 类,并通过反射的方式将 FlutterInjector 中的 flutterLoader 实例替换成新的实例即可。

2.2.方法二:重写 getFlutterShellArgs()

我们注意到 ensureInitializationComplete()方法中往 AOT_SHARED_LIBRARY_NAME 这个 key 里面添加了 2 个值,只有当相对路径下找不到文件的情况下才回去寻找绝对路径下的文件。那么我们只要将自定义的 so 文件路径设置成 "aot-shared-library-name" 第一条 value 就可以让框架只加载最新的安装包了。


由于 ensureInitializationComplete()方法会将参数 String[] args 中的内容全部加入 shellArgs 列表,那么我们只要在 args 中加上 "aot-shared-library-name=自定义路径" 这一条配置就行了,我们看看这个 args 参数怎么来的:



host.getFlutterShellArgs().toArray()即使 args 参数的来源了。从之前的分析,我们已经知道了,delegate 中的 host 对象是 FlutterActivity 的引用,我们再来看看 FlutterActivity 是怎么实现的:



这是一个 public 方法,那么我们只要在 MainActivity 中重写这个方法,并在获取到 FlutterShellArgs 之后将需要的配置添加进去即可:



很明显,这个方法更加简单有效。需要注意的是,这个配置只会在 RELEASE 模式下加载,所以 DEBUG 和 JIT_RELEASE 模式模式下调试是不起作用的。

3.总结

最后,大致进行一下总结:


1.纯 flutter 项目中,Android 默认以 FlutterActivity 的形式承载 flutter 界面。Native-Flutter 混合工程中还可以使用 FlutterFragment/FlutterView2 种方式,具体看使用场景。


2.FlutterActivity 将绝大部分工作委托给 FlutterActivityAndFragmentDelegate 实现。


3.启动过程主要是 FlutterActivity 的 onCreate()和 onStart()方法。


onCreate() 会初始化 FlutterEngine、注册各个插件,之后创建 FlutterView 并绑定到 FlutterEngine。


onStart() 主要是通过 DartExecutor 去执行 Dart 代码的入口函数。


4.初始化第一个 FlutterEngine 时会创建和初始化 DartVM。可以创建多个 FlutterEngine,一个 FlutterEngine 对应一个 DartExecutor,每个 DartExecutor 在自己的 DartIsolate 中执行。


5.DartExecutor 可以和 FlutterRender 配合渲染 UI,也可以只执行 Dart 代码不渲染 UI。


6.FlutterView 有两种模式:FlutterSurfaceView 和 FlutterTextureView。顾名思义,即分别使用 surfaceView 和 textureView 来承载 flutter 视图。FlutterSurfaceView 渲染性能更好,但是视图在 Native-Flutter 混合工程中不支持灵活的 z-index 设置。


文/KECHANGZHAO


关注得物技术,做最潮技术人!

发布于: 刚刚阅读数: 2
用户头像

得物技术

关注

得物APP技术部 2019.11.13 加入

关注微信公众号「得物技术」

评论

发布
暂无评论
Flutter启动流程分析之插件化升级探索