乐趣区

关于flutter:Flutter启动流程分析之插件化升级探索

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/FlutterView 2 种形式,具体看应用场景。

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

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

退出移动版