Flutter 动态化新知识

2021-05-17 10:40:09 浏览数 (1)

作者:rayszhang,腾讯 PCG 客户端开发工程师

背景

Flutter 的 release 产物会生成 libapp.so 以及放入 assets 的资源,包含了所有业务代码及所用资源。而随着业务越来越多,产物也越来越大。

某业务如要做下发,需要整体更新,牵一发而动全身,流量消耗也很可观。这时自然会产生一个想法,各业务能否独自生成产物,在用到时才下载运行。

而在 Flutter 的官方 git 上,已有不少的 issue 提出了这个问题,比如:

  • https://github.com/flutter/flutter/issues/53672
  • https://github.com/flutter/flutter/issues/16813
  • https://github.com/flutter/flutter/issues/62229
  • https://github.com/flutter/flutter/issues/75079
  • ......

所以在https://github.com/flutter/flutter/issues/57617这个 issue,官方终于开始支持此特性,并命名 deferred components,并在这个 issue 同步进展。

可以看到,3 月份代码已合入 master,并在 gallery 的 demo 演示了此能力,但是文档迟迟没有给出来,对于磨刀霍霍的我们已经等不及直接分析代码了。

分析

支持 deferred components,其实包含了工程结构,构建工具,底层支持等等各个方面,我们尽可能看一下都是如何实现的。既然 gallery 已经有了 demo,那我们就从 gallery 开始。

demo 工程编译

deferred components 只能在非 debug 版本开启,而且只支持 aab 产物,同时 crane 模块使用了 dymanic-feature,还需要 bundle-tools,所以需要如下编译:

  1. flutter build appbundle,生成 app.aab
  2. java -jar bundletool-all-1.5.0.jar build-apks --connected-device --bundle=app.aab --output=app.apks,生成 app.apks
  3. java -jar bundletool-all-1.5.0.jar install-apks --apks=app.apks,安装
demo 工程分析

gallery 的 pubspec.yaml 直接就可以看到 deferred-components 的定义,不难看出 crane 模块做了延迟加载

代码语言:javascript复制
flutter:
  deferred-components:
    - name: crane
      libraries:
        # Only one library from the loading unit is necessary.
        - package:gallery/studies/crane/app.dart
      assets:
        - packages/flutter_gallery_assets/crane/destinations/eat_1.jpg
        - packages/flutter_gallery_assets/crane/destinations/eat_2.jpg
        ......

crane 的调用在 routes.dart 里:

代码语言:javascript复制
Path(
  r'^'   crane_routes.defaultRoute,
  (context, match) => StudyWrapper(
    study: DeferredWidget(crane.loadLibrary,
        () => crane.CraneApp(), // ignore: prefer_const_constructors
        placeholder: DeferredLoadingPlaceholder(name: 'Crane')),
  ),
),

crane 的定义看 import:

代码语言:javascript复制
import 'package:gallery/studies/crane/app.dart' deferred as crane;
import 'package:gallery/studies/crane/routes.dart' as crane_routes;
import 'package:gallery/studies/fortnightly/app.dart' deferred as fortnightly;
import 'package:gallery/studies/fortnightly/routes.dart' as fortnightly_routes;
import 'package:gallery/studies/rally/app.dart' deferred as rally;
import 'package:gallery/studies/rally/routes.dart' as rally_routes;
import 'package:gallery/studies/reply/app.dart' as reply;
import 'package:gallery/studies/reply/routes.dart' as reply_routes;
import 'package:gallery/studies/shrine/app.dart' deferred as shrine;

使用了 deferred 关键字,也看到不只 crane 使用了该关键字,对于 deferred 关键字要特别注意下,在 dart doc 的解释:

Deferred loading (also called lazy loading) allows a web app to load a library on demand, if and when the library is needed. Here are some cases when you might use deferred loadingOnly dart2js supports deferred loading. Flutter, the Dart VM, and dartdevc don’t support deferred loading. For more information, see issue #33118 and issue #27776.

文档写了 deferred 只适用于 web,在其它平台是忽略的,但是 galery 的代码明显不是这种情况,所以应该是 deferred components 对该关键字做了扩展,但是官方文档还没有更新。从工程目录上可以看到使用了 deferred 关键字的模块都有独立的目录。

deferred as 添加了 loadLibrary 方法,是一个 Future,就是用来延迟加载产物的。DeferredWidget 是用来占位的,在 loadLibrary 没返回前显示一个 loading,返回后就创建真正的 widget 显示。

再来看 android 代码,crane 成了独立的 module,从 build.gradle 看到使用了 dynamic feature,并添加了两个目录到 src

crane 的 AndroidManifest.xml 启用了 dynamic feature 的 onDemand:

代码语言:javascript复制
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dist="http://schemas.android.com/apk/distribution"
    package="com.example.gallery.crane">

    <dist:module
        dist:instant="false"
        dist:title="@string/craneName">
        <dist:delivery>
            <dist:on-demand />
        </dist:delivery>
        <dist:fusing dist:include="true" />
    </dist:module>
</manifest>

而 app 的 AndroidManifest.xml 有两个地方不同寻常,一是 application 使用的是FlutterPlayStoreSplitApplication,二是多个了 meta-data。:

代码语言:javascript复制
<application
    android:label="Flutter Gallery"
    android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
    android:icon="@mipmap/ic_launcher">
 ......
 <meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="2:crane,3:,4:,5:,6:,7:,8:,9:,10:,11:"/>
</application>

直接看下FlutterPlayStoreSplitApplication,在 onCreate 里创建了PlayStoreDeferredComponentManager,名字上就可以看出来是用于延迟加载的,注释里也说明了用途,实现的是从 Google Play 下载 dynamic module 的延迟加载:

代码语言:javascript复制
/**
 * Flutter default implementation of DeferredComponentManager that downloads deferred component from
 * the Google Play store as a dynamic feature module.
 */

虽然国内用不了 Google Play,但这个实现方式对我们理解 deferred components 还是很有帮助的。

构造方法调用了initLoadingUnitMappingToComponentNames,我们来看一下,

代码语言:javascript复制
private void initLoadingUnitMappingToComponentNames() {
  String mappingKey = DeferredComponentManager.class.getName()   ".loadingUnitMapping";
  ApplicationInfo applicationInfo = getApplicationInfo();
  if (applicationInfo != null) {
    Bundle metaData = applicationInfo.metaData;
    if (metaData != null) {
      String rawMappingString = metaData.getString(MAPPING_KEY, null);
      if (rawMappingString == null) {
        Log.e(
            TAG,
            "No loading unit to dynamic feature module name found. Ensure '"
                  MAPPING_KEY
                  "' is defined in the base module's AndroidManifest.");
      } else {
        for (String entry : rawMappingString.split(",")) {
          // Split with -1 param to include empty string following trailing ":"
          String[] splitEntry = entry.split(":", -1);
          int loadingUnitId = Integer.parseInt(splitEntry[0]);
          loadingUnitIdToComponentNames.put(loadingUnitId, splitEntry[1]);
          if (splitEntry.length > 2) {
            loadingUnitIdToSharedLibraryNames.put(loadingUnitId, splitEntry[2]);
          }
        }
      }
    }
  }
}

在这里就理解了 AndroidManifest.xml 里 meta-data 的用途,是在这里解析做lodingUnitIdcomponentName映射用的。同时,也可以指定loadingUnitId对应的 so 名称。

然后看installDeferredComponent这个重要的方法,

代码语言:javascript复制
public void installDeferredComponent(int loadingUnitId, String componentName) {
  String resolvedComponentName =
      componentName != null ? componentName : loadingUnitIdToComponentNames.get(loadingUnitId);
  if (resolvedComponentName == null) {
    Log.e(
        TAG, "Deferred component name was null and could not be resolved from loading unit id.");
    return;
  }

  // Handle a loading unit that is included in the base module that does not need download.
  if (resolvedComponentName.equals("") && loadingUnitId > 0) {
    // No need to load assets as base assets are already loaded.
    loadDartLibrary(loadingUnitId, resolvedComponentName);
    return;
  }

  SplitInstallRequest request =
      SplitInstallRequest.newBuilder().addModule(resolvedComponentName).build();

  splitInstallManager
      // Submits the request to install the module through the
      // asynchronous startInstall() task. Your app needs to be
      // in the foreground to submit the request.
      .startInstall(request)
   ......

可以看到如果componentName为空并且loadingUnitId存在,比如 meta-data 里的 3,4,5 等这些 id,就直接执行 loadDartLibrary 加载 so,因为这些 so 没有单独打包,是包含在 base module 里的。

如果有componentName就开始执行 dynamic module 的加载流程,并在 dynamic module 下载完成后执行loadAssetsloadDartLibrary

再看loadDartLibrary是怎么实现的,

代码语言:javascript复制
public void loadDartLibrary(int loadingUnitId, String componentName) {
 ......

  String aotSharedLibraryName = loadingUnitIdToSharedLibraryNames.get(loadingUnitId);
  if (aotSharedLibraryName == null) {
    // If the filename is not specified, we use dart's loading unit naming convention.
    aotSharedLibraryName =
        flutterApplicationInfo.aotSharedLibraryName   "-"   loadingUnitId   ".part.so";
  }

  // Possible values: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64
  String abi;
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    abi = Build.SUPPORTED_ABIS[0];
  } else {
    abi = Build.CPU_ABI;
  }
  String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths.

  // TODO(garyq): Optimize this apk/file discovery process to use less i/o and be more
  // performant and robust.

  // Search directly in APKs first
  List<String> apkPaths = new ArrayList<>();
  // If not found in APKs, we check in extracted native libs for the lib directly.
  List<String> soPaths = new ArrayList<>();
  Queue<File> searchFiles = new LinkedList<>();
  searchFiles.add(context.getFilesDir());
  while (!searchFiles.isEmpty()) {
    File file = searchFiles.remove();
    if (file != null && file.isDirectory()) {
      for (File f : file.listFiles()) {
        searchFiles.add(f);
      }
      continue;
    }
    String name = file.getName();
    if (name.endsWith(".apk") && name.startsWith(componentName) && name.contains(pathAbi)) {
      apkPaths.add(file.getAbsolutePath());
      continue;
    }
    if (name.equals(aotSharedLibraryName)) {
      soPaths.add(file.getAbsolutePath());
    }
  }

  List<String> searchPaths = new ArrayList<>();

  // Add the bare filename as the first search path. In some devices, the so
  // file can be dlopen-ed with just the file name.
  searchPaths.add(aotSharedLibraryName);

  for (String path : apkPaths) {
    searchPaths.add(path   "!lib/"   abi   "/"   aotSharedLibraryName);
  }
  for (String path : soPaths) {
    searchPaths.add(path);
  }

  flutterJNI.loadDartDeferredLibrary(
      loadingUnitId, searchPaths.toArray(new String[apkPaths.size()]));
}

如果 meta-data 没有指定 so 名称,则 loadingUnitId 对应的 so 名称默认为 libapp.so-2.part.so,然后添加各种搜索路径,使用 FlutterJni.loadDartDeferredLibrary 做 so 加载。可以看到,整个 deferred components 一锤定音之处就在 loadDartDeferredLibrary,加载成功之后,dart 代码里的 loadLibrary 就会返回,然后就可以创建新下载的 Widget。

PlayStoreDeferredComponentManager 我们自然是用不了,但是好在它实现的是一个接口 DeferredComponentManager,这也就是给了我们自定义实现延迟加载留了接口,我们自己的实现完全可以从自己的下载通道下载 so 和 asset,然后使用 loadAssets 和 loadDartLibrary 做加载。

从上面的分析我们可以知道,dart 定义的 deferred-components 对应 android 的一个 dynamic module,并且使用 deferred as 指定的库会生成单独的 so,名字形如 libapp.so-x.part.so,x 为 loadingUnitId。每个单独的 so 会和使用的资源打包为单独的 apk,业务可以自定义 DeferredComponentManager 接口实现自己的加载方案。

编译过程分析

到这里为止,我们应该还是有疑惑的,比如 loadingUnitId 是怎么生成的,工程要怎么编译运行,是否必须使用 dynamic feature。下面我们从构建工具切入看下能否知道这些内容。

我们还是从 pubspec.yaml 开始,flutter_tools 处理 pubspec.yaml 的地方在 flutter_manifest.dart,

代码语言:javascript复制
void _validateFlutter(YamlMap? yaml, List<String> errors) {
  if (yaml == null || yaml.entries == null) {
    return;
  }
  for (final MapEntry<Object?, Object?> kvp in yaml.entries) {
    final Object? yamlKey = kvp.key;
    final Object? yamlValue = kvp.value;
    if (yamlKey is! String) {
      errors.add('Expected YAML key to be a string, but got $yamlKey (${yamlValue.runtimeType}).');
      continue;
    }
    switch (yamlKey) {
   ......
      case 'deferred-components':
        _validateDeferredComponents(kvp, errors);
        break;

对 pubspec.yaml 的验证新添加了 deferred-components 选项,验证完成后,外部可以由deferredComponents获取,

代码语言:javascript复制
/// Returns the deferred components configuration if declared. Returns
  /// null if no deferred components are declared.
  late final List<DeferredComponent>? deferredComponents = computeDeferredComponents();
  List<DeferredComponent>? computeDeferredComponents() {
    if (!_flutterDescriptor.containsKey('deferred-components')) {
      return null;
    }
    final List<DeferredComponent> components = <DeferredComponent>[];
    final Object? deferredComponents = _flutterDescriptor['deferred-components'];
    if (deferredComponents is! YamlList) {
      return components;
    }
    for (final Object? component in deferredComponents) {
      if (component is! YamlMap) {
        _logger.printError('Expected deferred component manifest to be a map.');
        continue;
      }
      List<Uri> assetsUri = <Uri>[];
      final List<Object?>? assets = component['assets'] as List<Object?>?;
      if (assets == null) {
        assetsUri = const <Uri>[];
      } else {
        for (final Object? asset in assets) {
          if (asset is! String || asset == null || asset == '') {
            _logger.printError('Deferred component asset manifest contains a null or empty uri.');
            continue;
          }
          try {
            assetsUri.add(Uri.parse(asset));
          } on FormatException {
            _logger.printError('Asset manifest contains invalid uri: $asset.');
          }
        }
      }
      components.add(
        DeferredComponent(
          name: component['name'] as String,
          libraries: component['libraries'] == null ?
              <String>[] : component['libraries'].cast<String>() as List<String>,
          assets: assetsUri,
        )
      );
    }
    return components;
  }

这里会把 name,libraries,assets 封装到DeferredComponent结构中,对于 gallery 来说,就是有个 name 为 crane 的 DeferredComponent。

再看 Android 的编译入口buildGradleApp

代码语言:javascript复制
//lib/src/android/gradle.dart
Future<void> buildGradleApp({
  @required FlutterProject project,
  @required AndroidBuildInfo androidBuildInfo,
  @required String target,
  @required bool isBuildingBundle,
  @required List<GradleHandledError> localGradleErrors,
  bool shouldBuildPluginAsAar = false,
  bool validateDeferredComponents = true,
  bool deferredComponentsEnabled = false,
  int retries = 1,
}) async {
  ......
  if (project.manifest.deferredComponents != null) {
      if (deferredComponentsEnabled) {
        command.add('-Pdeferred-components=true');
        androidBuildInfo.buildInfo.dartDefines.add('validate-deferred-components=$validateDeferredComponents');
      }
      // Pass in deferred components regardless of building split aot to satisfy
      // android dynamic features registry in build.gradle.
      final List<String> componentNames = <String>[];
      for (final DeferredComponent component in project.manifest.deferredComponents) {
        componentNames.add(component.name);
      }
      if (componentNames.isNotEmpty) {
        command.add('-Pdeferred-component-names=${componentNames.join(',')}');
        // Multi-apk applications cannot use shrinking. This is only relevant when using
        // android dynamic feature modules.
        _logger.printStatus(
          'Shrinking has been disabled for this build due to deferred components. Shrinking is '
          'not available for multi-apk applications. This limitation is expected to be removed '
          'when Gradle plugin 4.2  is available in Flutter.', color: TerminalColor.yellow);
        command.add('-Pshrink=false');
      }
    }
  ......
}

如果deferredComponents有值并且deferredComponentsEnable为 true,就添加-Pdeferred-components=true 的参数,并获取所有 DeferredComponent 的 name,添加到-Pdeferred-component-names 的参数,并且禁用 shrink。

这里的关键参数是deferredComponentsEnable,是buildGradleApp参数传入的,而调用buildGradleApp的地方,能传入这个值的就只有buildAab

代码语言:javascript复制
//lib/src/android/gradle.dart
@override
Future<void> buildAab({
  @required FlutterProject project,
  @required AndroidBuildInfo androidBuildInfo,
  @required String target,
  bool validateDeferredComponents = true,
  bool deferredComponentsEnabled = false,
}) async {
  await buildGradleApp(
    project: project,
    androidBuildInfo: androidBuildInfo,
    target: target,
    isBuildingBundle: true,
    localGradleErrors: gradleErrors,
    validateDeferredComponents: validateDeferredComponents,
    deferredComponentsEnabled: deferredComponentsEnabled,
  );
}

buildAabBuildAppBundleCommand这个 command 运行的,该 command 的构造方法会添加deferred-componentsvalidate-deferred-components为 true,

代码语言:javascript复制
//lib/src/commands/build_appbundle.dart
class BuildAppBundleCommand extends BuildSubCommand {
  BuildAppBundleCommand({bool verboseHelp = false}) {
    ......
    argParser.addFlag('deferred-components',
      negatable: true,
      defaultsTo: true,
         help: '......'
    );
    argParser.addFlag('validate-deferred-components',
      negatable: true,
      defaultsTo: true,
         help: '......'
    );

这就说明,如果要使用 deferred components 功能,需要使用 fluter build appbundle 构建 aab 格式产物。

runCommand时添加了DeferredComponentsPrebuildValidator的前置验证器,主要是检查资源和工程文件是否合法,同时也看到,只有非 debug 版本才会有 deferred components 功能,

代码语言:javascript复制
@override
Future<FlutterCommandResult> runCommand() async {

   ......

  if (FlutterProject.current().manifest.deferredComponents != null && boolArg('deferred-components') && boolArg('validate-deferred-components') && !boolArg('debug')) {
    final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
      FlutterProject.current().directory,
      globals.logger,
      globals.platform,
      title: 'Deferred components prebuild validation',
      exitOnFail: true,
    );
    validator.clearOutputDir();
    await validator.checkAndroidDynamicFeature(FlutterProject.current().manifest.deferredComponents);
    validator.checkAndroidResourcesStrings(FlutterProject.current().manifest.deferredComponents);

    validator.handleResults();

    // Delete intermediates libs dir for components to resolve mismatching
    // abis supported by base and dynamic feature modules.
    for (final DeferredComponent component in FlutterProject.current().manifest.deferredComponents) {
      final Directory deferredLibsIntermediate = FlutterProject.current().directory
        .childDirectory('build')
        .childDirectory(component.name)
        .childDirectory('intermediates')
        .childDirectory('flutter')
        .childDirectory(androidBuildInfo.buildInfo.mode.name)
        .childDirectory('deferred_libs');
      if (deferredLibsIntermediate.existsSync()) {
        deferredLibsIntermediate.deleteSync(recursive: true);
      }
    }
  }

   ......

   await androidBuilder.buildAab(
      project: FlutterProject.current(),
      target: targetFile,
      androidBuildInfo: androidBuildInfo,
      validateDeferredComponents: boolArg('validate-deferred-components'),
      deferredComponentsEnabled: boolArg('deferred-components') && !boolArg('debug'),
    );
    return FlutterCommandResult.success();

再看AssembleCommand

代码语言:javascript复制
@override
Future<FlutterCommandResult> runCommand() async {
  final List<Target> targets = createTargets();
  final List<Target> nonDeferredTargets = <Target>[];
  final List<Target> deferredTargets = <AndroidAotDeferredComponentsBundle>[];
  for (final Target target in targets) {
    if (deferredComponentsTargets.contains(target.name)) {
      deferredTargets.add(target);
    } else {
      nonDeferredTargets.add(target);
    }
  }

  ......

  if (FlutterProject.current().manifest.deferredComponents != null
        && decodedDefines.contains('validate-deferred-components=true')
        && deferredTargets.isNotEmpty
        && !isDebug()) {
      // Add deferred components validation target that require loading units.
      target = DeferredComponentsGenSnapshotValidatorTarget(
        deferredComponentsDependencies: deferredTargets.cast<AndroidAotDeferredComponentsBundle>(),
        nonDeferredComponentsDependencies: nonDeferredTargets,
        title: 'Deferred components gen_snapshot validation',
      );
    }
}

createTargets会创建适配 deferred components 的 target,在_kDefaultTargets中有定义,

代码语言:javascript复制
// lib/src/commands/assemble.dart
List<Target> _kDefaultTargets = <Target>[
  ......
  androidArmProfileDeferredComponentsBundle,
  androidArm64ProfileDeferredComponentsBundle,
  androidx64ProfileDeferredComponentsBundle,
  androidArmReleaseDeferredComponentsBundle,
  androidArm64ReleaseDeferredComponentsBundle,
  androidx64ReleaseDeferredComponentsBundle,
  ......

这些 target 都有依赖关系,比如androidArm64ReleaseDeferredComponentsBundle是 AndroidAotDeferredComponentsBundle 类型,其依赖关系如下,

代码语言:javascript复制
AndroidAotDeferredComponentsBundle -> AndroidAotBundle -> AndroidAot

AndroidAot会调用 gen_snapshot 编译 dart 代码,

代码语言:javascript复制
@override
Future<void> build(Environment environment) async {
  final AOTSnapshotter snapshotter = AOTSnapshotter(
    reportTimings: false,
    fileSystem: environment.fileSystem,
    logger: environment.logger,
    xcode: globals.xcode,
    processManager: environment.processManager,
    artifacts: environment.artifacts,
  );
 ......
  if (environment.defines[kDeferredComponents] == 'true') {
    extraGenSnapshotOptions.add('--loading_unit_manifest=$manifestPath');
    outputs.add(environment.fileSystem.file(manifestPath));
  }
 ......

kDeferredComponents=DeferredComponents,就是在buildGradleApp时传入的 deferred-components 参数,如果定义了此参数,就会像 gen_snapshot 添加--loading-unit_manifest参数,该参数是一个内容为 json 格式的文件路径,指定拆分的 so 和资源的描述文件,由 gen_snapshot 在编译时写入,路径如,

代码语言:javascript复制
~/gallery/build/app/intermediates/flutter/release/armeabi-v7a/manifest.json

gen_snapshot 也是根据这个参数来判断要不要做 deferred components 操作,

代码语言:javascript复制
// engine/src/third_party/dart/runtime/gen_snapshot.cc
static void CreateAndWritePrecompiledSnapshot() {
 ......
 if (snapshot_kind == kAppAOTElf) {
    if (strip && (debugging_info_filename == nullptr)) {
      Syslog::PrintErr(
          "Warning: Generating ELF library without DWARF debugging"
          " information.n");
    }
    if (loading_unit_manifest_filename == nullptr) {
      File* file = OpenFile(elf_filename);
      RefCntReleaseScope<File> rs(file);
      File* debug_file = nullptr;
      if (debugging_info_filename != nullptr) {
        debug_file = OpenFile(debugging_info_filename);
      }
      result = Dart_CreateAppAOTSnapshotAsElf(StreamingWriteCallback, file,
                                              strip, debug_file);
      if (debug_file != nullptr) debug_file->Release();
      CHECK_RESULT(result);
    } else {
      File* manifest_file = OpenLoadingUnitManifest();
      result = Dart_CreateAppAOTSnapshotAsElfs(NextElfCallback, manifest_file,
                                               strip, StreamingWriteCallback,
                                               StreamingCloseCallback);
      CHECK_RESULT(result);
      CloseLoadingUnitManifest(manifest_file);
    }
 }
 ......
}

如果没有 loading_unit_manifest 则编译的就是一整个 so,如果有就会做拆分操作,并写入拆分结果(loading unit id)到文件,

代码语言:javascript复制
static void NextElfCallback(void* callback_data,
                            intptr_t loading_unit_id,
                            void** write_callback_data,
                            void** write_debug_callback_data) {
  NextLoadingUnit(callback_data, loading_unit_id, write_callback_data,
                  write_debug_callback_data, elf_filename, "so");
}

static void NextLoadingUnit(void* callback_data,
                            intptr_t loading_unit_id,
                            void** write_callback_data,
                            void** write_debug_callback_data,
                            const char* main_filename,
                            const char* suffix) {
  char* filename = loading_unit_id == 1
                       ? Utils::StrDup(main_filename)
                       : Utils::SCreate("%s-%" Pd ".part.%s", main_filename,
                                        loading_unit_id, suffix);
  File* file = OpenFile(filename);
  *write_callback_data = file;

  if (debugging_info_filename != nullptr) {
    char* debug_filename =
        loading_unit_id == 1
            ? Utils::StrDup(debugging_info_filename)
            : Utils::SCreate("%s-%" Pd ".part.so", debugging_info_filename,
                             loading_unit_id);
    File* debug_file = OpenFile(debug_filename);
    *write_debug_callback_data = debug_file;
    free(debug_filename);
  }

  WriteLoadingUnitManifest(reinterpret_cast<File*>(callback_data),
                           loading_unit_id, filename);

  free(filename);
}

可以看到NextLoadingUnit定义了拆分的 so 的文件名,如果是主 so,就是 libapp.so,如果是分 so,就形如 libapp.[id].part.so

再回到AssembleCommand.runCommand(),在createTargets之后,会创建DeferredComponentsGenSnapshotValidatorTarget的根节点做 build,DeferredComponentsGenSnapshotValidatorTarget的 build 会创建DeferredComponentsGenSnapshotValidator来验证 gen_snapshot 写入的 manifest.json,

代码语言:javascript复制
@override
Future<void> build(Environment environment) async {
  environment.logger.printStatus('rays, DeferredComponentsGenSnapshotValidatorTarget build');
  final DepfileService depfileService = DepfileService(
    fileSystem: environment.fileSystem,
    logger: environment.logger,
  );
  validator = DeferredComponentsGenSnapshotValidator(
    environment,
    title: title,
    exitOnFail: exitOnFail,
  );

  final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits(
      environment.outputDir,
      environment.logger,
      abis: _abis
  );

  validator
    ..checkAppAndroidManifestComponentLoadingUnitMapping(
        FlutterProject.current().manifest.deferredComponents,
        generatedLoadingUnits,
    )
    ..checkAgainstLoadingUnitsCache(generatedLoadingUnits)
    ..writeLoadingUnitsCache(generatedLoadingUnits);

  validator.handleResults();

  depfileService.writeToFile(
    Depfile(validator.inputs, validator.outputs),
    environment.buildDir.childFile('flutter_$name.d'),
  );
}

验证过程会读取 manifest.json,载入 loading uint id,并和 AndroidManifest.xml 里的io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping的 meta-data 做比较,如果不一致,就会重新写入,并生成deferred_components_loading_units.yaml,在下次编译时做比较,如果不一致则会报错并重写deferred_components_loading_units.yaml

总结

至此,支持 deferred components 特性的工程结构和编译过程基本分析完了,总结起来有几点:

  1. 工程的 pubspec.yaml 使用deferred-components关键字定义哪个模块需要延迟加载。
  2. 对于延迟加载的工程在 dart 代码中使用deferred关键字引入,并使用loadLibrary()方法加载。
  3. native 加载可以自定义实现DeferredComponentManager,使用FlutterJNI.loadDartLibrary()FlutterJNI.loadAssets()加载 so 和资源。
  4. 延迟加载的工程需要使用 dynamic-feature 编译。

有了 deferred components,在业务上可以做到按模块编译和发布。基础能力比如性能监控、日志等可以编译为基础 so,在 loading unit id 上占 1,随 app 发布,业务模块统一分配从 2 开始的 loading unit id,同时因为有自定义加载的能力,所以可以避开 Play Store,使用自己的配置分发系统下载模块。

目前 deferred components 只合入了 master 分支,还没有发布,所以期待下个版本的 flutter 发布吧。

p.s. 官方也正在做 ios 的 deferred components,至于能不能成就跟踪此issue

相关热文

视频号最新视频

0 人点赞