讓 Flutter 在鴻蒙系統上跑起來

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"鴻蒙系統"},{"type":"text","text":" (HarmonyOS)是華爲推出的一款面向未來、面向全場景的分佈式操作系統。在傳統單設備系統能力的基礎上,鴻蒙提出了基於同一套系統能力、適配多種終端形態的分佈式理念。自 2020 年 9 月 HarmonyOS 2.0 發佈以來,華爲加快了鴻蒙系統大規模落地的步伐,預計 2021 年底,鴻蒙系統會覆蓋包括手機、平板、智能穿戴、智慧屏、車機在內的數億臺終端設備。對移動應用而言,"},{"type":"text","marks":[{"type":"strong"}],"text":"新的系統理念、新的交互形式"},{"type":"text","text":"**,也意味着新的機遇**。如果能夠利用好鴻蒙的開發生態及其特性能力,可以讓應用覆蓋更多的交互場景和設備類型,從而帶來新的增長點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與面臨的機遇相比,適配鴻蒙系統帶來的挑戰同樣巨大。當前手機端,儘管鴻蒙系統仍然支持安卓 APK 安裝及運行,但長期來看,華爲勢必會拋棄 AOSP,逐步發展出自己的生態,這意味着現有安卓應用在鴻蒙設備上將會逐漸變成“二等公民”。然而,如果在 iOS 及 Android 之外再重新開發和維護一套鴻蒙應用,在如今業界越來越注重開發迭代效率的環境下,所帶來的開發成本也是難以估量的。因此,通過打造一套合適的跨端框架,以相對低的成本移植應用到鴻蒙平臺,並利用好該系統的特性能力,就成爲了一個非常重要的選項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在現有的衆多跨端框架當中,Flutter 以其自渲染能力帶來的多端高度一致性,在新系統的適配上有着突出的優勢。雖然Flutter 官方"},{"type":"link","attrs":{"href":"https:\/\/github.com\/flutter\/flutter\/issues\/38437","title":"","type":null},"content":[{"type":"text","text":"並沒有適配鴻蒙的計劃"}]},{"type":"text","text":",但經過一段時間的探索和實踐,美團外賣 MTFlutter 團隊成功實現了 Flutter 對於鴻蒙系統的原生支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏也要提前說明一下,**因爲鴻蒙系統目前還處於Beta版本,所以這套適配方案還沒有在實際業務中上線,屬於技術層面比較前期的探索。**接下來本文會通過原理和部分實現細節的介紹,分享我們在移植和開發過程中的一些經驗。希望能對大家有所啓發或者幫助。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景知識和基礎概念介紹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在適配開始之前,我們要明確好先做哪些事情。先來回顧一下 Flutter 的三層結構:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e2\/e217c323f67e954512f725533d992d21.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Flutter 的架構設計中,最上層爲"},{"type":"text","marks":[{"type":"strong"}],"text":"框架層"},{"type":"text","text":",使用 Dart 語言開發,面向 Flutter 業務的開發者;中間層爲"},{"type":"text","marks":[{"type":"strong"}],"text":"引擎層"},{"type":"text","text":",使用 C\/C++ 開發,實現了 Flutter 的渲染管線和 Dart 運行時等基礎能力;最下層爲"},{"type":"text","marks":[{"type":"strong"}],"text":"嵌入層"},{"type":"text","text":",負責與平臺相關的能力實現。顯然我們要做的是將嵌入層移植到鴻蒙上,確切地說,我們要"},{"type":"text","marks":[{"type":"strong"}],"text":"通過鴻蒙原生提供的平臺能力,重新實現一遍 Flutter 嵌入層"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於 Flutter 嵌入層的適配,Flutter 官方有一份不算詳細的"},{"type":"link","attrs":{"href":"https:\/\/github.com\/flutter\/flutter\/wiki\/Custom-Flutter-Engine-Embedders","title":"","type":null},"content":[{"type":"text","text":"指南"}]},{"type":"text","text":",實際操作起來成本很高。由於鴻蒙的業務開發語言仍然可用 Java,在很多基礎概念上與 Android 也有相似之處(如下表所示),我們可以從 Android 的實現入手,完成對鴻蒙的移植。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/32\/322ce1d7893b65c23b177e13923ac44d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Flutter 在鴻蒙上的適配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如前文所述,要完成 Flutter 在新系統上的移植,我們需要完整實現 Flutter 嵌入層要求的所有子模塊,而從能力支持角度,"},{"type":"text","marks":[{"type":"strong"}],"text":"渲染"},{"type":"text","text":"、"},{"type":"text","marks":[{"type":"strong"}],"text":"交互"},{"type":"text","text":"以及"},{"type":"text","marks":[{"type":"strong"}],"text":"其他必要的原生平臺能力"},{"type":"text","text":"是保證 Flutter 應用能夠運行起來的最基本的要素,需要優先支持。接下來會依次進行介紹。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 渲染流程打通"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再來回顧一下 Flutter 的圖像渲染流程。如圖所示,設備發起"},{"type":"text","marks":[{"type":"strong"}],"text":"垂直同步"},{"type":"text","text":"(VSync)信號之後,先經過 UI 線程的渲染管線(Animate\/Build\/Layout\/Paint),再經過 Raster 線程的組合和柵格化,最終通過 OpenGL 或 Vulkan 將圖像"},{"type":"text","marks":[{"type":"strong"}],"text":"上屏"},{"type":"text","text":"。這個流程的大部分工作都由框架層和引擎層完成,對於鴻蒙的適配,我們主要關注的是與設備自身能力相關的問題,即:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)如何監聽設備的 VSync 信號並通知 Flutter 引擎?(2)OpenGL\/Vulkan 用於上屏的窗口對象從何而來?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/86\/861d445908d4b853e84aa917d9c09067.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"VSync 信號的監聽及傳遞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Flutter 引擎的 Android 實現中,設備的 VSync 信號通過 "},{"type":"link","attrs":{"href":"https:\/\/developer.android.com\/reference\/android\/view\/Choreographer","title":"","type":null},"content":[{"type":"text","text":"Choreographer"}]},{"type":"text","text":" 觸發,其產生及消費流程如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e3\/e31b78794fb4dd851ad6a59d3ddff40a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Flutter VSync"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 框架註冊 VSync 回調之後,通過 C++ 側的 VsyncWaiter 類等待 VSync 信號,後者通過 JNI 等一系列調用,最終 Java 側的 VsyncWaiter 類調用 Android SDK 的 "},{"type":"link","attrs":{"href":"https:\/\/developer.android.com\/reference\/android\/view\/Choreographer#postFrameCallback(android.view.Choreographer.FrameCallback)","title":"","type":null},"content":[{"type":"text","text":"Choreographer.postFrameCallback"}]},{"type":"text","text":" 方法,再通過 JNI 一層層傳回 Flutter 引擎消費掉此回調。Java 側的 VsyncWaiter 核心代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Override\npublic void asyncWaitForVsync(long cookie) {\n Choreographer.getInstance()\n .postFrameCallback(\n new Choreographer.FrameCallback() {\n @Override\n public void doFrame(long frameTimeNanos) {\n float fps = windowManager.getDefaultDisplay().getRefreshRate();\n long refreshPeriodNanos = (long) (1000000000.0 \/ fps);\n FlutterJNI.nativeOnVsync(\n frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);\n }\n });\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在整個流程中,除了來自 Android SDK 的 Choreographer 以外,大多數邏輯幾乎都由 C++ 和 Java 的基礎 SDK 實現,可以直接在鴻蒙上覆用,問題是鴻蒙目前的 API 文檔中尚沒有開放類似 Choreographer 的能力。所以現階段我們可以借用鴻蒙提供的類似 iOS "},{"type":"link","attrs":{"href":"https:\/\/developer.apple.com\/documentation\/DISPATCH","title":"","type":null},"content":[{"type":"text","text":"Grand Central Dispatch"}]},{"type":"text","text":" 的線程 API,模擬出 VSync 的信號觸發與回調:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Override\npublic void asyncWaitForVsync(long cookie) {\n \/\/ 模擬每秒 60 幀的屏幕刷新間隔:向主線程發送一個異步任務, 16ms 後調用\n applicationContext.getUITaskDispatcher().delayDispatch(() -> {\n float fps = 60; \/\/ 設備刷新幀率,HarmonyOS 未暴露獲取幀率 API,先寫死 60 幀\n long refreshPeriodNanos = (long) (1000000000.0 \/ fps);\n long frameTimeNanos = System.nanoTime();\n FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);\n }, 16);\n};\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"渲染窗口的構建及傳遞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這一部分,我們需要在鴻蒙系統上構建平臺容器,爲 Flutter 引擎的圖形渲染提供用於上屏的窗口對象。同樣,我們參考 Flutter for Android 的實現,看一下 Android 系統是怎麼做的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/5e\/5edb864bff15f30cbbd89c24a56bb8d5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 在 Android 上支持 Vulkan 和 OpenGL 兩種渲染引擎,篇幅原因我們只關注 OpenGL。拋開復雜的註冊及調用細節,本質上整個流程主要做了三件事:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"創建了一個"},{"type":"text","marks":[{"type":"strong"}],"text":"視圖對象"},{"type":"text","text":",提供可用於直接繪製的 Surface,將它通過 JNI 傳遞給原生側;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在原生側獲取 Surface 關聯的"},{"type":"text","marks":[{"type":"strong"}],"text":"本地窗口對象"},{"type":"text","text":",並交給 Flutter 的平臺容器;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"將本地窗口對象轉換爲 OpenGL ES 可識別的"},{"type":"text","marks":[{"type":"strong"}],"text":"繪圖表面("},{"type":"text","marks":[{"type":"strong"},{"type":"strong"},{"type":"strong"}],"text":"EGLSurface"},{"type":"text","marks":[{"type":"strong"}],"text":")"},{"type":"text","text":",用於 Flutter 引擎的渲染上屏。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們用鴻蒙提供的平臺能力來實現這三點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"a. 可用於直接繪製的視圖對象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鴻蒙系統的 UI 框架"},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-guides\/ui-java-overview-0000000000500404","title":"","type":null},"content":[{"type":"text","text":"提供了很多常用視圖組件(Component)"}]},{"type":"text","text":",比如按鈕、文字、圖片、列表等,但我們需要拋開這些上層組件,獲得直接繪製的能力。藉助官方"},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-guides\/tv-media-playback-0000001050714866","title":"","type":null},"content":[{"type":"text","text":"媒體播放器開發指導"}]},{"type":"text","text":"文檔,可以發現鴻蒙提供了 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/surfaceprovider-0000001054358716","title":"","type":null},"content":[{"type":"text","text":"SurfaceProvider"}]},{"type":"text","text":" 類,它管理的 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/surface-0000001054120059","title":"","type":null},"content":[{"type":"text","text":"Surface"}]},{"type":"text","text":" 對象可以用於視頻解碼後的展示。而 Flutter 渲染與視頻上屏從原理上是類似的,因此我們可以借用 SurfaceProvider 實現 Surface 的管理和創建:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ 創建一個用於管理 Surface 的容器組件\nSurfaceProvider surfaceProvider = new SurfaceProvider(context);\n\/\/ 註冊視圖創建回調\nsurfaceProvider.getSurfaceOps().get().addCallback(surfaceCallback);\n\n\/\/ ... 在 surfaceCallback 中\n@Override\npublic void surfaceCreated(SurfaceOps surfaceOps) {\n Surface surface = surfaceOps.getSurface();\n \/\/ ...將 surface 通過 JNI 交給 Native 側\n FlutterJNI.onSurfaceCreated(surface);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"b. 與 Surface 關聯的本地窗口對象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鴻蒙目前開放的 Native API 並不多,在官方文檔中,我們可以比較容易地找到 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/native__layer-0000001060033509","title":"","type":null},"content":[{"type":"text","text":"Native_layer API"}]},{"type":"text","text":"。根據文檔的說明,Native API 中的 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/native__layer-0000001060033509#EN-US_TOPIC_0000001060033509__ga10f0496160a17e00453c6744fb98a3f6","title":"","type":null},"content":[{"type":"text","text":"NativeLayer"}]},{"type":"text","text":" 對象剛好對應了 Java 側的 Surface 類,藉助 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/native__layer-0000001060033509#EN-US_TOPIC_0000001060033509__ga10f0496160a17e00453c6744fb98a3f6","title":"","type":null},"content":[{"type":"text","text":"GetNativeLayer"}]},{"type":"text","text":" 方法,我們實現了兩者之間的轉化:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ platform_view_android_jni_impl.cc\nstatic void SurfaceCreated(JNIEnv* env, jobject jcaller, jlong shell_holder, jobject jsurface) {\n fml::jni::ScopedJavaLocalFrame scoped_local_reference_frame(env);\n \/\/ 通過鴻蒙 Native API 獲取本地窗口對象 NativeLayer\n auto window = fml::MakeRefCounted(\n GetNativeLayer(env, jsurface));\n ANDROID_SHELL_HOLDER->GetPlatformView()->NotifyCreated(std::move(window));\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"c. 與本地窗口對象關聯的 EGLSurface"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Android 的 "},{"type":"link","attrs":{"href":"https:\/\/source.android.google.cn\/devices\/graphics\/arch-egl-opengl?hl=zh-cn","title":"","type":null},"content":[{"type":"text","text":"AOSP 實現"}]},{"type":"text","text":"中,EGLSurface 可通過 EGL 庫的 "},{"type":"link","attrs":{"href":"https:\/\/www.khronos.org\/registry\/EGL\/sdk\/docs\/man\/html\/eglCreateWindowSurface.xhtml","title":"","type":null},"content":[{"type":"text","text":"eglCreateWindowSurface"}]},{"type":"text","text":" 方法從本地窗口對象 "},{"type":"text","marks":[{"type":"strong"}],"text":"ANativeWindow"},{"type":"text","text":" 創建而來。對於鴻蒙而言,雖然我們沒有從公開文檔找到類似的說明,但是"},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/library-0000001060513586","title":"","type":null},"content":[{"type":"text","text":"鴻蒙標準庫"}]},{"type":"text","text":"默認支持了 OpenGL ES,而且鴻蒙 SDK 中也附帶了 EGL 相關的庫及頭文件,我們有理由相信在鴻蒙系統上,EGLSurface 也可以通過此方法從前一步生成的 "},{"type":"text","marks":[{"type":"strong"}],"text":"NativeLayer"},{"type":"text","text":" 轉化而來,在之後的驗證中我們也確認了這一點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ window->handle() 即爲之前得到的 NativeLayer\nEGLSurface surface = eglCreateWindowSurface(\n display, config_, reinterpret_cast(window->handle()),\n attribs);\n\/\/...交給 Flutter 渲染管線\n"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 交互能力實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"交互能力"},{"type":"text","text":"是支撐 Flutter 應用能夠正常運行的另一個基本要求。在 Flutter 中,交互包含了各種觸摸事件、鼠標事件、鍵盤錄入事件的傳遞及消費。以觸摸事件爲例,Flutter 事件傳遞的整個流程如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/3a\/3acc61a53eaaad55aa2c4eeadc9d762b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Flutter 事件分發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"iOS\/Android 的原生容器通過觸摸事件的回調 API 接收到事件之後,會將其打包傳遞至引擎層,後者將事件傳發給 Flutter 框架層,並完成事件的消費、分發和邏輯處理。同樣,整個流程的大部分工作已經由 Flutter 統一,我們要做的僅僅是在原生容器上"},{"type":"text","marks":[{"type":"strong"}],"text":"監聽"},{"type":"text","text":"用戶的輸入,並"},{"type":"text","marks":[{"type":"strong"}],"text":"封裝"},{"type":"text","text":"成指定格式交給引擎層而已。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在鴻蒙系統上,我們可以藉助平臺提供的"},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-guides\/ui-multimodal-overview-0000000000031876","title":"","type":null},"content":[{"type":"text","text":"多模輸入 API"}]},{"type":"text","text":",實現多種類型事件的監聽:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"flutterComponent.setTouchEventListener(touchEventListener); \/\/ 觸摸及鼠標事件\nflutterComponent.setKeyEventListener(keyEventListener); \/\/ 鍵盤錄入事件\nflutterComponent.setSpeechEventListener(speechEventListener); \/\/ 語音錄入事件\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於事件的封裝處理,可以複用 Android 已有的邏輯,只需要關注鴻蒙與 Android 在事件處理上的對應關係即可,比如觸摸事件的部分對應關係:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/d4\/d4207fd8222c51fbb920d1a6239a503d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"3. 其他必要的平臺能力"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了保證 Flutter 應用能夠正常運行,除了最基本的渲染和交互外,我們的嵌入層還要提供資源管理、事件循環、生命週期同步等平臺能力。對於這些能力 Flutter 大多都在嵌入層的公共部分有抽象類聲明,只需要使用鴻蒙 API 重新實現一遍即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如資源管理,引擎提供了 "},{"type":"link","attrs":{"href":"https:\/\/github.com\/flutter\/engine\/blob\/master\/assets\/asset_resolver.h","title":"","type":null},"content":[{"type":"text","text":"AssetResolver"}]},{"type":"text","text":" 聲明,我們可以使用鴻蒙 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/rawfile-0000001061151248","title":"","type":null},"content":[{"type":"text","text":"Rawfile"}]},{"type":"text","text":" API 來實現:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"class HAPAssetMapping : public fml::Mapping {\n public:\n HAPAssetMapping(RawFile* asset) : asset_(asset) {}\n ~HAPAssetMapping() override { CloseRawFile(asset_); }\n\n size_t GetSize() const override { return GetRawFileSize(asset_); }\n\n const uint8_t* GetMapping() const override {\n return reinterpret_cast(GetRawFileBuffer(asset_));\n }\n\n private:\n RawFile* const asset_;\n\n FML_DISALLOW_COPY_AND_ASSIGN(HAPAssetMapping);\n};\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於事件循環,引擎提供了 "},{"type":"link","attrs":{"href":"https:\/\/github.com\/flutter\/engine\/blob\/master\/fml\/message_loop_impl.h","title":"","type":null},"content":[{"type":"text","text":"MessageLoopImpl"}]},{"type":"text","text":" 抽象類,我們可以使用鴻蒙 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-references\/native__eventhandler-0000001054795159","title":"","type":null},"content":[{"type":"text","text":"Native_EventHandler"}]},{"type":"text","text":" API 實現:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ runner_ 爲鴻蒙 EventRunnerNativeImplement 的實例\nvoid MessageLoopHarmony::Run() {\n FML_DCHECK(runner_ == GetEventRunnerNativeObjForThread());\n int result = ::EventRunnerRun(runner_);\n FML_DCHECK(result == 0);\n}\n\nvoid MessageLoopHarmony::Terminate() {\n int result = ::EventRunnerStop(runner_);\n FML_DCHECK(result == 0);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於生命週期的同步,鴻蒙的 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/docs\/documentation\/doc-guides\/ability-page-concept-0000000000033573","title":"","type":null},"content":[{"type":"text","text":"Page Ability"}]},{"type":"text","text":" 提供了完整的生命週期回調(如下圖所示),我們只需要在對應的時機將狀態上報給引擎即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/a9\/a9abe3615e732fae160b23626d950869.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Page Ability Lifecycle"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當以上這些能力都準備好之後,我們就可以成功把 Flutter 應用跑起來了。以下是通過 "},{"type":"link","attrs":{"href":"https:\/\/developer.harmonyos.com\/cn\/develop\/deveco-studio","title":"","type":null},"content":[{"type":"text","text":"DevEco Studio"}]},{"type":"text","text":" 運行官方 "},{"type":"link","attrs":{"href":"https:\/\/github.com\/flutter\/gallery","title":"","type":null},"content":[{"type":"text","text":"Flutter Gallery"}]},{"type":"text","text":" 應用的截圖,截圖中 Flutter 引擎已經使用鴻蒙系統的平臺能力進行了重寫:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/01\/0109159b8c61528fab99764689d77148.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"DevEco Running Flutter"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藉由鴻蒙的多設備支持能力,此應用甚至可在 TV、車機、手錶、平板等設備上運行:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ac\/acf003ba4d9c1036f5c23a5032520c83.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Flutter Multiple Devices"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"總結和展望"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上述的構建和適配工作,我們以極小的開發成本實現了 Flutter 在鴻蒙系統上的移植,基於 Flutter 開發的上層業務幾乎不做任何修改就可以在鴻蒙系統上原生運行,爲迎接鴻蒙系統後續的大規模推廣也提前做好了技術儲備。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,故事到這裏並沒有結束。在最基本的運行和交互能力之上,我們更需要關注 Flutter 與鴻蒙自身生態的結合:如何優雅地適配鴻蒙的分佈式技術?如何用 Flutter 實現設備之間的快速連接、資源共享?現有的衆多 Flutter 插件如何應用到鴻蒙系統上?未來 MTFlutter 團隊將在這些方面做更深入的探索,因爲解決好這些問題,纔是真正能讓應用覆蓋用戶生活的全場景的關鍵。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"參考文獻:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/developer.huawei.com\/consumer\/cn\/events\/hdc2020\/"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/developer.harmonyos.com\/cn\/documentation"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/flutter.dev\/docs\/resources\/architectural-overview"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/github.com\/flutter\/flutter\/wiki\/Custom-Flutter-Engine-Embedders"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"楊超,2016 年加入美團外賣技術團隊,目前主要負責 MTFlutter 相關的基礎建設工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文轉載自美團技術團隊(ID:meituantech)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/vTWZRaxvsOS_VRjfh6l4FQ","title":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/mp.weixin.qq.com\/s\/vTWZRaxvsOS_VRjfh6l4FQ"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章