Android 动态加载 JS — Flutter Kraken
一、布景
在根据 GSL 的跨端 UI 结构中,假如咱们只依赖事前制定的已有组件来支撑事务,从事务可变性和开发本钱来说,都会存在不可规避的瓶颈问题。试想:假如 Web 端完结一个杂乱的日历组件开发,并希望其能在设备端上烘托。
在没有动态性的支撑下,设备端一是无法支撑(无法识别日历组件),二是需求复刻这个日历组件开发一遍,发布软件版别,并祈求一切线上用户都能升级到最新软件。
根据这样的布景,端的动态烘托便有其存在的必要和使命。
二、计划
本文标题便已点明咱们探求的是哪种解决计划,即阿里开源的、根据 Flutter 的 Kraken 烘托引擎来完结对 JS 的动态加载。
相信搞过 Flutter 开发的同学都了解 Flutter 导入事务有两种模式:一种是根据纯 Flutter 的开发,另一种是 Flutter 与原生渠道的交融开发。而第二种办法中 Flutter 在 Native 渠道上又有三种 UI 烘托载体,分别为:
- FlutterActivity
- FlutterFragment
- FlutterView
结合咱们自研的跨渠道 UI 结构对动态性烘托的诉求:
- 动态烘托的 JS 组件作为子节点交融进原生 View 树
- 烘托组件支撑与外部作双向通讯
那么咱们根本敲定思路:在 Android 中经过动态创立 FlutterView 衔接 Flutter,并与之做数据的传输;而 Flutter 便借助 Kraken 专注于接收 JS 数据并履行烘托,一起支撑其组件交互事情的跨渠道呼应。
三、完结
Step 1 – Android 引进 Flutter module
这部分现已很成熟了,官文 诚不欺我,自行在上搜搜也能找到其他攻略:即怎么在原生项目中,引进 Flutter 模块。
Step 2 – Android 运用 FlutterView 烘托 Dart UI
正如 官文 所提及的 “Integrating via a FlutterView requires a bit more work than via FlutterActivity and FlutterFragment previously described.” 集成 FlutterView 需求更多的人为操作,没有 FlutterActivity 那么便利。
Github add_to_app 是官方供给的 Demo,根本阐述了一个 FlutterView 在 Android 渠道中的接入进程,即:
- 创立 FlutterEngine(一个 FlutterView 对应一个 FlutterEngine)
- 创立 FlutterView 并增加进 Android View 容器
- 确保 FlutterEngine 在恰当的时机调用FlutterEngine.lifecycleChannel.appIsResumed()
- FlutterEngine 履行自定义或默许的 Dart EntryPoint
- FlutterView 与 FlutterEngine 完结衔接(attach)
上面过程都需求咱们在 Android Native 侧完结,详细代码见 Demo 即可,这儿便不赘述。
需求特别指出的是:FlutterView 默许的 renderSurface 是 SurfaceView,这并不支撑透明布景,且无法融入原生 View 树的,所以在构建 FlutterView 的时分应该运用 TextureView 作为其 renderSurface,即:
val flutterTextureView = FlutterTextureView(this)
flutterTextureView.isOpaque = false
val view = FlutterView(this, flutterTextureView)
Step 3 – Flutter 根据 Kraken 烘托 JS UI
根据 Kraken 官网,将 Kraken 在 Flutter 中跑起来是十分清楚且简略的事情,主张把 官方文档 大体过一遍,对 Kraken 会有个较为全面的知道。
尽管问题不大,但不免仍是踩了一些小坑:
- 本地 Bundle 的加载与官方描绘不符
// 官方描绘:
Kraken kraken = Kraken(
bundle: KrakenBundle.fromUrl('assets://assets/bundle.js')
);
// 实践有效:
Kraken kraken = Kraken(
bundle: KrakenBundle.fromUrl('assets:///assets/bundle.js')
);
// 这两者的不同仅在于 assets: 后是两个 '/' 仍是三个 '/',着实是坑
- JS 组件打包
根据公司内部主流前端技术栈,咱们经过 React Kraken 来完结 JS 组件的开发,依照 Kraken 官方供给的 React Demo 中关于 webpack.config.js 的修正,将其同步至自己的 React 项目,并经过 Kraken Cli 完结项目的 Debug。
留意:一定要确保 webpack 的装备跟 Kraken 的装备 同步,不然经过 npm run build 打出来的 bundle.js 在 Android 端是无法运用的,详细详见 kraken-react-demo。
- JS 组件适配(尺度)
首要咱们必须知道,一个 JS 组件的烘托尺度,是由多个渠道决议的:
(1)Android FlutterView(LayoutParams)
(2)Flutter Dart UI
(3)Kraken viewPort size
(4)React JS widget size
留意:Android add FlutterView 的时分假如不指定巨细,FlutterView 默许撑满父布局
过多的因素无疑会造成杂乱性的陡增,所以结合咱们自己的事务特色,进行范围的收窄。 由此规定:
- Android 增加 FlutterView 的时分清晰指定 JS 组件能显现的最大范围(GSL 结构协议指定);
- Flutter Dart UI 不干涉尺度设定,默许撑满外部容器
- Kraken 不指定 ViewPort size,默许撑满外部容器
- React JS 即可指定详细尺度,亦可设置自适应尺度
假设按一倍图的规范进行尺度的设定,比方一个长宽是 200px 的方块,JS 的尺度设定为:
// React JS
const styles = {
width: '200px',
height: '200px'
}
Android add FlutterView 时的尺度则为:
// Android
addView(flutterView,
DisplayUtil.dip2px(this, 200f),
DisplayUtil.dip2px(this, 200f)
)
Step 4 – Multi Flutter Kraken View
Kraken 计划是否可用的要害因素之一在于是否支撑增加多个视图,而多 FlutterView 的要害是多 FlutterEngine。
Flutter 2.0 之后优化了 FlutterEngine 多实例的资源占用,使得咱们在同一进程,同一界面一起处理多个 Flutter Kraken View 具备了可行性。为此试验 Demo 做了三种 FlutterEngine 结构和运用办法进行验证,分别是:
- new FlutterEngine()
最原始的办法,将一个 FlutterEngine 所需求的一切资源都构建出来,占用内存最大。单个构建本钱:瞬时暴增 124.2MB(89.1->213.3),安稳一段时刻后,阅历两段开释降为 192.1MB 至 165.5MB
再次增加 FlutterView,构建 FlutterEngine,每次增量约 64MB
- FlutterEngineGroup.createAndRunEngine()
借助 FlutterEngineGroup 生成的 FlutterEngine 具有常用共享资源(例如 GPU 上下文、字体衡量和阻隔线程的快照)的功能优势,能加快初次烘托的速度、下降推迟并下降内存占用,但每个 FlutterEngine 又坚持其独立性,各自保护路由栈、UI 和运用状况。其构建本钱:首个 FlutterEngine 128.4MB(89.7->218.1),后续衍生构建的 FlutterEngine,官方称 180KB,实测 197KB 根本符合(如下方图 2 所示)
- FlutterEngineCache.put() / get()
尝试构建一个指向某个 EntryPoint 的 FlutterEngine 后,存入 FlutterEngineCache,要用时再取出复用是否可行?
官方给出了清晰提示:一个 FlutterView 应该对应一个 FlutterEngine,而且同一个 FlutterEngine 不允许履行屡次 executeDartEntrypoint(DartEntrypoint point)。假如强行将某个现已 attach FlutterView 的 FlutterEngine 绑到另一个 FlutterView 又会有什么反应呢?请见下图:
本来 attach 在第 1 个 FlutterView 的 FlutterEngine,不断地替换 attach 目标到第 2 个、第 3 个
经过试验,直接给结论:
-
FlutterEngine 会将数据同步给后边 attach 的 FlutterView,也就是新创立的 FlutterView 跟 FlutterEngine 上一个 attach 的 View 长得如出一辙
-
被 FlutterEngine attach 的 FlutterView 能够呼应 FlutterEngine 中的数据变化,而“失掉” FlutterEngine 的 FlutterView 将不再呼应数据变化,更新 UI
-
失掉 FlutterEngine 的 FlutterView 被点击后,引起的数据更新仍然能同步至被 FlutterEngine attach 的新的 FlutterView
所以,此办法并不是处理多 FlutterView 的良药,一起缓存 FlutterEngine 本身并不能削减 Multi FlutterEngine 的内存占用,它的意义在于利用空间换时刻,削减 FlutterEngine 结构并履行 DartEntrypoint 所占用的时刻。
最终,有个值得你留意的地方:当你在 Flutter Dart 中构建 Kraken 目标时,假如传入了 ChromeDevToolsService 那么你的 FlutterEngine 的内存就无法开释,导致内存泄露。
var kraken = Kraken(
background: Colors.green,
bundle: WebFBundle.fromUrl('assets:///jss/bundle.js'),
javaScriptChannel: javaScriptChannel,
// 坑呐!开启 DevToolService 会导致内存泄露
devToolsService: ChromeDevToolsService(),
);
Step 5 – Kraken 的跨渠道通讯
Kraken 的跨渠道通讯无非要打通两条路,即:
- Native —— Flutter
- Flutter —— Kraken
打通两条路其实也很简略,在官方文档都有教程,在 Kraken React 部分跟我们稍微提个醒,当 JS 项目经过 Kraken cli 运转后,Kraken 会在其 JS window 目标挂上 kraken 目标,假如你用的是 Kraken 的 Fork 项目 OpenWebF,那 window 上挂载的目标则是 webf。详细通讯代码如下(Demo 节选,后续会抽成 Bridge Library):
// React JS Demo
const krakenObj = window.kraken;
// Kraken invoked Flutter method
krakenObj.methodChannel.invokeMethod('onJSCall', new Date().getTime().toString(), ['Param Two'], {
value: 'Param Three',
})
.then(result => {
console.log('Received reply from Kraken', result);
setNativeReply(result);
})
.catch(err => {
console.log('Some error occured', err);
});
// Flutter invode Kraken method
krakenObj.methodChannel.clearMethodCallHandler();
krakenObj.methodChannel.addMethodCallHandler((method, args) => {
var request = method + ' method invoked' + '\n' + 'Its params is : ' + args;
console.log('Received request from Kraken : ' + request);
setNativeRequest(request);
});
四、烘托功能
如上图所示,两对图片分别展示了烘托 FlutterView Dart UI 和 Kraken UI 各个环节所占用的时刻。而每对照片左右两张比照的则是:初次加载和复用 Engine 的二次加载
对其「要害功能数据」做一番记载可得:
烘托场景 | 初始化 FlutterView | 创立 FlutterEngine | 衔接 View to Engine | 烘托 Dart UI | 烘托 JS UI | 总耗时 |
---|---|---|---|---|---|---|
初次烘托 FlutterView | 3ms | 75ms | 11ms | 744ms | \ | 833ms |
二次烘托 FlutterView | 0ms | 2ms | 7ms | 142ms | \ | 151ms |
初次烘托 Kraken FlutterView | 2ms | 72ms | 16ms | 869ms | 133ms | 1092ms |
二次烘托 Kraken FlutterView | 0ms | 1ms | 7ms | 170ms | 78ms | 256ms |
当然上面的数据仅仅参阅,跟机型、跟代码的计算办法都有关系,但总的来说,在初始化一个 FlutterEngine 后对其复用,所到达的总耗时是可接受的。
五、写在最终
网传 Kraken 不在保护了?
现实的确如此,原有的团队已不再保护 Kraken,但重整旗鼓,根据 Kraken fork 了 OpenWebF,目前支撑 Flutter 3.0,其用法除了 API 的名字要从 Kraken 改成 WebF 外,其他运用办法与 Kraken 并没有什么不同。留意前端项目在 Debug 的时分应换成 OpenWebF cli 运转。
怎么看待阿里北海 Kraken 项目行将弃坑?
Kraken 是个好东西,思路好,具有学习价值,也能实践解决咱们的事务痛点,业内一些大厂的轮子也有一些是根据 Kraken,Kraken 在跨端烘托也算是一个里程碑了。
Demo Github
- Android : github.com/TDForLife/N…
- React : github.com/TDForLife/k…