一、布景

  在根据 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 渠道中的接入进程,即:

  1. 创立 FlutterEngine(一个 FlutterView 对应一个 FlutterEngine)
  2. 创立 FlutterView 并增加进 Android View 容器
  3. 确保 FlutterEngine 在恰当的时机调用FlutterEngine.lifecycleChannel.appIsResumed()
  4. FlutterEngine 履行自定义或默许的 Dart EntryPoint
  5. 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 默许撑满父布局

  过多的因素无疑会造成杂乱性的陡增,所以结合咱们自己的事务特色,进行范围的收窄。 由此规定

  1. Android 增加 FlutterView 的时分清晰指定 JS 组件能显现的最大范围(GSL 结构协议指定);
  2. Flutter Dart UI 不干涉尺度设定,默许撑满外部容器
  3. Kraken 不指定 ViewPort size,默许撑满外部容器
  4. 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 结构和运用办法进行验证,分别是:

  1. new FlutterEngine()

最原始的办法,将一个 FlutterEngine 所需求的一切资源都构建出来,占用内存最大。单个构建本钱:瞬时暴增 124.2MB(89.1->213.3),安稳一段时刻后,阅历两段开释降为 192.1MB 至 165.5MB

Android 动态加载 JS — Flutter Kraken

再次增加 FlutterView,构建 FlutterEngine,每次增量约 64MB

Android 动态加载 JS — Flutter Kraken

  1. FlutterEngineGroup.createAndRunEngine()

借助 FlutterEngineGroup 生成的 FlutterEngine 具有常用共享资源(例如 GPU 上下文、字体衡量和阻隔线程的快照)的功能优势,能加快初次烘托的速度、下降推迟并下降内存占用,但每个 FlutterEngine 又坚持其独立性,各自保护路由栈、UI 和运用状况。其构建本钱:首个 FlutterEngine 128.4MB(89.7->218.1),后续衍生构建的 FlutterEngine,官方称 180KB,实测 197KB 根本符合(如下方图 2 所示)

Android 动态加载 JS — Flutter Kraken

  1. FlutterEngineCache.put() / get()

尝试构建一个指向某个 EntryPoint 的 FlutterEngine 后,存入 FlutterEngineCache,要用时再取出复用是否可行?

  官方给出了清晰提示:一个 FlutterView 应该对应一个 FlutterEngine,而且同一个 FlutterEngine 不允许履行屡次 executeDartEntrypoint(DartEntrypoint point)。假如强行将某个现已 attach FlutterView 的 FlutterEngine 绑到另一个 FlutterView 又会有什么反应呢?请见下图:

本来 attach 在第 1 个 FlutterView 的 FlutterEngine,不断地替换 attach 目标到第 2 个、第 3 个

Android 动态加载 JS — Flutter Kraken

经过试验,直接给结论:

  • 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);
});

四、烘托功能

Android 动态加载 JS — Flutter Kraken

如上图所示,两对图片分别展示了烘托 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…