前言HarmonyOS是华为推出的面向未来、面向全场景的分布式操作系统。鸿蒙在传统单设备系统能力的基础上,提出基于同一套系统能力,适配多种终端形态的分布式概念。自2020年9月发布HarmonyOS2.0以来,华为加快了HarmonyOS系统规模化落地的步伐。预计到2021年底,HarmonyOS系统将覆盖手机、平板、智能穿戴、智慧屏、汽车等数亿终端设备。对于移动应用来说,新的系统概念和新的交互形式也意味着新的机遇。如果能够利用好鸿蒙的开发生态和特性能力,应用可以覆盖更多的交互场景和设备类型,从而带来新的增长点。与面临的机遇相比,适配鸿蒙系统带来的挑战同样巨大。目前手机端,虽然鸿蒙系统仍然支持AndroidAPK的安装运行,但从长远来看,华为势必会放弃AOSP,逐步发展自己的生态,这意味着鸿蒙设备上现有的Android应用将逐渐沦为“二等公民”。但是,如果在iOS、Android之外再重新开发和维护一个鸿蒙应用,在当今业界越来越注重开发迭代效率的环境下,其开发成本将是无法估量的。因此,搭建一个合适的跨终端框架,以较低的成本将应用移植到鸿蒙平台,并利用好系统的特性能力,就成为了一个非常重要的选择。在现有的众多跨终端框架中,Flutter凭借其自渲染能力和高多端一致性,在适配新系统方面具有突出优势。虽然Flutter官方暂无适配鸿蒙的计划,但经过一段时间的探索和实践,美团外卖MTFlutter团队已经成功实现了Flutter对鸿蒙系统的原生支持。这里我也想提前说明一下,因为鸿蒙系统还处于Beta版本,所以这个适配方案还没有在实际业务上线,属于技术层面的早期探索。接下来,本文将通过原理介绍和一些实现细节来分享我们在移植开发过程中的一些经验。希望对您有所启发或帮助。背景知识和基本概念介绍在适配开始之前,我们需要先弄清楚要做什么。首先回顾一下Flutter的三层结构:在Flutter的架构设计中,最上层是框架层,使用Dart语言开发,面向Flutter业务开发者;中间层为引擎层,使用C/C++开发,实现渲染管线、Dart运行时等Flutter基础能力;最底层是embedding层,负责平台相关能力的实现。显然我们要做的就是将embedding层移植到鸿蒙。准确的说,我们需要通过鸿蒙提供的平台能力,重新实现Flutter的embedding层。对于Flutterembedding层的适配,Flutter官方有一份不太详细的指南,操作起来成本很高。由于鸿蒙的业务开发语言仍然可以使用Java,并且在很多基本概念上与Android有相似之处(如下表所示),所以我们可以从Android的实现入手,完成鸿蒙的移植。Flutter在鸿蒙上的适配如前所述,为了完成Flutter在新系统上的移植,我们需要完整实现Flutter嵌入层所需的所有子模块。从能力支撑来看,渲染、交互等原生平台必备的能力是保证Flutter应用能够运行的最基本要素,需要优先支持。下面依次介绍。1.开启渲染流程我们来回顾一下Flutter的图像渲染流程。如图所示,设备发起垂直同步(VSync)信号后,首先经过UI线程的渲染管线(Animate/Build/Layout/Paint),然后经过Raster的组合和栅格化线程,最后将图像转换为屏幕。这个过程中的大部分工作都是由框架层和引擎层完成的。对于鸿蒙的适配,我们主要关注与设备自身能力相关的问题,即:(1)如何监听设备的VSync信号,并通知Flutter引擎?(2)OpenGL/Vulkan用于on-screen的window对象从何而来?VSync信号的监听和传输在Flutter引擎的Android实现中,设备的VSync信号由Choreographer触发。它的生成和消费过程如下图所示:Flutter框架注册VSync回调后,通过C++端的VsyncWaiter类等待VSync信号,然后或者通过JNI等一系列调用,最后Java端的VsyncWaiter类调用AndroidSDK的Choreographer.postFrameCallback方法,然后通过JNI层层回传给Flutter引擎来消费这个回调。Java侧的VsyncWaiter内核代码如下:@OverridepublicvoidasyncWaitForVsync(longcookie){Choreographer.getInstance().postFrameCallback(newChoreographer.FrameCallback(){@OverridepublicvoiddoFrame(longframeTimeNanos){floatfps=windowManager.getDefaultDisplay().getRefreshRate();longrefresh=(lodNanos))(1000000000.0/fps);FlutterJNI.nativeOnVsync(frameTimeNanos,frameTimeNanos+refreshPeriodNanos,cookie);}});}整个过程中,除了AndroidSDK中的Choreographer外,大部分逻辑几乎都由基础SDK完成C++和Java的可以直接在鸿蒙上复用。问题是鸿蒙目前的API文档还没有开放类似Choreographer的能力。所以在这个阶段,我们可以借用鸿蒙提供的类似于iOSGrandCentralDispatch的线程API来模拟VSync的信号触发和回调:@OverridepublicvoidasyncWaitForVsync(longcookie){//模拟每秒60帧的屏幕刷新间隔:发送一个向主线程Task异步消息,调用applicationContext.getUITaskDispatcher().delayDispatch(()->{floatfps=60;//16ms后设备刷新帧率,HarmonyOS不暴露帧率获取API,先写死60帧longrefreshPeriodNanos=(long)(1000000000.0/fps);longframeTimeNanos=System.nanoTime();FlutterJNI.nativeOnVsync(frameTimeNanos,frameTimeNanos+refreshPeriodNanos,cookie);},16);};渲染窗口的构建与调用这部分我们需要在鸿蒙系统上构建一个平台容器,为Flutter引擎的图形渲染提供上屏的窗口对象。同样,我们参考FlutterforAndroid的实现,看看Android系统是如何实现的:Flutter在Android上支持Vulkan和OpenGL两种渲染引擎。由于篇幅原因,我们只关注OpenGL。抛开复杂的注册和调用细节不谈,整个过程本质上做了三件事:创建视图对象,提供一个可以直接绘制的Surface,通过JNI传递给native端;获取native端关联的Surface本地window对象,交给Flutter的平台容器;本地窗口对象被转换为OpenGLES可识别的绘图表面(EGLSurface),用于Flutter引擎在屏幕上的渲染。接下来我们利用鸿蒙提供的平台能力来实现这三点。A。可用于直接绘制的视图对象HarmonyOS的UI框架提供了很多常见的视图组件(Components),比如按钮、文本、图片、列表等,但是我们需要去掉这些上层组件才能获得直接绘画的能力。借助官方媒体播放器开发指导文档,可以发现鸿蒙提供了SurfaceProvider类,其管理的Surface对象可以用于视频解码后的显示。Flutter渲染在原理上类似于视频屏幕,所以我们可以使用SurfaceProvider来实现Surface的管理和创建://创建管理Surface的容器组件SurfaceProvidersurfaceProvider=newSurfaceProvider(context);//注册视图创建回调surfaceProvider.getSurfaceOps()。get().addCallback(surfaceCallback);//...在surfaceCallback@OverridepublicvoidsurfaceCreated(SurfaceOpssurfaceOps){Surfacesurface=surfaceOps.getSurface();//...通过JNI把surface给Native端FlutterJNI.onSurfaceCreated(surface);}b。与Surface关联的本地窗口对象。鸿蒙目前开放的NativeAPI并不多。我们很容易在官方文档中找到Native_layerAPI。根据文档,NativeAPI中的NativeLayer对象正好对应Java端的Surface类。借助GetNativeLayer方法,我们实现了两者之间的转换://platform_view_android_jni_impl.ccstaticvoidSurfaceCreated(JNIEnv*env,jobjectjcaller,jlong??shell_holder,jobjectjsurface){fml::jni::ScopedJavaLocalFramescoped_local_reference_frame(env);//获取本地通过鸿蒙NativeAPI获取window对象NativeLayerautowindow=fml::MakeRefCounted
