0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Flutter 在鸿蒙系统上移植的小成本实现案例解析

鸿蒙系统HarmonyOS 来源:csdn 作者:杨超 2021-01-26 09:55 次阅读

鸿蒙系统HarmonyOS)是华为推出的一款分布式操作系统,那么如何在保证开发迭代效率的前提下,以相对低的成本将移动应用快速移植到鸿蒙平台上呢?美团外卖 MTFlutter 团队近期做了一次技术探索,成功地实现了 Flutter 对于鸿蒙系统的原生支持。

作者 | 杨超

本文经授权转载自美团技术团队

前言

鸿蒙系统 (HarmonyOS)是华为推出的一款面向未来、面向全场景的分布式操作系统。在传统单设备系统能力的基础上,鸿蒙提出了基于同一套系统能力、适配多种终端形态的分布式理念。自 2020 年 9 月 HarmonyOS 2.0 发布以来,华为加快了鸿蒙系统大规模落地的步伐,预计 2021 年底,鸿蒙系统会覆盖包括手机、平板、智能穿戴、智慧屏、车机在内的数亿台终端设备。对移动应用而言,新的系统理念、新的交互形式,也意味着新的机遇。如果能够利用好鸿蒙的开发生态及其特性能力,可以让应用覆盖更多的交互场景和设备类型,从而带来新的增长点。

与面临的机遇相比,适配鸿蒙系统带来的挑战同样巨大。当前手机端,尽管鸿蒙系统仍然支持安卓 APK 安装及运行,但长期来看,华为势必会抛弃 AOSP,逐步发展出自己的生态,这意味着现有安卓应用在鸿蒙设备上将会逐渐变成“二等公民”。然而,如果在 iOSAndroid 之外再重新开发和维护一套鸿蒙应用,在如今业界越来越注重开发迭代效率的环境下,所带来的开发成本也是难以估量的。因此,通过打造一套合适的跨端框架,以相对低的成本移植应用到鸿蒙平台,并利用好该系统的特性能力,就成为了一个非常重要的选项。

在现有的众多跨端框架当中,Flutter 以其自渲染能力带来的多端高度一致性,在新系统的适配上有着突出的优势。虽然 Flutter 官方并没有适配鸿蒙的计划(https://github.com/flutter/flutter/issues/38437),但经过一段时间的探索和实践,美团外卖 MTFlutter 团队成功实现了 Flutter 对于鸿蒙系统的原生支持。

这里也要提前说明一下,因为鸿蒙系统目前还处于 Beta 版本,所以这套适配方案还没有在实际业务中上线,属于技术层面比较前期的探索。接下来本文会通过原理和部分实现细节的介绍,分享我们在移植和开发过程中的一些经验。希望能对大家有所启发或者帮助。

背景知识和基础概念介绍

在适配开始之前,我们要明确好先做哪些事情。先来回顾一下 Flutter 的三层结构:

pIYBAGAPdUyALZrpAAGJt4RXlO8582.png

在 Flutter 的架构设计中,最上层为框架层,使用 Dart 语言开发,面向 Flutter 业务的开发者

中间层为引擎层,使用 C/C++ 开发,实现了 Flutter 的渲染管线和 Dart 运行时等基础能力;

最下层为嵌入层,负责与平台相关的能力实现。显然我们要做的是将嵌入层移植到鸿蒙上,确切地说,我们要通过鸿蒙原生提供的平台能力,重新实现一遍 Flutter 嵌入层。

对于 Flutter 嵌入层的适配,Flutter 官方有一份不算详细的指南(https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders),实际操作起来成本很高。由于鸿蒙的业务开发语言仍然可用 Java,在很多基础概念上与 Android 也有相似之处(如下表所示),我们可以从 Android 的实现入手,完成对鸿蒙的移植。

o4YBAGAPdVyAaf_yAAEjXmePLb8862.png

Flutter 在鸿蒙上的适配

如前文所述,要完成 Flutter 在新系统上的移植,我们需要完整实现 Flutter 嵌入层要求的所有子模块,而从能力支持角度,渲染、交互以及其他必要的原生平台能力是保证 Flutter 应用能够运行起来的最基本的要素,需要优先支持。接下来会依次进行介绍。

1. 渲染流程打通

我们再来回顾一下 Flutter 的图像渲染流程。如图所示,设备发起垂直同步(VSync)信号之后,先经过 UI 线程的渲染管线(Animate/Build/Layout/Paint),再经过 Raster 线程的组合和栅格化,最终通过 OpenGL 或 Vulkan 将图像上屏。这个流程的大部分工作都由框架层和引擎层完成,对于鸿蒙的适配,我们主要关注的是与设备自身能力相关的问题,即:

(1)如何监听设备的 VSync 信号并通知 Flutter 引擎?

(2)OpenGL/Vulkan 用于上屏的窗口对象从何而来?

o4YBAGAPdXaAZIbZAADRKqjfepA812.png

VSync 信号的监听及传递

在 Flutter 引擎的 Android 实现中,设备的 VSync 信号通过 Choreographer 触发,其产生及消费流程如下图所示:

pIYBAGAPdYmAYAPnAACVTdb3mYI602.png

Flutter VSync

Flutter 框架注册 VSync 回调之后,通过 C++ 侧的 VsyncWaiter 类等待 VSync 信号,后者通过 JNI 等一系列调用,最终 Java 侧的 VsyncWaiter 类调用 Android SDK 的 Choreographer.postFrameCallback 方法,再通过 JNI 一层层传回 Flutter 引擎消费掉此回调。Java 侧的 VsyncWaiter 核心代码如下:

@OverridepublicvoidasyncWaitForVsync(long cookie){Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {@OverridepublicvoiddoFrame(long frameTimeNanos){float fps = windowManager.getDefaultDisplay().getRefreshRate();long refreshPeriodNanos = (long) (1000000000.0 / fps);FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);}});}

在整个流程中,除了来自 Android SDK 的 Choreographer 以外,大多数逻辑几乎都由 C++ 和 Java 的基础 SDK 实现,可以直接在鸿蒙上复用,问题是鸿蒙目前的 API 文档中尚没有开放类似 Choreographer 的能力。所以现阶段我们可以借用鸿蒙提供的类似 iOS Grand Central Dispatch 的线程 API,模拟出 VSync 的信号触发与回调:

@OverridepublicvoidasyncWaitForVsync(long cookie){// 模拟每秒 60 帧的屏幕刷新间隔:向主线程发送一个异步任务, 16ms 后调用applicationContext.getUITaskDispatcher().delayDispatch(() -> {float fps = 60; // 设备刷新帧率,HarmonyOS 未暴露获取帧率 API,先写死 60 帧long refreshPeriodNanos = (long) (1000000000.0 / fps);long frameTimeNanos = System.nanoTime();FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);}, 16);};

渲染窗口的构建及传递

在这一部分,我们需要在鸿蒙系统上构建平台容器,为 Flutter 引擎的图形渲染提供用于上屏的窗口对象。同样,我们参考 Flutter for Android 的实现,看一下 Android 系统是怎么做的:

o4YBAGAPda2AL0kzAAEG3lDtiaU627.png

Flutter 在 Android 上支持 Vulkan 和 OpenGL 两种渲染引擎,篇幅原因我们只关注 OpenGL。抛开复杂的注册及调用细节,本质上整个流程主要做了三件事:

创建了一个视图对象,提供可用于直接绘制的 Surface,将它通过 JNI 传递给原生侧;在原生侧获取 Surface 关联的本地窗口对象,并交给 Flutter 的平台容器;将本地窗口对象转换为 OpenGL ES 可识别的绘图表面(EGLSurface),用于 Flutter 引擎的渲染上屏。接下来我们用鸿蒙提供的平台能力来实现这三点。

a. 可用于直接绘制的视图对象

鸿蒙系统的 UI 框架提供了很多常用视图组件(Component),比如按钮、文字、图片、列表等,但我们需要抛开这些上层组件,获得直接绘制的能力。借助官方媒体播放器开发指导文档,可以发现鸿蒙提供了 SurfaceProvider 类,它管理的 Surface 对象可以用于视频解码后的展示。而 Flutter 渲染与视频上屏从原理上是类似的,因此我们可以借用 SurfaceProvider 实现 Surface 的管理和创建:

// 创建一个用于管理 Surface 的容器组件SurfaceProvider surfaceProvider = new SurfaceProvider(context);// 注册视图创建回调surfaceProvider.getSurfaceOps().get().addCallback(surfaceCallback);// ... 在 surfaceCallback 中@OverridepublicvoidsurfaceCreated(SurfaceOps surfaceOps) {Surface surface = surfaceOps.getSurface();// ...将 surface 通过 JNI 交给 Native 侧FlutterJNI.onSurfaceCreated(surface);}

b. 与 Surface 关联的本地窗口对象

鸿蒙目前开放的 Native API 并不多,在官方文档中,我们可以比较容易地找到 Native_layer API(https://developer.harmonyos.com/cn/docs/documentation/doc-references/native__layer-0000001060033509)。根据文档的说明,Native API 中的 NativeLayer(https://developer.harmonyos.com/cn/docs/documentation/doc-references/native__layer-0000001060033509#EN-US_TOPIC_0000001060033509__ga10f0496160a17e00453c6744fb98a3f6)对象刚好对应了 Java 侧的 Surface 类,借助 GetNativeLayer (https://developer.harmonyos.com/cn/docs/documentation/doc-references/native__layer-0000001060033509#EN-US_TOPIC_0000001060033509__ga10f0496160a17e00453c6744fb98a3f6)方法,我们实现了两者之间的转化:

// platform_view_android_jni_impl.ccstaticvoidSurfaceCreated(JNIEnv* env, jobject jcaller, jlong shell_holder, jobject jsurface){fml::jni::ScopedJavaLocalFrame scoped_local_reference_frame(env);// 通过鸿蒙 Native API 获取本地窗口对象 NativeLayerauto window = fml::MakeRefCounted(GetNativeLayer(env, jsurface));ANDROID_SHELL_HOLDER->GetPlatformView()->NotifyCreated(std::move(window));}

c. 与本地窗口对象关联的 EGLSurface

在 Android 的 AOSP 实现(https://source.android.google.cn/devices/graphics/arch-egl-opengl?hl=zh-cn)中,EGLSurface 可通过 EGL 库的 eglCreateWindowSurface(https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreateWindowSurface.xhtml)方法从本地窗口对象 ANativeWindow 创建而来。对于鸿蒙而言,虽然我们没有从公开文档找到类似的说明,但是鸿蒙标准库(https://developer.harmonyos.com/cn/docs/documentation/doc-references/library-0000001060513586)默认支持了 OpenGL ES,而且鸿蒙 SDK 中也附带了 EGL 相关的库及头文件,我们有理由相信在鸿蒙系统上,EGLSurface 也可以通过此方法从前一步生成的 NativeLayer 转化而来,在之后的验证中我们也确认了这一点:

// window->handle() 即为之前得到的 NativeLayerEGLSurface surface = eglCreateWindowSurface(display, config_, reinterpret_cast(window->handle()),attribs);//...交给 Flutter 渲染管线

2. 交互能力实现

交互能力是支撑 Flutter 应用能够正常运行的另一个基本要求。在 Flutter 中,交互包含了各种触摸事件、鼠标事件、键盘录入事件的传递及消费。以触摸事件为例,Flutter 事件传递的整个流程如下图所示:

pIYBAGAPdb6AOVQRAAEDCKyB7eo240.png

Flutter 事件分发

iOS/Android 的原生容器通过触摸事件的回调 API 接收到事件之后,会将其打包传递至引擎层,后者将事件传发给 Flutter 框架层,并完成事件的消费、分发和逻辑处理。同样,整个流程的大部分工作已经由 Flutter 统一,我们要做的仅仅是在原生容器上监听用户的输入,并封装成指定格式交给引擎层而已。

在鸿蒙系统上,我们可以借助平台提供的多模输入 API(https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-multimodal-overview-0000000000031876),实现多种类型事件的监听:

flutterComponent.setTouchEventListener(touchEventListener); // 触摸及鼠标事件flutterComponent.setKeyEventListener(keyEventListener); // 键盘录入事件flutterComponent.setSpeechEventListener(speechEventListener); // 语音录入事件

对于事件的封装处理,可以复用 Android 已有的逻辑,只需要关注鸿蒙与 Android 在事件处理上的对应关系即可,比如触摸事件的部分对应关系:

o4YBAGAPdcuAYU-7AAE16XvBuJY104.png

3. 其他必要的平台能力

为了保证 Flutter 应用能够正常运行,除了最基本的渲染和交互外,我们的嵌入层还要提供资源管理、事件循环、生命周期同步等平台能力。对于这些能力 Flutter 大多都在嵌入层的公共部分有抽象类声明,只需要使用鸿蒙 API 重新实现一遍即可。

比如资源管理,引擎提供了 AssetResolver(https://github.com/flutter/engine/blob/master/assets/asset_resolver.h)声明,我们可以使用鸿蒙 Rawfile(https://developer.harmonyos.com/cn/docs/documentation/doc-references/rawfile-0000001061151248)API 来实现:

classHAPAssetMapping :public fml::Mapping {public:HAPAssetMapping(RawFile* asset) : asset_(asset) {}~HAPAssetMapping() override { CloseRawFile(asset_); }size_t GetSize() const override { return GetRawFileSize(asset_); }const uint8_t* GetMapping()const override {returnreinterpret_cast(GetRawFileBuffer(asset_));}private:RawFile* const asset_;FML_DISALLOW_COPY_AND_ASSIGN(HAPAssetMapping);};

对于事件循环,引擎提供了 MessageLoopImpl(https://github.com/flutter/engine/blob/master/fml/message_loop_impl.h)抽象类,我们可以使用鸿蒙 Native_EventHandler(https://developer.harmonyos.com/cn/docs/documentation/doc-references/native__eventhandler-0000001054795159)API 实现:

// runner_ 为鸿蒙 EventRunnerNativeImplement 的实例void MessageLoopHarmony::Run() {FML_DCHECK(runner_ == GetEventRunnerNativeObjForThread());int result = ::EventRunnerRun(runner_);FML_DCHECK(result == 0);}void MessageLoopHarmony::Terminate() {int result = ::EventRunnerStop(runner_);FML_DCHECK(result == 0);}

对于生命周期的同步,鸿蒙的 Page Ability(https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-page-concept-0000000000033573)提供了完整的生命周期回调(如下图所示),我们只需要在对应的时机将状态上报给引擎即可。

o4YBAGAPdduASg9aAAEc8wYic24986.png

Page Ability Lifecycle

当以上这些能力都准备好之后,我们就可以成功把 Flutter 应用跑起来了。以下是通过 DevEco Studio(https://developer.harmonyos.com/cn/develop/deveco-studio)运行官方 Flutter Gallery(https://github.com/flutter/gallery)应用的截图,截图中 Flutter 引擎已经使用鸿蒙系统的平台能力进行了重写:

DevEco Running Flutter

借由鸿蒙的多设备支持能力,此应用甚至可在 TV、车机、手表、平板等设备上运行:

Flutter Multiple Devices

总结和展望

通过上述的构建和适配工作,我们以极小的开发成本实现了 Flutter 在鸿蒙系统上的移植,基于 Flutter 开发的上层业务几乎不做任何修改就可以在鸿蒙系统上原生运行,为迎接鸿蒙系统后续的大规模推广也提前做好了技术储备。

当然,故事到这里并没有结束。在最基本的运行和交互能力之上,我们更需要关注 Flutter 与鸿蒙自身生态的结合:如何优雅地适配鸿蒙的分布式技术?如何用 Flutter 实现设备之间的快速连接、资源共享?现有的众多 Flutter 插件如何应用到鸿蒙系统上?未来 MTFlutter 团队将在这些方面做更深入的探索,因为解决好这些问题,才是真正能让应用覆盖用户生活的全场景的关键。

参考文献

https://developer.huawei.com/consumer/cn/events/hdc2020/https://developer.harmonyos.com/cn/documentationhttps://flutter.dev/docs/resources/architectural-overviewhttps://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders作者简介:

杨超,2016 年加入美团外卖技术团队,目前主要负责 MTFlutter 相关的基础建设工作。
编辑:hfy

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 分布式技术
    +关注

    关注

    0

    文章

    23

    浏览量

    7700
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2624

    浏览量

    65380
收藏 人收藏

    评论

    相关推荐

    Flutter首次亮相Google Cloud Next大会

    Flutter 团队在近期首次参加了 Google Cloud Next 大会,这意味着 Flutter 在开发社区中的影响力正在日益增长。
    的头像 发表于 05-09 10:15 182次阅读

    最新开源代码证实!“鸿蒙原生版”微信正在积极开发中

    迁移到另一个操作系统平台的时间和成本会非常高。 目前看来,微信的鸿蒙原生版确实正在开发中,并且已经取得了一定的进展。这对于鸿蒙操作系统的推广
    发表于 05-08 17:08

    鸿蒙系统三防平板怎么样

    系统作为华为自主研发的操作系统,具有高度的兼容性和稳定性。用户可以设备安装各种应用程序,满足工作、娱乐、学习等多种需求。同时,鸿蒙
    发表于 04-12 14:26

    鸿蒙】标准系统移植指南

    标准系统移植指南 本文描述了移植一块开发板的通用步骤,和具体芯片相关的详细移植过程无法在此一一列举。后续社区还会陆续发布开发板移植的实例供开
    的头像 发表于 02-27 14:36 345次阅读
    【<b class='flag-5'>鸿蒙</b>】标准<b class='flag-5'>系统</b><b class='flag-5'>移植</b>指南

    鸿蒙开发者预览版如何?

    Linux内核以及AOSP代码,采用的鸿蒙内核以及代码,HarmonyOS NEXT系统仅支持鸿蒙内核和鸿蒙系统的应用,不再兼容安卓应用AP
    发表于 02-17 21:54

    浅谈兼容 OpenHarmony 的 Flutter

    OpenHarmony SIG 组织在 Gitee 开源了兼容 OpenHarmony 的 Flutter。该组织主要用于孵化 OpenHarmony 相关的开源生态项目。     ▲ 仓库地址
    的头像 发表于 02-02 15:22 277次阅读
    浅谈兼容 OpenHarmony 的 <b class='flag-5'>Flutter</b>

    华为鸿蒙系统怎么样 华为鸿蒙系统和安卓系统的区别

    华为鸿蒙系统是华为公司自主研发的全场景分布式操作系统,于2019年8月首次发布。鸿蒙系统不同于传统的操作
    的头像 发表于 02-02 14:54 736次阅读

    鸿蒙系统和安卓的区别 鸿蒙系统有什么特别之处

    了分布式架构,可以在不同设备上实现无缝连接和协同工作。而安卓系统采用的是集中式架构,设备之间的连接和协同工作相对较为困难。 鸿蒙系统具备高度灵活性和可扩展性,支持设备与设备之间的直接通
    的头像 发表于 01-18 11:45 4398次阅读

    了解 Flutter 3.16 功能更新

    作者 / Kevin Chisholm 我们在季度 Flutter 稳定版发布会上带来了 Flutter 3.16,此版本包含诸多更新: Material 3 成为新的默认主题、为 Android
    的头像 发表于 12-16 15:55 439次阅读
    了解 <b class='flag-5'>Flutter</b> 3.16 功能更新

    如何在Torizon平台使用Flutter来开发用户界面

    Google 面向 Android, iOS 推出的跨平台移动应⽤开发框架 Flutter 可以构建高质量的原⽣⽤户界⾯,并可以扩展支持 Web 和桌面应用。Flutter 尚未官方支持嵌入式系统,但目前 Sony 和 Ubun
    发表于 12-07 10:39 210次阅读
    如何在Torizon平台使用<b class='flag-5'>Flutter</b>来开发用户界面

    华为鸿蒙系统

    华为鸿蒙系统(HUAWEI Harmony OS),是华为公司2019年8月9日于东莞举行的华为开发者大会(HDC.2019)正式发布的操作系统
    发表于 11-02 19:39

    鸿蒙 OS 应用开发初体验

    Package 的缩写)。是鸿蒙操作系统设计的应用程序包格式。 .hap 文件包含了应用程序的代码、资源和元数据等信息,用于 HarmonyOS 设备安装和运行应用程序。 整
    发表于 11-02 19:38

    鸿蒙操作系统的前世今生

    研发鸿蒙操作系统的号角。 2019年8月9号,华为正式发布了HarmonyOS 1.0,该系统率先部署智慧屏。2019年8月10日,(原
    发表于 10-08 19:55

    【今晚开播】社区说 | 精益求精: Flutter 技巧专题篇

    分享包括: Flutter 定位分析与未来展望、Flutter Lint 的代码优化最佳实践、可滚动控件 Lazy Loading 源码解析等。既深入细节,又展望未来,走在精通 Flutter
    的头像 发表于 07-27 17:40 300次阅读
    【今晚开播】社区说 | 精益求精: <b class='flag-5'>Flutter</b> 技巧专题篇

    社区说 | 精益求精: Flutter 技巧专题篇

    社分享包括: Flutter 定位分析与未来展望、Flutter Lint 的代码优化最佳实践、可滚动控件 Lazy Loading 源码解析等。既深入细节,又展望未来,走在精通 Flutte
    的头像 发表于 07-25 17:45 307次阅读
    社区说 | 精益求精: <b class='flag-5'>Flutter</b> 技巧专题篇