13

怎样的Flutter Engine定制流程,才能实现真正“开箱即用”?

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU4MDUxOTI5NA%3D%3D&%3Bmid=2247484875&%3Bidx=1&%3Bsn=13a5071feb60e57fab25e23d926a71a8
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

引言

使用Flutter的过程中,如果遇到Flutter Engine的问题需要对其进行修改定制,那么我们需要对它的编译、打包以及发布流程非常清楚。这次在Flutter升级的过程中,发现之前Flutter Engine编译发布的脚本存在不少问题:

  • 没法做到开箱即用

  • 脚本 分散在多个文件中不便于维护

  • En gine源码准备过程过于复杂,需要对git库重置和切换分支

  • 另外Flu tter Engine从1.5.4升级到1.9.1,Flutter Engine的产物结构发生了变化。

因此,我们 对Engine打包发布的脚本进行了重写,简化编译发布的流程。

背景知识

想要对Engine进行定制,首先就要熟悉它的编译和调试,虽然Flutter官方文档中对Engine的编译有说明,但内容比较分散,很多地方讲解得也不够详细。

通过依赖关系确定代码版本

在我们使用Flutter开发的时候最直接接触的并不是Flutter Engine 而是 Flutter Framework。 所以我们第一步就是要安装我们需要使用的Flutter Framework的版本,比如我们需要使用Flutter 1.9.1 ,则本地拉取对应tag的Flutter 进行安装,从Flutter Framework目录下的bin/internal/engine.version文件中我们可以看到对应的Flutter Engine的版本 ,这个版本是通过Flutter Engine对应commit id(git提交的sha-1哈希值)来表示的。

我们可以先把Flutter Engine的代码clone下来看下,clone之后 checkout到上面的commit节点,Flutter Engine根目录下面有一个比较重要的文件DEPS , 这个文件中描述了所有的依赖,如果你需要对其中的某些依赖比如skia,boringssl做定制的话,那么就需要基于这里面声明的版本来进行相应的修改。

工具链

在编译之前我们还需要了解下Flutter Engine编译所使用的一些工具

  • gclient,https://www.chromium.org/developers/how-tos/depottools/gclient ,这是chromium所使用的一个源码库管理的工具,可以很好的管理源码以及对应的依赖,通过gclinet我们可以获取所有的编译需要的源码和依赖

  • ninja,https://ninja-build.org/  ,编译工具,负责最终的编译工作

  • gn,https://gn.googlesource.com/gn ,负责生成 ninja编译需要的build文件,特别像Flutter这种跨多种操作系统平台跨多种CPU架构的,就需要通过gn生成很多套不同的ninja build文件。

上面的这些工具的使用场景,简单点说就是通过gclient获取Flutter Engine编译所需要的编译环境,源码和依赖库,然后通过gn生成ninja编译所需要的build文件,最终通过ninja来进行编译。

编译

Flutter的编译并不需要我们直接取拉Flutter Engine的源码,都是通过gclient来进行源码和依赖的管理,我们要做的第一步就是创建一个工作目录,比如一个名为engine的目录,目录下创建一个gclient的配置文件.gclient, 此配置文件的语法可以参见 https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/HEAD/README.gclient.md

进入engine目录执行 gclient sync,这个步骤比较耗时,第一次运行,即使100%之后还是会下载东西,我们可以通过进程管理器来查看gclient相应进程(.cipd_client)的网络活动情况,不要提前手动kill掉进程。 第一次gclient sync 执行完成了,engine/src/flutter为Flutter Engine源码的位置,我们需要手动切换到对应的版本分支,然后再次执行gclinet sync对此版本的依赖重新同步下,此次执行会比首次执行快很多。

接下来就是对Engine进行编译了,这里我们以iOS为例,我们编译了iOS模拟器的Flutter Engine的debug产物

gn在生成build文件的时候有不少参数需要我们关注,可以通过类似--ios --android来指定系统平台,不指定则为host平台,比如在macOS中为macOS,在windows中为windows;通过--unoptimized来指定Flutter Engine是否进行debug编译,如果指定了--unoptimized,则打出来的产物会带debug的一些东西,比如额外的log,assert,ios则会带上dSYM信息。所以如果你想要进行Engine源码的调试则必须指定--unoptimized; 另外我们可以通过runtime-mode来指定flutter的运行模式,包含debug,release,profile不指定则为debug。

编译完成后 可以在out对应的目录中看到对应的产物 有两个比较关心的就是 Flutter.framework和clang x64目录下的gen snapshot,其中Flutter.framework是Flutter Engine的编译的结果,gen_snapshot则是担当着dart的编译器。

调试

首先我们可以通过IDE或者flutter命令创建一个demo工程,然后通过命令使用local engine来运行,

flutter run --local-engine-src-path=/Users/Luke/Projects/engine/src  --local-engine=ios debug sim

unopt

在flutter demo工程下通过local engine的方式运行,这里我们使用的是ios模拟器来进行调试的,运行之后确认模拟器可以正常run起来。这个时候我们通过Xcode打开ios目录下的iOS的工程,会发现Generated.xcconfig中多了一些FLUTTER

ENGINE,LOCAL_ENGINE的内容。

这个时候我们可以在main函数中设置断点(swift的工程没有main的情况下,断点设置在@UIApplicationMain下面)。 debug走到断点的时候我们可以在console中通过br set -f FlutterViewController.mm -l 123来设置断点。

当然还有个更简单的方法,就是将local engine对应的生成的iOS的project拖入demo工程,就可以直接在Engine的源码中设置断点。

这两种方法都可以进行断点调试。

Flutter Engine发布流程定制

上面介绍了如何对官方的engine代码进行编译和调试,但是在真实的开发流程中我们并不能直接使用local engine。

自己的代码库

如果你定制的代码库也是放在github上那么直接fork官方的repo进行修改便可以了,如果代码库需要在自己的服务器上,那么步骤稍微多一些,首先在你自己的git服务中创建自己的repo,比如在自己搭建的gitlab中创建一个MyFlutterEngine的repo,后继就可以进行代码库的准备了。

到这里我们就准备好我们自己的Flutter Engine的代码库了,你可以在里面进行代码的修改。

Flutter Engine 产物发布的格式和方式

但是当我们真正用于线上产品打包发布的时候,我们并不会使用local engine的方式来工作。 Flutter Framework的目录下有一个bin/cache的目录(此目录默认是gitignore的),所有的不同架构不同平台的engine的产物都会缓存在下面,通过检查会发现,这下面的engine产物和我们直接编译得出的产物并不完全一致,所以第一步就需要弄清楚bin/cache下engine产物的结构。

这里我们只关心iOS和安卓,iOS的比较简单就三个目录,ios,ios-profile,ios-release,分别对应debug,profile,release的flutter运行模式,每一个其实都是不同CPU架构进行了合并(通过lipo工具进行合并)主要包含armv7,arm64,这里gen_snapshot有两个版本,分别用于arm64和amrv7的架构进行dart的aot编译,

uEFvmiQ.png!web

由于安卓平台中,没法对不同CPU架构进行合并所以安卓产物的目录比较多,

ZVnMnuj.png!web

想知道详细的逻辑可以参见flutter tool中关于cache部分的源码 https://github.com/flutter/flutter/blob/v1.9.1-hotfixes/packages/flutter tools/lib/src/cache.dart, 这些Flutter Engine的构建产物在需要的时候从称之为flutter infra的镜像中下载,在国内可以通过国内的镜像(https://storage.flutter-io.cn/flutter infra)进行下载,具体可以查看 https://flutter.dev/community/china 中的说明。

发布流程

经过以上的了解,我们可以开始着手准备Flutter Engine定制化的发布了。

我们可以通过一个git库来管理我们的发布脚本和一些配置文件,这样可以保证别人只要clone下此库就可以直接使用了。

如果需要支持多Flutter Engine版本的打包发布,可以一个版本对应一个打包发布脚本,将公用的方法比如log,打包状态这些抽离到公用的脚本中。

以下为单个Flutter Engine版本的发布的流程:

VVNv2qQ.png!web

首先我们需要准备一个.gclient文件,实际使用时候可以将此文件做成一个模版文件,每一个Flutter Engine版本对应一个.gclient模版文件,在gclient sync之前将相应版本的模版拷贝成.gclient。

在.gclient的配置中我们可以直接指定好Flutter Engine代码及其对应的revision,如果部分依赖的库也需要修改,则可以在custom_deps中加入需要修改的依赖库的git地址及其revision,指定好revision可以避免首次gclient sync之后需要额外切换Flutter Engine的代码分支后再gclient sync的情况,也不需要手动去修改定制过的依赖的代码库和分支,可以减少不少工作量。

v1.9.1版本的.gclient模版文件:

gclient sync将Flutter Engine代码以及对应的依赖都准备好了之后就是编译的工作了,同步完成后目录下面出现的src目录其本身也是一个git库,具体可查看 https://github.com/flutter/buildroot ,内容主要是Flutter Engine的编译环境,src下面的flutter则为Flutter Engine的代码,下面是具体的编译脚本,这里以iOS为例

执行之后所有的初步的产物都会在out对应的子目录中,现在我们再次进入到Flutter Engine 编译的根目录中,进行产物的组装和发布,在这里我们目前采用了一种比较简单的发布方案,我们将自己的Flutter Framework的bin/cache目录从gitignore中移除,发布的时候就将产物覆盖然后提交到我们自己的Flutter Framework的库中,缺点就是Flutter Framework的git库体积会比较大,而且后继万一官方做一些缓存策略的改变也会被影响到。

下面以iOS debug的产物为例,release,profile都是类似的过程:

收益

  • 通过将此脚本,只要我们下载好发布工具库,直接执行脚本就可以自动开始编译发布了,真正做到开箱即用,免去别的配置和准备。

  • .gclient 使用模版文件,不同版本的engine对应不同的模版,打包时拷贝执行

  • .gclient 中指定好版本分支和自定义的依赖信息,源代码和依赖sync后一步到位,避免二次切换分支

  • 打包流程脚本和公用方法分离,不同版本的打包脚本独立分开,通用方法共享,方便维护

后续计划

前面提到将产物放到Flutter Framework的bin/cache目录下并不是最优的方案,通过官方的文档可以知道通过设置FLUTTER STORAGE BASE URL的环境变量可以更改flutter infra的镜像的地址,所以后继的方案就是搭建自己的flutter infra镜像,产物编译完成后提交到自己的镜像网站中。

闲鱼团队是Flutter+Dart FaaS前后端一体化新技术的行业领军者,就是现在! 客户端/服务端java/架构/前端/质量工程师 面向社会招聘,base杭州阿里巴巴西溪园区,一起做有创想空间的社区产品、做深度顶级的开源项目,一起拓展技术边界成就极致!

*投喂简历给小闲鱼→ [email protected]

quQ3Mn7.jpg!web

ZriEn26.png!web

开源项目、峰会直击、关键洞察、深度解读

请认准 闲鱼技术


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK