Plug-in upgrade exploration of Flutter startup process analysis

得物技术
中文

Flutter is a cross-platform framework launched by Google. Unlike other cross-end frameworks such as Weex, Flutter's interface layout drawing is done by itself, rather than converted into native components of the corresponding platform. So how did each platform start it? From the architecture diagram officially provided by Flutter, the Flutter Embedder layer provides the program entry from the underlying operating system to Flutter, and the platform adopts a method suitable for the current system characteristics to implement each. Based on the source code of flutter 2.0.6, this article explores the startup process corresponding to the flutter Embedder layer on the Android platform, to see what has been done in this process, and what problems need our attention in the project.

This part of the source code is located in the /engine/shell/platform/android/ directory in the engine source code.

1. Main process

Let's take a look at the overall process first:

Android hosts the flutter interface in the form of FlutterActivity/FlutterFragment/FlutterView When we use AndroidStudio create a new project flutter, resulting MainActivity directly inherited the FlutterActivity, it is clear that the main logic in this FlutterActivity inside. As you can see from the flow chart, the startup process of flutter also starts from the onCreate method of FlutterActivity:

1.FlutterActivity delegates the main operation of onCreate to the delegate object to implement.

2. Call setupFlutterEngine in the delegate to create a FlutterEngine.

3. After FlutterEngine initializes various channels, it creates FlutterLoader to load resource files and packaged products in apk, and then initializes several threads of JNI and DartVM.

4. After delegate, register each plugin through FlutterEngine.

5.FlutterActivity calls delegate's onCreateView to create FlutterView.

6. Finally, in the onStart life cycle, DartExecutor.executeDartEntrypoint is executed through the onStart method of the delegate, which will execute the entry function of the Dart code at the jni layer. The startup is now complete.

1.1.FlutterActivity

FlutterActivity is also an inherited Activity, but it delegates its main functions to the FlutterActivityAndFragmentDelegate class to implement. The implemented Host interface mainly supports obtaining some parameters of FlutterActivity in the delegate, such as configureFlutterEngine. These methods can be rewritten by subclasses. Implement custom configuration.

Next, let's look at the onCreate() of FlutterActivity. The main two steps are:

1.delegate.onAttach(this): Initialize FlutterEngine and register each plugin. (Note that the this host object in the delegate)

2.setContentView( createFlutterView() ): Create a FlutterView and bind it to the FlutterEngine.

These two steps are delegated to FlutterActivityAndFragmentDelegate to implement.

1.2.FlutterActivityAndFragmentDelegate

1.2.1.onAttach

To sum up, onAttach mainly does the following things:

1. Set up flutterEngine:

1.1. Determine whether to obtain from the cache;

1.2. Determine whether there is a custom flutterEngine;

1.3.new a new flutterEngine object;

  1. Attaching the plugin to the host activity will eventually call the onAttachedToActivity method of each plugin.

3. Create PlatformPlugin

4. Register the plugin.

1.2.2.configureFlutterEngine

Let's talk about what configureFlutterEngine (flutterEngine) mainly does. This method is implemented in FlutterActivity. The code is as follows:

It finds the GeneratedPluginRegistrant class through reflection and calls its registerWith method. This class can be found in the /android/java/ directory in the project. It is automatically generated by the flutter tool. It will be generated when we pubspec.yaml and execute the pub get command.

The system uses reflection by default. We can also override this method in MainActivity and call the registerWith method directly.

1.3.FlutterEngine

Let's take a look at the constructor of FlutterEngine. FlutterEngine is a independent flutter runtime environment , through which it can use DartExecutor execute Dart code.

DartExecutor can work with FlutterRenderer to render UI, or it can only run Dart code in the background without rendering UI.

When the first FlutterEngine is initialized, the DartVM will be created, and then multiple FlutterEngines can be created. The DartExecutor corresponding to each FlutterEngine is executed in a different DartIsolate, but the same Native process has only one DartVM.

As you can see, there is still a lot going on here:

1. Initialize AssetsManager.

2. Create DartExecutor and set the corresponding PlatformMessageHandler .

3. Initialize a series of system channels.

4. Initialize FlutterLoader, load Resource resources and apk products such as libflutter.so and libapp.so.

5. Create FlutterRenderer and FlutterEngineConnectionRegistry.

6. Automatically register plugins declared in pubspec.yaml if needed.

Next, let's take a look at the content related to FlutterLoader.

1.4.FlutterLoader

FlutterLoader exists as a singleton, and a process only needs to be initialized once. It is used to load resource files and code products in the apk installation package and must be performed in the main thread.

The startInitialization() method mainly does the following things:

1. Load the meta configuration information passed to the activity;

2. Extract assets resources in the apk installation package, mainly JIT_RELEASE mode, such as vmSnapshotData, isolateSnapshotData, etc.;

3. Load the flutter engine C++ part of the source code, that is, execute 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);
  }
}

The function of this method is to dynamically configure various resource paths and other configurations before the flutter engine starts, --key=value , and then call flutterJNI.init to the C++ layer for processing, and the C++ layer will Save the incoming configuration to a setting object, then create a FlutterMain object based on the setting, and save it as a global static variable g_flutter_main . After initializing DartVM and other steps, the configuration information saved here can be used.

1.5.onStart

According to the life cycle of Activity in Android, onCreate is executed after onStart. Similarly, FlutterView still delegates the operation in onStart to the delegate object to complete.

As you can see, the onStart life cycle does one thing: the entry function that executes the Dart code. Here are some points to note:

  1. DartExecutor will only be executed once, which means that DartExecutor 161f0c7504a6e9 corresponding to a does not support restarting or reloading .

2. The initial route of Dart Navigator is "/" by default. We can override getInitialRoute customize.

3. Dart entry function default main (), rewrite getDartEntrypointFunctionName method can be customized.

  1. executeDartEntrypoint will eventually call the JNI method through the FlutterJNI method to execute. Execute DartIsolate.Run(config) in the UI Thread, find the handle of the Dart entry according to entrypoint_name, run _startIsolate to execute the entry function, and then execute the runApp() of the main function.

So far, the Flutter project has been successfully launched on the Android platform.

2. Application - Hot Update

In fact, one of the main purposes of my exploration of the Flutter startup process this time is to find a hot update solution for Flutter on the Android side. So after reading the whole process, how do we do hot updates?

Several main products of the apk installation package of the flutter app are flutter_assets, libflutter.so and libapp.so:

flutter_assets: Contains resource files, fonts, images, audio, etc. in the flutter application project;

libflutter.so: C++ code related to the flutter embedder layer.

libapp.so: the compiled product of the Dart code we wrote

As long as the file libapp.so can be dynamically replaced before loading, the hot update of flutter code can be achieved.

2.1. Method 1: Reflection to modify FlutterLoader

So where is libapp.so loaded? In fact, as mentioned above in 1.4.FlutterLoader , in the ensureInitializationComplete() method, there is a shellArgs list that stores the resource path configuration information. The key corresponding to libapp.so is "aot-shared-library-name" .

Then, just replace this piece of code and set the path to a custom path to let the framework load the new libapp_fix.so file. The specific steps are:

1. Inherit FlutterLoader, rewrite ensureInitializationComplete(), and set "aot-shared-library-name" to a custom path.

2. Let's see how the FlutterLoader instance is created in the flutterEngine:

flutterLoader = FlutterInjector.instance().flutterLoader();

Then, we only need to instantiate the custom FlutterLoader class and replace the flutterLoader instance in the FlutterInjector with a new instance by reflection.

2.2. Method 2: Rewrite getFlutterShellArgs()

We noticed that in the ensureInitializationComplete() method, AOT_SHARED_LIBRARY_NAME . Only when the file was not found in the relative path, it went back to find the file in the absolute path. Then we only need to set the custom so file path to the "aot-shared-library-name" , so that the framework can only load the latest installation package.

Since the ensureInitializationComplete() method will add all the content in the parameter String[] args to the shellArgs list, we only need to add "aot-shared-library-name=custom path" configuration, we See how this args parameter comes from:

host.getFlutterShellArgs().toArray() even if the source of the args parameter is gone. From the previous analysis, we already know that the host object in the delegate is a reference to FlutterActivity. Let's take a look at how FlutterActivity is implemented:

This is a public method, so we just need to rewrite this method in MainActivity and add the required configuration after getting FlutterShellArgs:

Obviously, this method is simpler and more effective. It should be noted that this configuration will only be loaded in RELEASE mode, so debugging in DEBUG and JIT_RELEASE modes will not work.

3. Summary

Finally, a rough summary:

1. In a pure flutter project, Android hosts the flutter interface in the form of FlutterActivity Native-Flutter hybrid projects can also use FlutterFragment/FlutterView 2 methods, depending on the usage scenario.

2.FlutterActivity delegates most of the work to the implementation of FlutterActivityAndFragmentDelegate.

3. The startup process is mainly the onCreate() and onStart() methods of FlutterActivity.

onCreate() will initialize the FlutterEngine, register each plugin, and then create a FlutterView and bind it to the FlutterEngine.

onStart() is mainly the entry function to execute Dart code through DartExecutor.

4. DartVM is created and initialized when the first FlutterEngine is initialized. Multiple FlutterEngines can be created, one FlutterEngine corresponds to one DartExecutor, and each DartExecutor executes in its own DartIsolate.

5.DartExecutor can work with FlutterRender to render UI, or it can only execute Dart code without rendering UI.

6.FlutterView has two modes: FlutterSurfaceView and FlutterTextureView. As the name suggests, it uses surfaceView and textureView respectively to host flutter views. FlutterSurfaceView has better rendering performance, but views do not support flexible z-index settings in Native-Flutter hybrid projects.

Text/KECHANGZHAO

Pay attention to Dewu Technology and be the most fashionable technical person!

阅读 1.7k
584 声望
1.4k 粉丝
0 条评论
584 声望
1.4k 粉丝
文章目录
宣传栏