Original text: Deferred – Components

By Google engineer Gary Qian

In the last article, we introduced what delay valence is from an operational level. How has it been applied? This article will explore the technical details of the delay component. And how to customize lazy loading.

Introduction to the

Flutter supports building applications that download additional Dart code and static resources at runtime. This reduces the size of the apK installed application and downloads functionality and static resources when the user needs them.

We refer to each individual downloadable Dart library and static resource as a “deferred component.” This feature is currently only available on Android, and the code in the deferred component does not affect other platforms, which normally build applications with all the deferred components and resources during the initial installation.

Lazy loading is only available if the application is compiled into Release or Profile mode. In Debug mode, all delayed components are treated as regular imports and are loaded immediately upon startup. Therefore, hot overloading is still possible in Debug mode.

Gallery case

In the fully deferred Flutter Gallery branch, change all the demo to lazy loading components. Compare the size data of Apk files installed without lazy loading as follows:

Using the delay component:

  • Base – arm64_v8a. Apk – 12325372 bytes
  • Base – master. Apk – 37889309 bytes
  • Installation package size: 50,214,681 bytes

No delay component used:

  • Base – arm64_v8a. Apk – 12521900 bytes
  • Base – master. Apk – 80605796 bytes
  • Installation package size: 93,127,696 bytes

We can see that the compiled code size (BASE-arm64_v8A.apk) has been reduced by about 200kB and the initial package size (base-master.apk) by about 43MB. Overall, the initial installation size was reduced by 46%. Dart code, resource files, and so on are moved into separate components and downloaded at run time only when needed. After installing all the components, the size of the application is only a few kilobytes larger than that of the application without delayed installation.

Application structure of delay component

The delayed Dart library generates “load units” through gen_snapshot(the Dart compiler), and each load unit is output as a split AOT shared library (.so file) when built in profile or release mode. The load unit is the smallest set of libraries that code imports with the Deferred keyword and can be separated from the base library.

The following figure shows the application structure using the delay component, and the delay DART library is compiled into the load unit and packaged into.aabThe “life cycle” of a file.

This example has the following characteristics:

  • Four Dart libraries, of which Dart library lib1 depends on lib2. Lib1, lib3, and lib4 are imported into the main code of the FLUTTER application as delay components.
  • Four load units, of which id 1 is the base unit, and load unit 2 contains both lib1 and lib2. Load units 3 and 4 contain lib3 and lib4, respectively.
  • Three defined delay components, plus an implicit base component. Delay component 1 contains load unit 2 and static resources. Delay component 2 contains load cells 3 and 4 and has no static resources. Deferred component 3 is a component that contains only static resources.
  • app-release.aabIs the complete build output file containing the three delay components as well as the basic components.

The.aab file always contains an unexplicitly declared basic component that contains the core Flutter package and the most basic application code. Any libraries that are not lazily loaded are included in the base load unit. If no load unit other than the base unit is generated, this may mean that the file imported late is incorrect.

For lazy-imported libraries, non-lazy-imported files are compiled into a load unit:

loadLibrary()The lifecycle of the call

Deferred components are triggered to be downloaded, installed, and loaded primarily through the loadLibrary() call in DART. This call is handled differently in Dart2JS and AOT/Native. Here, we tease out how the loadLibrary() call is converted to a deferred component:

Dart native layer Dart_DeferredLoadHandler function called by loadLibrary(), This callback is set by Dart_SetDeferredLoadHandler in DartIsolate::Initialize. Dart internally retrieves the load unit ID assigned to the library and passes it to the callback function. The callback to DartIsolate: : OnDartLoadLibrary.

The load unit ID is then passed to FlutterJNI in the Android embedding layer via the Runtime Controller, Engine, and Platform View. Here, the loading unit ID is passed to the DeferredComponentsManager installDeferredComponent method, ID from an integer to a String name mapping, Identifies the delay component defined by PubSpec to which the request library belongs. This transformation is mapped by meta-data in AndroidManifest, which is created and validated during the build phase.

Download the module after PlayStoreDeferredComponentManager call API. The Module installation locates the.so file and passes the path to engine for dloopen. The Engine sends parsed symbols to the DART ISOLATE to load them into the Dart VM. The entire loading process must be associated with the loading unit ID, otherwise the Future object returned by loadLibrary() will not complete.

Keep in mind that multiple load units may be contained within a delay component, but loadLibrary will only load the DART symbol from the specific DART library called. Each load unit must call loadLibrary separately before it can be used. For components that have already been downloaded, subsequent calls to loadLibrary are not reloaded, but are not synchronous either, with at least one frame between invocation and completion.

Install by delay component name

We also provide the Framework-Side DeferredComponent Utility Class, which allows direct installation by deferring component names.

This method can serve two purposes:

  • Install delay components that have only static resources.
  • Download the delay component ahead of time for later use. However, in order to use the DART code in the pre-downloaded component, loadLibrary() must still be called.

This direct API calls the installDeferredComponent method of DynamicFeatureManager directly through Platform Channels and does not load any dart code for the component due to an unspecified load unit. Only static resources are loaded. To use the Dart code, you must also call loadLibrary().

uninstall

DeferredComponent tool class also provides uninstallDeferredComponent method, this method use the platform operating system unloading request for channels and delete files associated with the specified delay components. The uninstall behavior varies from platform to platform, and in Android file deletion is queued and can take a long time before it is actually executed.

Uninstallation can only be requested with the name of the component to be uninstalled. There is currently no support for unloading by loading the unit ID or calling DART directly.

tool

The delay component must be built as Android App Bundles (.aab) to work properly. If built as a debug file or APK file, DART will compile normally and generate a.so file.

The deferred component builds using the $flutter build appbundle command, which checks for deferred-components in pubspec.yaml to decide whether to delay the build. When the application contains deferred components and the build mode is profile or Release, Gen_Snapshot receives a loading_unit_manifest path that tells gen_Snapshot to generate split AOT artifacts, Contains a base file, and a.so for each delay library in the code base. These segmented units are called “load units” and are assigned an internal integer ID called the load unit ID.

The build process also relies on project Settings to function. Each delay component must correspond to the Android Module defined in the android directory of the application. The base Module is built as an APP, and each add-on should have a Module with the same name as that component. The base module Androidmanifest.xml also needs to contain the mapping between the loading unit ID and the delay component.

The flutter build appbundle command executes a validator that guides the developer to complete the correct build. The validation program is necessary because the load units generated by gen_Snapshot are not known until gen_Snapshot completes compilation. Therefore, some project Settings can only be completed after the gen_SNAPSHOT step.

Because mistakenly importing a deferred component library as a non-deferred component library causes files to be compiled into the base load unit, the deferred component validator also has a mechanism to prevent unexpected changes to the load unit that the application eventually generates. This check will cause the build to fail if the generated load unit does not match the results of the previous run cached in the deferred_COMPONentS_loading_units.yaml file. After detecting an error thrown by a change, if no other changes are made, the build will automatically pass this check the next time it runs. This means that this check is not proof of error, because you can still ignore mismatched load unit errors and continue to build.

Custom implementation

Custom downloads can be implemented without the Android Play store. This is only recommended for advanced developers and is mainly for apps with special needs, such as large static resources, certain specific download behaviors, or regions where the Play Store is not accessible (such as China).

Introduction to the

The Flutter embed layer allows custom implementations to handle custom delayed component downloads and decompression, while still allowing access to the core Dart callback that registers the load unit with the Dart Runtime. This process is more complicated than the default Play Store version.

To implement a custom delay component system, it mainly consists of the following parts:

  • An implementation of the Android embedding layer of DeferredComponentManager that handles communication between applications and servers and extracts.so files and static resources from downloaded components.

  • A tool for packaging components compatible with DeferredComponentManager and interpreting the gen_snapshot output of loading units.

  • The server where the components are stored, if there is no Play store delivered as a dynamic function module, this must be customized.

The following sections provide detailed guidance:

Custom ComponentManager- Android embedded layer

The embedding layer is responsible for downloading and installing the packaged component files. This can be done by inheriting the abstract Class DeferredComponentManager in the Android embedding layer

InstallDeferredComponent is the entry point to this class, which provides the loading unit ID and component name to determine what components to install. LoadLibrary () call transfer only a load cell id, and the framework layer DeferredComponent. InstallDeferredComponent () call need to displacement components of components to load the contains only static resources.

In order to resolve the load unit ID to a specific component, you typically need to store the mapping of the load unit ID to the component name. In the default implementation, we do this by storing a key-value pair data in the androidmanifest.xml in the application, but this can be done in any desired way.

You can be in engine source of shell/platform/android/IO/flutter/embedded/engine/deferredcomponents/DeferredComponentManager found in Java DeferredComponentManager a detailed explanation of each method. The default Play store can achieve in the shell/platform/android/IO/flutter/embedded / engine/deferredcomponents/PlayStoreDeferredComponentManager found in Java, can be used as a rough guide.

To load the Dart library, please provide the loading unit id and the path list and calls FlutterJNI. Loadartdeferredlibrary provide, of these path contains your loadartlibrary. So file. Engine attempts to retrieve each provided path until the file is successfully opened.

To load a new static resource, create a resource manager that can access the newly downloaded static resource. Through FlutterJNI updateJavaAssetManager update.

FlutterJNI instances are passed in through setJNI.

tool

The build tool of Flutter has the ability to boot gen_snapshot to build split AOT and package.so files and static resources into Android dynamic Module. Custom implementations typically cannot use this tool. As a result, you may have to write custom tools to package.so files and static resources to work with your custom DeferredComponentManager.

To have gen_snapshot generate load units and.so shared libraries, pass the — loading_unit_manifest=

configuration to gen_Snapshot. It will create a.json file in your manifestPath that contains the loading unit and the corresponding generated.so library. After that, you can package the SO file and static into whatever format you want to publish on the file server. You also need to parse the files in the DeferredComponentManager implementation class.

File server

Since custom implementations typically do not use the Play store, users should implement a file management service. The implementation of this part is flexible, with the only requirement that it works with the DeferredComponentManager implementation to transfer the files needed to load the Dart shared libraries and static resources.

The last

In fact, according to the case of Gallery, it can be seen that if static resources account for a high proportion in the installation package, lazy loading is very obvious for app volume optimization. Domestic delay component management must be implemented in a customized way, which has its own cost. So, whether this technology is really suitable for business needs to be evaluated. But from a technical standpoint, it’s pretty playable, and the ability to provide dynamically loaded libraries can actually go a lot of directions. If you are interested in it, please follow it, like it, and leave your opinion in the comments section.

The list fluency optimization frame component is about to go through the review process and is expected to be released within two weeks. Welcome to follow me.

Past quality columns:

How to design and implement a high-performance Flutter list

The Flutter core rendering mechanism

Flutter routing design and source code analysis

Flutter event distribution

The most detailed guide to the advancement and optimization of Flutter has been collected in Advance of Flutter or RunFlutter.