关于flutter:Android-Flutter-多实例实践

34次阅读

共计 6150 个字符,预计需要花费 16 分钟才能阅读完成。

引言

Flutter CLI 工具反对将 Flutter Module 打包成 Android AAR 包以供内部依赖应用,即 Flutter AAR。在一个没有应用 Flutter 技术栈的 Android 工程中集成 Flutter AAR 是没有任何问题的,但如果指标工程自身曾经应用了 Flutter 框架,在此基础上再接入 Flutter AAR 就会失败,咱们称之为 Flutter 多实例问题。本文次要介绍在 Android 平台下 Flutter 多实例问题的一种解决方案。

背景

企业的业务往往是简单多样的,如果是 ToC 的业务,咱们大多时候须要开发一个体验良好的利用 APP;而如果是 ToB 的业务,咱们往往须要提供一个易于接入和应用的 SDK。在 ToC 业务上,Flutter 框架提供的跨平台、高效开发与高性能个性,使得挪动端利用开发变得更加简略且高效;那在 ToB 业务上,SDK 的开发是否可能享受 Flutter 框架提供的这些红利呢?这一点对于像咱们网易云信这样的服务、能力提供商而言尤为重要。网易云信是集网易 21 年 IM 以及音视频技术打造的交融通信云服务专家,稳固易用的通信与视频 PaaS 平台,其服务大多以能力 SDK 的模式对外提供,如果可能进步 SDK 的生产效率和研发效力,益处显而易见。所以,下面的问题答案当然是必定的!就像应用 Flutter 开发 APP 一样,咱们同样能够应用 Flutter 进行 SDK 开发,从而在 Android / iOS 甚至更多平台中共享统一的业务逻辑实现,减小人力、进步生产效率和研发效力。

在应用 Flutter 进行 SDK 开发时,产物的打包形式次要有以下两种模式:

  • Flutter Package / Flutter Plugin:该打包形式须要以 Dart 源码模式公布到 Pub.dev 或 GitHub,第三方开发者在接入时实质上是以源码的模式依赖,同时接入方本地须要搭建并引入 Flutter 开发环境。此种形式有显著的缺点:首先,源码发布会将 SDK 外部实现细节齐全裸露在外(Flutter 框架并未提供相似 Proguard 的混同工具),这对企业的非开源我的项目而言是不可承受的;其次,它变相要求接入方应用 Flutter 技术栈,这对于以后没有在指标我的项目中应用 Flutter 开发的接入方而言,门槛较高不说,接入体验也不太敌对。
  • Android AAR:AAR 是 Android 利用官网的依赖模式,并不存在显著的短板。通过 Flutter 框架提供的 CLI 工具,能够很不便地将 Flutter Module 打包成 AAR 公布进来,不必放心透露业务源码,也不损失接入体验。因为打包工具会将 Flutter 层的业务代码编译成 AOT 共享库,而平台层的 Java 业务代码则能够开启混同防止反编译(为了简便,前面对立应用 Flutter AAR 命名由 Flutter Module 打包而成的 Android AAR 包)。

综上所言,对于企业的一个商业 SDK 我的项目来说,如果抉择应用 Flutter 技术栈进行开发,那么应用 Flutter AAR 模式来公布才是明智之举。但其实这又会引入新的问题。在前文 Flutter 混合开发根底 中咱们介绍了,一个 Flutter APP 的包构造,它蕴含有引擎库 libflutter.so、业务库 libapp.so、以及flutter_assets 等局部。同理,一个 Flutter Module 打包进去的 AAR 也会蕴含相似的构造以及产物文件。那在一个 Flutter APP 中,应该以何种姿态接入 Flutter AAR 呢?能够预感的是,它们之间必然存在抵触,文件抵触曾经不言而喻,类、资源、甚至 Flutter Engine 也可能会抵触,这种惯例的 Flutter AAR 包显然是无奈集成到 Flutter APP 工程中应用的。有问题就有答案,接下来,咱们就一起来剖析、摸索该问题的解决方案。

Flutter APP 集成 Flutter AAR 问题剖析

下面说到 Flutter APP 无奈集成惯例打包进去的 Flutter AAR,因为存在一系列的抵触,但具体会呈现什么样的谬误,还是须要咱们真正入手去集成能力晓得。这个环节感兴趣的小伙伴能够亲自动手尝试,不再赘述,上面间接给出论断阐明两者共存存在哪些问题:

  • 构建失败,其实就是因为文件、类抵触导致编译失败。次要抵触有:

    • Flutter 版本依赖抵触:Flutter APP 宿主工程与 Flutter AAR 应用的 Flutter 版本不统一导致,包含 Flutter Embedding Jar 与 Flutter SO Jar,前者蕴含平台层 Java 代码,后者蕴含 libflutter.so 引擎库文件。通过 Gradle 咱们能够解决这个依赖的版本抵触,例如强制应用其中某个版本,但这样做极有可能会呈现运行时谬误。
    • Flutter Plugin 平台代码 / 资源抵触:Flutter APP 和 Flutter AAR 援用了雷同的 Plugin 但版本不统一导致。插件中会蕴含平台层的代码,版本不统一同样可能会导致编译失败或者运行时谬误。
    • GeneratedPluginRegistrant.java 文件抵触:该文件为 Flutter 工具生成的插件主动注册类,用于 Flutter Engine 启动时主动加载所需插件。Flutter APP 与 Flutter AAR 均有对应的类文件,负责加载各自依赖的插件,两者缺一不可。
    • libapp.so 抵触:这是 Dart 代码通过 AOT 生成的动静库,Flutter APP 和 Flutter AAR 都会生成与其对应的 so 库,咱们不能单纯的只应用它们其中之一,因为它们自身蕴含的 AOT 代码是从不同的源码编译过去的。
  • 运行时谬误

    • 同一个 Flutter Engine 不反对加载多个 AOT 库:Flutter Engine 在初始化时会动静链接 libapp.so 这个 AOT 库,解析其中的数据段,并执行代码段中的机器指令。但在咱们的场景中,运行时其实是蕴含有两个 AOT 库的,它们都须要加载到 Flutter Engine 中来,应用同一个 Engine 是无奈满足需要的,因为在 Flutter 的实现中,一个 Engine 只能对应一个 AOT 库。
    • 图片资源、字体库无奈失常显示:此类资源会被打包至 flutter_assets 中,并且会生成对应的 Manifest 资源形容清单文件。但 Flutter APP 生成的资源清单文件会笼罩 Flutter AAR 中的资源清单文件,这样导致 Flutter Engine 在加载资源时,无奈从清单文件中查问到对应的资源,因而加载失败。

以上就是咱们在 Flutter APP 中接入 Flutter AAR 遇到的问题。针对这些问题,咱们首先想到的是,Flutter Team 或者开源社区是不是曾经有此类问题的解决方案了?但在通过调研后发现目前并没有。Flutter 框架是反对多个 Engine 的,包含 Flutter 2.0 新反对的 Engine Group 仅反对加载和运行同一个 AOT 库下的代码,显著不能满足咱们的需要。咱们还给官网提了对应 Issue(https://github.com/flutter/fl… 进行探讨,然而临时还没有失去称心的解决方案,为此咱们不得已走上了本人摸索解决方案的自强之路。

解决方案摸索

通过下面的剖析,咱们曾经理解了接入过程中呈现的具体谬误以及出错起因。在真正着手摸索解决方案前,还应设立指标解决方案应该满足的一些准则:

  • 首先计划应该朝着最小引擎改变、甚至无改变的方向致力。因为 Flutter 框架始终在一直迭代演进,如果咱们批改了引擎这块的逻辑,除非这些改变能通过 PR 进入骨干分支,否则引擎一旦更新,咱们的计划就得从新适配,前期保护工作大。
  • 其次计划应该尽量不依赖宿主工程做额定的革新或反对。首先 Flutter APP 接入 Flutter AAR 就跟一般 Android APP 接入 Android AAR 一样简略,不应引入额定的插件或是 Gradle 脚本;其次 Flutter AAR 和 Flutter APP 的 Flutter 运行时环境应该尽量隔离。

明确指标之后,咱们再来看看动手点在哪里。因为须要尽量避免引擎改变,那应该是自上而下,首先从应用层切入,看是否找到对策。这就须要咱们深刻源码,从上到下理解 Flutter 框架的初始化、运行机制。这里不做独自解说,在具体问题剖析解决上再阐明。当初咱们再回过头来看最后遇到的一系列问题,并尝试使用所把握的 Android、Flutter 框架常识来解决。

Class 抵触解决

Class 抵触是因为 Flutter AAR 与 Flutter APP 都有本人的 Plugins 依赖、以及可能会依赖不同版本的 Flutter Embedding Jar,这些依赖库里都蕴含有平台代码,这会导致编译期类反复而失败。那如何解决这个问题呢?
最简略也是最暴力的办法就是对 Flutter AAR 依赖的所有 Plugin 以及 Embedding Jar 源码进行重命名(批改类名或者包名),尽管能解决问题,但工作量微小、批改面广、不灵便,一旦 Plugin 或 Flutter 版本更新都须要从新批改。

那有没有更好的方法呢?答案是 自定义 ClassLoader。具体的,在构建 Flutter AAR 时,在源代码编译成 .class 阶段实现之后,将所有的插件、Flutter Embedding Jar 对应的 .class 文件收集起来,打包成一个 DEX 文件放入 Flutter AAR 的 assets 中。在运行时,须要将 assets 下的 DEX 文件拷贝到利用的 data 公有目录下,再通过 DexClassLoader 去动静加载这个 DEX。这里须要留神的是 DEX 文件是版本号的概念的,它跟 Flutter AAR 的版本号是绑定的,意味着每次加载这个 DEX 时,咱们首先须要查看以后公有目录下的文件版本是否与 Flutter AAR 版本统一,统一则间接加载即可,不统一须要删除原 DEX 文件并从新拷贝后再加载。要害代码如下:

针对 DEX 文件的加载一般而言咱们只须要应用 DexClassLoader 这个零碎类就行了,但这里咱们须要继承 BaseDexClassLoader,并重写 findClass 办法。

默认类的加载基于双亲委派模型,个别都是先申请父加载器加载,如果父加载器加载失败子加载器才有机会加载。但在这里,咱们 findClass 的逻辑须要反其道而行之。Flutter AAR 须要加载的类应该优先应用子加载器从 DEX 文件中加载,加载失败后能力通过父加载器加载。代码如下:

库文件抵触解决

libflutter.so 是 Flutter Engine 动静库文件,在运行时会被 Flutter Embedder Jar 加载进来。这个库文件抵触,咱们不能单纯应用宿主中同名的库文件,因为两者的 Engine 版本可能不统一以及不违反运行时 Flutter 版本隔离的指标。

这里解决抵触最简略的办法就是 重命名。通过浏览代码,咱们发现 Android 以 so 库的门路为 key 保留所有曾经加载的动静库,即使是完全相同的 so 库,只有文件门路不统一,就能够同时 load 进来。因而,这里通过重命名能解决文件抵触的问题,也不会影响到 so 的加载。

libapp.so 抵触也是相似的,咱们同样须要对 Flutter AAR 中的 libapp.so 重命名。此外,咱们还须要非凡解决这两个 so 的加载流程。因为 Flutter 运行时硬编码了动静库的名称,如果不批改加载流程,在查考库时就会找到 Flutter APP 生成的库文件,而不是咱们 Flutter AAR 的库文件。

Flutter Engine 的初始化是在 FlutterLoader 这个类中,在这里会加载 libflutter.so 并配置一系列的参数初始化 Native Engine。咱们须要做的就是替换 libflutter.so 的加载逻辑,转而去加载重命名后的 Engine 库文件。对于 libapp.so,它并不是在 Java 层加载的,而是由 Native Engine 通过 dlopen 链接的。通过查阅 Engine 的代码咱们发现通过 –aot-shared-library-name 选项能够设置要加载的指标 libapp.so 门路。要害代码如下:

Flutter 资源抵触解决

Flutter 相干资源是打包放到 assets 目录下的,且通过对应的 Manifest 文件来申明,别离是:FontManifest.json 与 AssetsManifest.json 文件。这两个文件别离列出了 Flutter 依赖的所有字体资源与门路映射关系、图片资源与门路映射关系。

Flutter-Engine 在运行时通过这两个文件来解析图片与字体资源,Flutter AAR 中尽管也蕴含了这两个文件,但会被 Flutter APP 宿主中的同名文件笼罩,导致字体或资源无奈加载。所以,这里有两个简略计划:

  • 反对编译期合并对应的资源清单 json 文件;这须要开发 Plugin 插件供宿主应用,实现简单而且接入不敌对;
  • Flutter AAR 中抽离出一个独立的资源包 Package 供 Flutter APP 依赖,资源包中仅蕴含 Flutter AAR 援用的所有图片、字体资源(不蕴含任何业务逻辑,因而能够释怀的公布到 pub 平台),宿主在 Flutter 层依赖这个 Package,这样宿主在构建时 Flutter 工具会合并所有的的资源,并生成残缺的资源清单文件。

至此,咱们解决了 Flutter AAR 与 Flutter APP 的共存问题。当然整个计划落地下来,其中还会碰到其余一些问题,比方:生成的 DEX 文件须要拜访宿主中的其余类的时候,在混同启用的状况下,应该如何保障 DEX 拜访主 ClassLoader 中的类、办法没有问题;再如:Flutter AAR 的 DEX 中如果蕴含有 Android 组件怎么办?Android 四大组件都是须要由利用的主 ClassLoader 进行加载的,如果主 DEX 中没有蕴含这些类,那么必定启动失败;等等诸如此类问题,这里不再一一列举。

总结

下图所示为 Flutter 多实例运行时的架构图。相似于多 Flutter Engine,以上计划实现的多 Flutter 实例,也是通过创立多个 Native 的 AndroidShellHolder 来实现的。不同的是,在多 Engine 下不同的 ShellHolder 绑定雷同的 libapp.so,而多实例下绑定的是不同的 libapp.so,因而该计划能在运行时隔离 Flutter APP 与 Flutter AAR 的 Flutter 运行时环境。

该计划的次要劣势体现在:

  • 无 Engine 定制,可维护性较高
  • Flutter APP 与 Flutter AAR 的 Flutter 版本、运行时环境互相独立

有得必有失,绝对地,在其余方面,该计划有所有余:

  • 应用了独立的 Flutter Engine 库文件,因而会导致包体积减少
  • 会加载两个不同的 Flutter Engine,内存会有所增加

综上,在 SDK 开发中采纳 Flutter 技术,同样可能施展 Flutter 在 APP 开发中的劣势,前提是咱们可能解决好 Flutter 多实例的问题。本文次要解说了 Android Flutter 多实例的一种实现思路,心愿可能对大家有所帮忙。

作者简介

李成达,网易云信资深挪动端开发工程师,热衷于钻研跨平台开发技术以及工程提效,目前次要负责视频会议组件化 SDK 的相干研发工作。

更多技术干货,欢送关注【网易智企技术 +】微信公众号

正文完
 0