Android Flutter 多实例实践

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"引言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter CLI 工具支持将 Flutter Module 打包成 Android AAR 包以供外部依赖使用,即 Flutter AAR。在一个没有使用 Flutter 技术栈的 Android 工程中集成 Flutter AAR 是没有任何问题的,但如果目标工程本身已经使用了 Flutter 框架,在此基础上再接入 Flutter AAR 就会失败,我们称之为 Flutter 多实例问题。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"本文主要介绍在 Android 平台下 Flutter 多实例问题的一种解决方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"企业的业务往往是复杂多样的,如果是 ToC 的业务,我们大多时候需要开发一个体验良好的应用 APP;而如果是 ToB 的业务,我们往往需要提供一个易于接入和使用的 SDK。在 ToC 业务上,Flutter 框架提供的跨平台、高效开发与高性能特性,使得移动端应用开发变得更加简单且高效;那在 ToB 业务上,SDK 的开发是否能够享受 Flutter 框架提供的这些红利呢?这一点对于像我们网易云信这样的服务、能力提供商而言尤为重要。网易云信是集网易 21 年 IM 以及音视频技术打造的融合通信云服务专家,稳定易用的通信与视频 PaaS 平台,其服务大多以能力 SDK 的形式对外提供,如果能够提高 SDK 的生产效率和研发效能,好处不言而喻。所以,上面的问题答案当然是肯定的!就像使用 Flutter 开发 APP 一样,我们同样可以使用 Flutter 进行 SDK 开发,从而在 Android / iOS 甚至更多平台中共享一致的业务逻辑实现,减小人力、提高生产效率和研发效能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用 Flutter 进行 SDK 开发时,产物的打包方式主要有以下两种形式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Flutter Package / Flutter Plugin","attrs":{}},{"type":"text","text":":该打包方式需要以 Dart 源码形式发布到 Pub.dev 或 GitHub,第三方开发者在接入时本质上是以源码的形式依赖,同时接入方本地需要搭建并引入 Flutter 开发环境。此种方式有明显的缺陷:首先,源码发布会将 SDK 内部实现细节完全暴露在外( Flutter 框架并未提供类似 Proguard 的混淆工具),这对企业的非开源项目而言是不可接受的;其次,它变相要求接入方使用 Flutter 技术栈,这对于当前没有在目标项目中使用 Flutter 开发的接入方而言,门槛较高不说,接入体验也不太友好。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Android AAR","attrs":{}},{"type":"text","text":":AAR 是 Android 应用官方的依赖形式,并不存在明显的短板。通过 Flutter 框架提供的 CLI 工具,可以很方便地将 Flutter Module 打包成 AAR 发布出去,不用担心泄漏业务源码,也不损失接入体验。因为打包工具会将 Flutter 层的业务代码编译成 AOT 共享库,而平台层的 Java 业务代码则可以开启混淆避免反编译(为了简便,后面统一使用 Flutter AAR 命名由 Flutter Module 打包而成的 Android AAR 包)。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"综上所言,对于企业的一个商业 SDK 项目来说,如果选择使用 Flutter 技术栈进行开发,那么使用 Flutter AAR 形式来发布才是明智之举。但其实这又会引入新的问题。在前文 ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/nFsko8urXj_y0t0SzAz_ug","title":null,"type":null},"content":[{"type":"text","text":"Flutter 混合开发基础","attrs":{}}]},{"type":"text","text":" 中我们介绍了,一个 Flutter APP 的包结构,它包含有引擎库 `libflutter.so`、业务库 `libapp.so`、 以及`flutter_assets` 等部分。同理,一个 Flutter Module 打包出来的 AAR 也会包含类似的结构以及产物文件。那在一个 Flutter APP 中,应该以何种姿势接入 Flutter AAR 呢?可以预见的是,它们之间必然存在冲突,文件冲突已经显而易见,类、资源、甚至 Flutter Engine 也可能会冲突,这种常规的 Flutter AAR 包显然是无法集成到 Flutter APP 工程中使用的。有问题就有答案,接下来,我们就一起来分析、探索该问题的解决方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Flutter APP 集成 Flutter AAR 问题分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面说到 Flutter APP 无法集成常规打包出来的 Flutter AAR,因为存在一系列的冲突,但具体会出现什么样的错误,还是需要我们真正动手去集成才能知道。这个环节感兴趣的小伙伴可以亲自动手尝试,不再赘述,下面直接给出结论说明两者共存存在哪些问题:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"构建失败,其实就是因为文件、类冲突导致编译失败。主要冲突有:","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 版本依赖冲突:Flutter APP 宿主工程与 Flutter AAR 使用的 Flutter 版本不一致导致,包括 Flutter Embedding Jar 与 Flutter SO Jar,前者包含平台层 Java 代码,后者包含 libflutter.so 引擎库文件。通过 Gradle 我们可以解决这个依赖的版本冲突,例如强制使用其中某个版本,但这样做极有可能会出现运行时错误。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter Plugin 平台代码 / 资源冲突:Flutter APP 和 Flutter AAR 引用了相同的 Plugin 但版本不一致导致。插件中会包含平台层的代码,版本不一致同样可能会导致编译失败或者运行时错误。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GeneratedPluginRegistrant.java 文件冲突:该文件为 Flutter 工具生成的插件自动注册类,用于 Flutter Engine 启动时自动加载所需插件。Flutter APP 与 Flutter AAR 均有对应的类文件,负责加载各自依赖的插件,两者缺一不可。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libapp.so 冲突:这是 Dart 代码经过 AOT 生成的动态库,Flutter APP 和 Flutter AAR 都会生成与其对应的 so 库,我们不能单纯的只使用它们其中之一,因为它们本身包含的 AOT 代码是从不同的源码编译过来的。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行时错误","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同一个 Flutter Engine 不支持加载多个 AOT 库:Flutter Engine 在初始化时会动态链接 libapp.so 这个 AOT 库,解析其中的数据段,并执行代码段中的机器指令。但在我们的场景中,运行时其实是包含有两个 AOT 库的,它们都需要加载到 Flutter Engine 中来,使用同一个Engine 是无法满足需求的,因为在 Flutter 的实现中,一个 Engine 只能对应一个 AOT 库。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"图片资源、字体库无法正常显示:此类资源会被打包至 flutter_assets 中,并且会生成对应的 Manifest 资源描述清单文件。但 Flutter APP 生成的资源清单文件会覆盖 Flutter AAR 中的资源清单文件,这样导致 Flutter Engine 在加载资源时,无法从清单文件中查询到对应的资源,因此加载失败。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是我们在 Flutter APP 中接入 Flutter AAR 遇到的问题。针对这些问题,我们首先想到的是,Flutter Team 或者开源社区是不是已经有此类问题的解决方案了?但在经过调研后发现目前并没有。Flutter 框架是支持多个 Engine 的,包括 Flutter 2.0 新支持的 Engine Group 仅支持加载和运行同一个 AOT 库下的代码,明显不能满足我们的需求。我们还给官方提了对应 Issue(https://github.com/flutter/flutter/issues/64542) 进行讨论,但是暂时还没有得到满意的解决方案,为此我们不得已走上了自己探索解决方案的自强之路。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"解决方案探索","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过上面的分析,我们已经了解了接入过程中出现的具体错误以及出错原因。在真正着手探索解决方案前,还应设立目标解决方案应该满足的一些原则:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先方案应该朝着最小引擎改动、甚至无改动的方向努力。因为 Flutter 框架一直在不断迭代演进,如果我们修改了引擎这块的逻辑,除非这些改动能通过 PR 进入主干分支,否则引擎一旦更新,我们的方案就得重新适配,后期维护工作大。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次方案应该尽量不依赖宿主工程做额外的改造或支持。首先 Flutter APP 接入 Flutter AAR 就跟普通 Android APP 接入 Android AAR 一样简单,不应引入额外的插件或是 Gradle 脚本;其次 Flutter AAR 和 Flutter APP 的 Flutter 运行时环境应该尽量隔离。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"明确目标之后,我们再来看看入手点在哪里。由于需要尽量避免引擎改动,那应该是自上而下,首先从应用层切入,看能否找到对策。这就需要我们深入源码,从上到下了解 Flutter 框架的初始化、运行机制。这里不做单独讲解,在具体问题分析解决上再说明。现在我们再回过头来看最初遇到的一系列问题,并尝试运用所掌握的 Android 、Flutter 框架知识来解决。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Class 冲突解决","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Class 冲突是因为 Flutter AAR 与 Flutter APP 都有自己的 Plugins 依赖、以及可能会依赖不同版本的 Flutter Embedding Jar,这些依赖库里都包含有平台代码,这会导致编译期类重复而失败。那如何解决这个问题呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最简单也是最暴力的方法就是对 Flutter AAR 依赖的所有 Plugin 以及 Embedding Jar 源码进行重命名(修改类名或者包名),虽然能解决问题,但工作量巨大、修改面广、不灵活,一旦 Plugin 或 Flutter 版本更新都需要重新修改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那有没有更好的办法呢?答案是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"自定义ClassLoader","attrs":{}},{"type":"text","text":"。具体的,在构建 Flutter AAR 时,在源代码编译成 .class 阶段完成之后,将所有的插件、Flutter Embedding Jar 对应的 .class 文件搜集起来,打包成一个 DEX 文件放入 Flutter AAR 的 assets 中。在运行时,需要将 assets 下的 DEX 文件拷贝到应用的 data 私有目录下,再通过 DexClassLoader 去动态加载这个 DEX。这里需要注意的是 DEX 文件是版本号的概念的,它跟 Flutter AAR 的版本号是绑定的,意味着每次加载这个 DEX 时,我们首先需要检查当前私有目录下的文件版本是否与 Flutter AAR 版本一致,一致则直接加载即可,不一致需要删除原 DEX 文件并重新拷贝后再加载。关键代码如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/41/4102ed23983215bad709e2201fa03398.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bd/bd40c84a1c86f9e7d955fa5af61d707e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"针对 DEX 文件的加载一般而言我们只需要使用 DexClassLoader 这个系统类就行了,但这里我们需要继承 BaseDexClassLoader,并重写 findClass 方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默认类的加载基于双亲委派模型,一般都是先请求父加载器加载,如果父加载器加载失败子加载器才有机会加载。但在这里,我们 findClass 的逻辑需要反其道而行之。Flutter AAR 需要加载的类应该优先使用子加载器从 DEX 文件中加载,加载失败后才能通过父加载器加载。代码如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/27/279f9b5aff52158d48d4cb8e3d03a3e1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"库文件冲突解决","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libflutter.so 是Flutter Engine 动态库文件,在运行时会被 Flutter Embedder Jar 加载进来。这个库文件冲突,我们不能单纯使用宿主中同名的库文件,因为两者的 Engine 版本可能不一致以及不违背运行时 Flutter 版本隔离的目标。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里解决冲突最简单的方法就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重命名","attrs":{}},{"type":"text","text":"。通过阅读代码,我们发现 Android 以 so 库的路径为 key 保存所有已经加载的动态库,即便是完全相同的 so 库,只要文件路径不一致,就可以同时 load 进来。因此,这里通过重命名能解决文件冲突的问题,也不会影响到 so 的加载。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libapp.so 冲突也是类似的,我们同样需要对 Flutter AAR 中的 libapp.so 重命名。此外,我们还需要特殊处理这两个 so 的加载流程。因为 Flutter 运行时硬编码了动态库的名称,如果不修改加载流程,在查考库时就会找到 Flutter APP 生成的库文件,而不是我们 Flutter AAR 的库文件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter Engine 的初始化是在 FlutterLoader 这个类中,在这里会加载 libflutter.so 并配置一系列的参数初始化 Native Engine。我们需要做的就是替换 libflutter.so 的加载逻辑,转而去加载重命名后的 Engine 库文件。对于 libapp.so ,它并不是在 Java 层加载的,而是由 Native Engine 通过 dlopen 链接的。通过查阅 Engine 的代码我们发现通过 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"--aot-shared-library-name","attrs":{}},{"type":"text","text":" 选项可以设置要加载的目标 libapp.so 路径。关键代码如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/80438f2b18258c92f24c7babdb917600.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Flutter 资源冲突解决","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 相关资源是打包放到 assets 目录下的,且通过对应的 Manifest 文件来声明,分别是:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"FontManifest.json 与 AssetsManifest.json ","attrs":{}},{"type":"text","text":"文件。这两个文件分别列出了 Flutter 依赖的所有字体资源与路径映射关系、图片资源与路径映射关系。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter-Engine 在运行时通过这两个文件来解析图片与字体资源,Flutter AAR 中虽然也包含了这两个文件,但会被 Flutter APP 宿主中的同名文件覆盖,导致字体或资源无法加载。所以,这里有两个简单方案:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持编译期合并对应的资源清单 json 文件;这需要开发 Plugin 插件供宿主使用,实现复杂而且接入不友好;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Flutter AAR 中抽离出一个独立的资源包 Package 供 Flutter APP 依赖,资源包中仅包含 Flutter AAR 引用的所有图片、字体资源(不包含任何业务逻辑,因此可以放心的发布到pub平台),宿主在 Flutter 层依赖这个 Package,这样宿主在构建时 Flutter 工具会合并所有的的资源,并生成完整的资源清单文件","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,我们解决了 Flutter AAR 与 Flutter APP 的共存问题。当然整个方案落地下来,其中还会碰到其他一些问题,比如:生成的 DEX 文件需要访问宿主中的其他类的时候,在混淆启用的情况下,应该如何保证 DEX 访问主ClassLoader中的类、方法没有问题;再如:Flutter AAR 的 DEX 中如果包含有 Android 组件怎么办?Android 四大组件都是需要由应用的主ClassLoader进行加载的,如果主 DEX 中没有包含这些类,那么肯定启动失败;等等诸如此类问题,这里不再一一列举。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下图所示为 Flutter 多实例运行时的架构图。类似于多 Flutter Engine,以上方案实现的多 Flutter 实例,也是通过创建多个 Native 的 AndroidShellHolder 来实现的。不同的是,在多 Engine 下不同的 ShellHolder 绑定相同的 libapp.so,而多实例下绑定的是不同的 libapp.so ,因此该方案能在运行时隔离 Flutter APP 与 Flutter AAR 的 Flutter 运行时环境。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/66/66ed58df8353a64be956d57c5c195b24.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该方案的主要优势表现在:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"无 Engine 定制,可维护性较高","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter APP 与 Flutter AAR 的 Flutter 版本、运行时环境相互独立","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有得必有失,相对地,在其他方面,该方案有所不足:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用了独立的 Flutter Engine 库文件,因此会导致包体积增加","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"会加载两个不同的 Flutter Engine ,内存会有所增加","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"综上,在 SDK 开发中采用 Flutter 技术,同样能够发挥 Flutter 在 APP 开发中的优势,前提是我们能够解决好 Flutter 多实例的问题。本文主要讲解了 Android Flutter 多实例的一种实现思路,希望能够对大家有所帮助。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"作者简介","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"李成达,网易云信资深移动端开发工程师,热衷于研究跨平台开发技术以及工程提效,目前主要负责视频会议组件化 SDK 的相关研发工作。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章