手把手教你分离flutter ios 编译产物--附工具

手把手教你分离flutter ios 编译产物–附工具

1、为什么写这篇文章?

Flutter ios安装包size的裁剪一直是个备受关注的主题,年前字节跳动分享了一篇文章(https://juejin.im/post/5de8a32c51882512664affa4),提到了ios分离AOT编译产物,把里面的数据段和资源提取出来以减少安装包size,但文章里面并没有展开介绍如何实现,这篇文章会很详细的分析如何分离AOT编译产物。并给出工具,方便没编译flutter engine经验的同学也可以快速的实现这功能。

2、ios编译产物构成

本文主要分析App.framework里面的生成流程,以及如何分离AOT编译产物,App.framework的构成如下图所示。

在这里插入图片描述

主要有App动态库二进制文件、flutter_assets还有Info.plist三部分构成,而App动态库二进制文件又由4部分构成,vm的数据段、代码段和isolate的数据段、代码段。其中flutter_assets、vm数据段、isolate数据段都是可以不打包到ipa中,可以从外部document中加载到,这就让我们有缩减ipa包的可能了。

3、真实线上项目AOT编译产物前后对比

很多人肯定会关心最终缩减的效果。我们先给出一个真实线上项目,用官方编译engine和用分离产物的engine生成的App.framework的对比图。

官方engine生成的App.framework构成如下,其中App动态库二进制文件19.2M,flutter_assets有3.3M,共22.5M。

在这里插入图片描述

在这里插入图片描述

用分离产物的engine生成的App.framework构成如下,只剩App动态库二进制文件14.8M。
在这里插入图片描述

App.framework从22.5裁到14.8M,不同项目可能不一样。

4、AOT编译产物生成原理及分离方法介绍

每次xcode项目进行进行构建前都会运行xcode_backend.sh这个脚本进行flutter产物打包,我们从xcode_backend.sh开始分析。从上文分析App.framework里面总共有三个文件生成二进制文件App、资源文件flutter_assets目录和Info.plist文件,这里面我们只关心二进制文件App和flutter_assets目录是怎样生成的。

4.1、App文件生成流程

4.1.1、xcode_backend.sh

分析xcode_backend.sh,我们可以发现生成App和flutter_assets的关键shell代码如下


# App动态库二进制文件
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics           \
  ${verbose_flag}                                                       \
  build aot                                                             \
  --output-dir="${build_dir}/aot"                                       \
  --target-platform=ios                                                 \
  --target="${target_path}"                                             \
  --${build_mode}                                                       \
  --ios-arch="${archs}"                                                 \
  ${flutter_engine_flag}                                                \
  ${local_engine_flag}                                                  \
  ${bitcode_flag}

.
.
.

RunCommand cp -r -- "${app_framework}" "${derived_dir}"


# 生成flutter_assets
RunCommand "${FLUTTER_ROOT}/bin/flutter"     \
    ${verbose_flag}                                                         \
    build bundle                                                            \
    --target-platform=ios                                                   \
    --target="${target_path}"                                               \
    --${build_mode}                                                         \
    --depfile="${build_dir}/snapshot_blob.bin.d"                            \
    --asset-dir="${derived_dir}/App.framework/${assets_path}"               \
    ${precompilation_flag}                                                  \
    ${flutter_engine_flag}                                                  \
    ${local_engine_flag}                                                    \
    ${track_widget_creation_flag}

4.1.2、${FLUTTER_ROOT}/bin/flutter

从上面的代码可以看到这里调用了的远行了 /bin/flutter 这个shell脚本,这里介绍另一篇讲解Flutter命令执行机制的文章/bin/flutter 里面提到真正运行代码的是

...
FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"

DART="$DART_SDK_PATH/bin/dart"
PUB="$DART_SDK_PATH/bin/pub"

//真正的执行逻辑
"$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"

//等价于下面的命令
/bin/cache/dart-sdk/bin/dart $FLUTTER_TOOL_ARGS "bin/cache/flutter_tools.snapshot" "$@"

就是说通过dart命令运行flutter_tools.snapshot这个产物

###4.1.3、dart代码

flutter_tools.snapshot的入口是

[-> flutter/packages/flutter_tools/bin/flutter_tools.dart]

import 'package:flutter_tools/executable.dart' as executable;

void main(List<String> args) {
  executable.main(args); 
}

import 'runner.dart' as runner;

Future<void> main(List<String> args) async {
  ...
  await runner.run(args, <FlutterCommand>[
    AnalyzeCommand(verboseHelp: verboseHelp),
    AttachCommand(verboseHelp: verboseHelp),
    BuildCommand(verboseHelp: verboseHelp),
    ChannelCommand(verboseHelp: verboseHelp),
    CleanCommand(),
    ConfigCommand(verboseHelp: verboseHelp),
    CreateCommand(),
    DaemonCommand(hidden: !verboseHelp),
    DevicesCommand(),
    DoctorCommand(verbose: verbose),
    DriveCommand(),
    EmulatorsCommand(),
    FormatCommand(),
    GenerateCommand(),
    IdeConfigCommand(hidden: !verboseHelp),
    InjectPluginsCommand(hidden: !verboseHelp),
    InstallCommand(),
    LogsCommand(),
    MakeHostAppEditableCommand(),
    PackagesCommand(),
    PrecacheCommand(),
    RunCommand(verboseHelp: verboseHelp),
    ScreenshotCommand(),
    ShellCompletionCommand(),
    StopCommand(),
    TestCommand(verboseHelp: verboseHelp),
    TraceCommand(),
    TrainingCommand(),
    UpdatePackagesCommand(hidden: !verboseHelp),
    UpgradeCommand(),
    VersionCommand(),
  ], verbose: verbose,
     muteCommandLogging: muteCommandLogging,
     verboseHelp: verboseHelp,
     overrides: <Type, Generator>{
       CodeGenerator: () => const BuildRunner(),
     });
}

经过一轮调用后,真正编译产物的类在 GenSnapshot.run,调用栈http://gityuan.com/2019/09/07/flutter_run/这篇文章有详细介绍,这里就不细说了

[-> lib/src/base/build.dart]

class GenSnapshot {

  Future<int> run({
    @required SnapshotType snapshotType,
    IOSArch iosArch,
    Iterable<String> additionalArgs = const <String>[],
  }) {
    final List<String> args = <String>[
      '--causal_async_stacks',
    ]..addAll(additionalArgs);
    //获取gen_snapshot命令的路径
    final String snapshotterPath = getSnapshotterPath(snapshotType);

    //iOS gen_snapshot是一个多体系结构二进制文件。 作为i386二进制文件运行将生成armv7代码。 作为x86_64二进制文件运行将生成arm64代码。
    // /usr/bin/arch可用于运行具有指定体系结构的二进制文件
    if (snapshotType.platform == TargetPlatform.ios) {
      final String hostArch = iosArch == IOSArch.armv7 ? '-i386' : '-x86_64';
      return runCommandAndStreamOutput(<String>['/usr/bin/arch', hostArch, snapshotterPath]..addAll(args));
    }
    return runCommandAndStreamOutput(<String>[snapshotterPath]..addAll(args));
  }
}

GenSnapshot.run具体命令根据前面的封装,最终等价于:

//这是针对iOS的genSnapshot命令
/usr/bin/arch -x86_64 flutter/bin/cache/artifacts/engine/ios-release/gen_snapshot
  --causal_async_stacks
  --deterministic
  --snapshot_kind=app-aot-assembly
  --assembly=build/aot/arm64/snapshot_assembly.S
  build/aot/app.dill

此处gen_snapshot是一个二进制可执行文件,所对应的执行方法源码为third_party/dart/runtime/bin/gen_snapshot.cc
这个文件是flutter engine里面文件,需要拉取engine的代码才能修改,编译flutter engine 可以参考文章手把手教你编译Flutte

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值