一、前言
小程序自誕生以來(lái)运怖,經(jīng)過(guò)兩年多的發(fā)展拼弃,成為了微信開發(fā)者生態(tài)中最具有生命力的一環(huán),為外部開發(fā)者開辟了全新的想象空間摇展。然而吻氧,小程序帶來(lái)的改變絕不僅限于微信之外,小程序技術(shù)棧的確立,又對(duì)微信客戶端的研發(fā)產(chǎn)生了怎樣的影響盯孙?
二鲁森、微信客戶端的跨平臺(tái)實(shí)踐
微信客戶端團(tuán)隊(duì),早在 2012 年的時(shí)候就已經(jīng)開始使用跨平臺(tái)技術(shù)進(jìn)行研發(fā)振惰,從最初為了應(yīng)對(duì)多平臺(tái)客戶端代碼邏輯不統(tǒng)一的問(wèn)題歌溉,到后續(xù)面向業(yè)務(wù)和 UI 開發(fā),一直在嘗試研發(fā)跨平臺(tái)的解決方案骑晶。
最早的跨平臺(tái)組件是基于 C99 開發(fā)的 mmnet痛垛,在 2012 年 10 月份的時(shí)候?yàn)榱私鉀Q多平臺(tái)客戶端出現(xiàn)的一系列不一致問(wèn)題而打造的基礎(chǔ)網(wǎng)絡(luò)組件,后續(xù)經(jīng)過(guò)不斷的迭代優(yōu)化桶蛔,尤其是在應(yīng)對(duì)弱網(wǎng)絡(luò)做了深度的優(yōu)化榜晦,并且加入了安全、容災(zāi)等各種網(wǎng)絡(luò)策略羽圃。mmnet 的通用部分邏輯代碼于 2016 年以 mars 的名字在 github 開源乾胶,在業(yè)界獲得了廣泛的認(rèn)可,完成了一個(gè)內(nèi)部實(shí)驗(yàn)的跨平臺(tái)組件到最終升華為所有人可用的開源項(xiàng)目朽寞。同樣在 github 受到歡迎的還有相似思路完成的 wcdb识窿、mmkv 等跨平臺(tái)組件。
在完成基礎(chǔ)組件的跨平臺(tái)之后脑融,隨之而來(lái)的是面向業(yè)務(wù)和 UI 開發(fā)的跨平臺(tái)嘗試喻频。為了面對(duì)內(nèi)部快速變化的創(chuàng)新業(yè)務(wù),微信客戶端團(tuán)隊(duì)不得不去尋求在多端上快速迭代的開發(fā)模式肘迎。在業(yè)務(wù)開發(fā)的過(guò)程中甥温,能否可以做到像使用基礎(chǔ)跨平臺(tái)組件那樣,只寫一次代碼就能在多端上得到體驗(yàn)一致的 UI 功能界面呢妓布?
在嘗試了不同的方案之后姻蚓,我們將目光放到了小程序上。在微信小程序快速發(fā)展的兩年內(nèi)匣沼,各內(nèi)部業(yè)務(wù)團(tuán)隊(duì)開始基于小程序去做創(chuàng)新業(yè)務(wù)的開發(fā)狰挡。借助微信小程序框架,這些業(yè)務(wù)可以獲得相比于純?cè)蛻舳碎_發(fā)周期短释涛、上線快的優(yōu)勢(shì)加叁,同時(shí)可以滿足較強(qiáng)的運(yùn)營(yíng)需求。這種基于微信小程序的業(yè)務(wù)開發(fā)模式在內(nèi)部逐漸的受到認(rèn)可唇撬。
我們認(rèn)為好的跨平臺(tái)開發(fā)模式必須要達(dá)到以下的四個(gè)目標(biāo):
- 減少平臺(tái)差異性:應(yīng)該最大限度減少不同平臺(tái)上開發(fā)的差異性它匕,盡可能減少各平臺(tái)特有的開發(fā)負(fù)擔(dān);
- 提高研發(fā)效率:從研發(fā)效率的角度看窖认,在提高人效比的同時(shí)豫柬,應(yīng)該盡可能提升開發(fā)人員在開發(fā)過(guò)程中的效率告希,包括編碼、調(diào)試轮傍、運(yùn)行暂雹、測(cè)試等多個(gè)環(huán)節(jié);
- 原生的性能和體驗(yàn):從最終的研發(fā)產(chǎn)物看创夜,應(yīng)該有和分平臺(tái)原生技術(shù)開發(fā)一樣的性能表現(xiàn)和用戶體驗(yàn)杭跪,讓用戶無(wú)法感知出差距;
- 易學(xué)的可控技術(shù)棧:跨平臺(tái)的技術(shù)棧應(yīng)該具有較好的學(xué)習(xí)曲線驰吓,能夠讓更多原生開發(fā)的同學(xué)快速學(xué)習(xí)并掌握涧尿,且無(wú)論從技術(shù)還是商業(yè)角度看,都應(yīng)該是可控檬贰、安全的技術(shù)棧姑廉。
那么,小程序技術(shù)棧是否能夠滿足這些要求呢翁涤?
三桥言、小程序與微信客戶端
微信小程序采用了以前端技術(shù)棧為主的方案,框架上面抹平了許多平臺(tái)差異性葵礼,同時(shí)業(yè)務(wù)也可以隨時(shí)動(dòng)態(tài)部署更新号阿,而體驗(yàn)和性能也比較接近原生。隨著小程序生態(tài)的發(fā)展鸳粉,還出現(xiàn)了更多豐富的插件擴(kuò)展機(jī)制扔涧、自定義組件機(jī)制和第三方開發(fā)框架。同時(shí)届谈,小程序作為微信團(tuán)隊(duì)內(nèi)部自主研發(fā)的框架枯夜,小程序已經(jīng)是一個(gè)非常優(yōu)秀的跨平臺(tái)框架,滿足一般的業(yè)務(wù)開發(fā)是沒(méi)有問(wèn)題的艰山。
然而湖雹,當(dāng)我們以“小程序技術(shù)棧作為客戶端跨平臺(tái)開發(fā)技術(shù)”這一命題展開,關(guān)注其中的一些細(xì)節(jié)時(shí)程剥,也發(fā)現(xiàn)了問(wèn)題劝枣。
附近的餐廳就是微信團(tuán)隊(duì)內(nèi)部基于小程序開發(fā)的一個(gè)類似原生體驗(yàn)的業(yè)務(wù)。通過(guò)小程序?qū)崿F(xiàn)了一次開發(fā)運(yùn)行在 iOS织鲸、Android 兩個(gè)客戶端上的功能。整個(gè)開發(fā)的過(guò)程都主要以微信小程序的開發(fā)工具和開發(fā)標(biāo)準(zhǔn)為主溪胶,配合客戶端實(shí)現(xiàn)部分額外增補(bǔ)能力搂擦,在基本功能完成之后我們也發(fā)現(xiàn)了一些在 Android 平臺(tái)上出現(xiàn)的問(wèn)題,這里舉兩個(gè)比較典型的例子哗脖。
第一個(gè)是字體一致性體驗(yàn)問(wèn)題瀑踢。微信小程序使用 WebView 渲染扳还,與原生客戶端的是兩套不同的視圖渲染體系,在 Android 平臺(tái)上出現(xiàn)了無(wú)法跟隨系統(tǒng)字體保持一致的問(wèn)題橱夭,體驗(yàn)上會(huì)有較為明顯的割裂感氨距。
第二個(gè)在大量的圖片和視頻混排的場(chǎng)景下,會(huì)出現(xiàn)一些掉幀現(xiàn)象棘劣,在 Android 中低端機(jī)上較為明顯俏让。如下圖所示,在圖片滑動(dòng)等連續(xù)過(guò)程中茬暇,會(huì)偶爾出現(xiàn) LAG 的情況首昔。并且受目前小程序框架所限,視頻糙俗、圖片的全屏顯示效果也不夠理想勒奇。
正是因?yàn)槲⑿判〕绦蚩蚣茉诿鎸?duì)復(fù)雜業(yè)務(wù)的場(chǎng)景下還會(huì)存在一些體驗(yàn)和性能不盡人意的地方,在性能和體驗(yàn)上雖然接近原生巧骚,但仍不能達(dá)到原生體驗(yàn)效果赊颠,我們決定針對(duì)這些細(xì)節(jié)嘗試進(jìn)行一步步的優(yōu)化。
先來(lái)看看小程序目前的系統(tǒng)架構(gòu)劈彪。
四竣蹦、基于小程序技術(shù)棧的跨平臺(tái)開發(fā)
微信小程序的系統(tǒng)架構(gòu)相信今天大部分的讀者都比較熟悉了,總體來(lái)講分為兩部分:
View 視圖端通過(guò)小程序的框架將用戶采用 WXML 和 WXSS 描述的UI信息處理成 H5 元素粉臊,最終交給 WebView 去渲染草添;
-
App Service 端運(yùn)行用戶編寫的 JavaScript 邏輯,并且可以調(diào)用具有微信開放能力的 JSAPI扼仲。邏輯和視圖分離远寸,通過(guò)事件和數(shù)據(jù)彼此之間建立聯(lián)系。
回到我們上面的問(wèn)題屠凶,在中低端機(jī)和稍復(fù)雜的業(yè)務(wù)上驰后,受制于 Web 龐大而復(fù)雜的體系,要達(dá)到原生視圖體系這樣簡(jiǎn)單設(shè)計(jì)的體驗(yàn)矗愧,難度很高灶芝。那么是否能夠使用平臺(tái)原生的視圖渲染體系來(lái)解決問(wèn)題呢?
-
基于原生渲染優(yōu)化
原理上我們可以將用戶描述的 UI唉韭,轉(zhuǎn)換成系統(tǒng)原生的組件夜涕,行業(yè)里面早有實(shí)踐,受到 ReactNative 這類框架的啟發(fā)属愤,我們將小程序的視圖端進(jìn)行了一些改造女器,在 Android 平臺(tái)上我們 dump 出小程序框架中 Virtual DOM 的信息和所有的 CSS 樣式,在 Java 層逐一的去解析映射成原生的組件住诸。但原生體系并不能完全的表達(dá)過(guò)于復(fù)雜的 CSS 樣式驾胆,因此前期只支持了部分的 WXSS 特性涣澡。
LV-CPP
我們初步方案當(dāng)中有太多的實(shí)現(xiàn)一開始是用 Java 去做的,考慮平臺(tái)兼容問(wèn)題丧诺,為了方便移植到其他平臺(tái)以及可以更低成本的更換渲染模塊入桂,我們就將原來(lái)解析 DOM 和 CSS 樣式的實(shí)現(xiàn)單獨(dú)抽離了出來(lái),形成一個(gè)獨(dú)立的跨平臺(tái)模塊驳阎。最終選擇了 C++ 實(shí)現(xiàn)的 LV-CPP 模塊抗愁,由 LV-CPP 去做跨平臺(tái)的小程序 UI 體系處理器,完成 DOM 和 CSS 的解析搞隐、布局計(jì)算驹愚,同時(shí)執(zhí)行 JS 的功能由 V8 或者 JSCore 來(lái)完成。
當(dāng) WXML/WXSS 描述的 UI 發(fā)生改變時(shí)劣纲,小程序前端公共庫(kù)(WXA Framework)通過(guò)內(nèi)部計(jì)算逢捺,將 Virtual DOM 樹 Diff 的結(jié)果以操作指令的形式提交到 LV-CPP。LV-CPP 接收指令后癞季,更新相應(yīng)的節(jié)點(diǎn)劫瞳,進(jìn)行 CSS 的匹配、CSS 屬性的轉(zhuǎn)換以及布局的計(jì)算绷柒,計(jì)算好之后再調(diào)用 Native View 進(jìn)行界面的渲染志于。
CSS 匹配上,目前支持了 ID 選擇器(#id)废睦、標(biāo)簽選擇器(button)伺绽、類選擇器(.class)、組合選擇器(A,B嗜湃、A B奈应、A>B、A+B购披、A~B)杖挣。為了提高性能,其中組合選擇器的匹配使用了 WebKit 的逆序解析方案刚陡。
之所以在 LV-CPP 中進(jìn)行 CSS 屬性的轉(zhuǎn)換以及布局計(jì)算惩妇,目的是為了盡量抹平以后即使使用不同的渲染模塊所帶來(lái)的屬性和布局上的差異。最典型的是顏色的轉(zhuǎn)換筐乳。CSS 中顏色有各種表示方法歌殃,最常見的有:十六進(jìn)制顏色,如:#0000ff
RGB 顏色蝙云,如:rgb(0,0,255)
RGBA 顏色挺份,如:rgba(255,0,0,0.5)
HSL 顏色,如:hsl(120,65%,75%)
HSLA 顏色贮懈,如:hsla(120,65%,75%,0.3)
顏色名匀泊,如:black
這些不同種類的顏色表示方式,經(jīng)過(guò) LV-CPP 計(jì)算后輸出的全部是十進(jìn)制的顏色值朵你,再交由渲染模塊進(jìn)行渲染各聘。
采用原生組件的方案確實(shí)在體驗(yàn)和性能方面能夠帶來(lái)不錯(cuò)的提升唆樊,在 Pixel 2 XL 的機(jī)器上我們測(cè)出惩阶,幀率方面比 WebView 提升了 27.5%幔崖,內(nèi)存也可以下降 14%~23%掀宋。但隨著我們要將該方案推廣到各平臺(tái)的時(shí)候事镣,我們意識(shí)到需要在各個(gè)平臺(tái)去做適配是一個(gè)巨大工作量的事情驶悟,而且后續(xù)的維護(hù)成本也將無(wú)法預(yù)測(cè)焊夸。
基于 Web 的渲染滿足不了性能和體驗(yàn)的要求苛败,基于原生渲染又會(huì)帶來(lái)高維護(hù)成本問(wèn)題水孩,我們需要一個(gè)跨平臺(tái)的渲染方案來(lái)解決镰矿。在研究各種可能的方案的時(shí)候,F(xiàn)lutter 再次走進(jìn)了我們的視野俘种。-
Flutter
Flutter 是 Google 為跨平臺(tái)打造的高性能應(yīng)用框架秤标,受到了很多同行的關(guān)注,但如果按照我們?cè)O(shè)定的微信跨平臺(tái)開發(fā)的目標(biāo)來(lái)看宙刘,F(xiàn)lutter 并不完全符合苍姜,使用 Dart 開發(fā)會(huì)對(duì)現(xiàn)有開發(fā)同學(xué)造成額外的學(xué)習(xí)成本,所以一開始我們并沒(méi)有將 Flutter 作為客戶端跨平臺(tái)開發(fā)的候選悬包。
但當(dāng)我們的問(wèn)題重新設(shè)定為“尋找一個(gè)跨平臺(tái)的高性能渲染框架”時(shí)衙猪,F(xiàn)lutter 就逐漸體現(xiàn)出了各項(xiàng)優(yōu)勢(shì)。從一些經(jīng)典的 Benchmarks 案例中看到布近,F(xiàn)lutter 具有非常不錯(cuò)的性能水平垫释。
這組數(shù)據(jù)是我們?cè)?ARM 平臺(tái)測(cè)出的 Java,Dart JIT 和 Dart AOT 的對(duì)比數(shù)據(jù)吊输,數(shù)值越高表示性能越好饶号。同時(shí)另一個(gè)有意思的情況是,隨著 Flutter 版本的提升季蚂,性能表現(xiàn)會(huì)越來(lái)越好茫船,也說(shuō)明 Flutter 的開發(fā)人員在不斷地優(yōu)化性能表現(xiàn)。
而且從 Benchmarks Game 上能獲取到和 JavaScript 的一些對(duì)比數(shù)據(jù)扭屁,從中大概能得出一個(gè)結(jié)論:Dart 的語(yǔ)言性能是超過(guò) JavaScript算谈,和 Java 有得一拼的。
可以看下官方對(duì) Flutter 的介紹: 快速開發(fā):Flutter 的熱重載可以快速地進(jìn)行測(cè)試料滥、構(gòu)建UI然眼、添加功能并更快地修復(fù)錯(cuò)誤。
富有表現(xiàn)力葵腹,漂亮的用戶界面:自帶的 Material Design 和 Cupertino(iOS風(fēng)格)widget高每、豐富的 motion API屿岂、平滑而自然的滑動(dòng)效果。
響應(yīng)式框架:使用 Flutter 的現(xiàn)代鲸匿、響應(yīng)式框架爷怀,和一系列基礎(chǔ) widget,輕松構(gòu)建您的用戶界面带欢。
訪問(wèn)本地功能和 SDK:Flutter 可以復(fù)用現(xiàn)有的 Java运授、Swift 或 ObjC代碼,訪問(wèn) iOS 和 Android 上的原生系統(tǒng)功能和系統(tǒng) SDK乔煞。
統(tǒng)一的應(yīng)用開發(fā)體驗(yàn):Flutter 擁有豐富的工具和庫(kù)吁朦,可以幫助開發(fā)者輕松地同時(shí)在 iOS 和 Android 系統(tǒng)中實(shí)現(xiàn)想法和創(chuàng)意。
原生性能:Flutter 包含了許多核心的 widget渡贾,如滾動(dòng)逗宜、導(dǎo)航、圖標(biāo)和字體等剥啤,這些都可以在 iOS 和 Android 上達(dá)到原生應(yīng)用一樣的性能锦溪。
在一系列的評(píng)估基礎(chǔ)上,我們覺(jué)得可以使用 Flutter 去嘗試一下府怯。于是我們提出了基于 Flutter 的小程序框架渲染優(yōu)化方案刻诊。
4. 基于 Flutter 渲染優(yōu)化
我們把渲染部分由原來(lái)的平臺(tái)原生組件替換成了 Flutter 的 Widgets,依然只支持精簡(jiǎn)后的 WXML 和 WXSS牺丙。
在這個(gè)架構(gòu)下则涯,我們就將 Layout 層的 LV-CPP 專門的作為小程序的 UI 體系處理器,將 UI 信息布局計(jì)算好再提交給抽象的后端去渲染冲簿,LV-CPP 作為小程序的框架和渲染器的中間層粟判,集中的在 C++ 層去處理與 Web 相關(guān)的復(fù)雜特性。渲染端就可以基于特定的協(xié)議和接口專注將元素轉(zhuǎn)化為 UI 組件峦剔,最終繪制出來(lái)档礁。
通過(guò)結(jié)合 Flutter 和 LV-CPP,我們把實(shí)現(xiàn)代碼收斂在 C++ 和 Dart 上吝沫,進(jìn)一步簡(jiǎn)化了基于小程序技術(shù)棧實(shí)現(xiàn)跨平臺(tái)業(yè)務(wù)開發(fā)的框架維護(hù)成本呻澜。
然而,真正實(shí)現(xiàn)的過(guò)程中我們還得做更多的思考和優(yōu)化惨险。
5. 通信難題
小程序的框架是使用 JavaScript 再加上一些平臺(tái)注入的接口來(lái)實(shí)現(xiàn)的羹幸,它們是運(yùn)行在 JS Engine 的環(huán)境當(dāng)中。而 Layout 層是采用 C++ 來(lái)實(shí)現(xiàn)辫愉,如何去解決 JavaScript 和 C++ 的互相通信問(wèn)題呢栅受?LV-CPP 在 C++ 層計(jì)算好布局之后,又如何將這些信息傳遞給渲染后端 Flutter 的 Dart 環(huán)境中呢?要想保障框架的性能屏镊,那么我們就必須要去解決兩個(gè)問(wèn)題依疼。
a. JS 的通信
基于 Android WebView 的體系下可以在 Java 層通過(guò) WebView 提供的接口注入一個(gè) JavaScriptInterface,JS 就可以得到一個(gè)擴(kuò)展的 API闸衫,調(diào)用的時(shí)候經(jīng)過(guò) V8 最終反射到 Java 上面涛贯。
在 iOS 上面也是類似的實(shí)現(xiàn),這種方式第一是會(huì)帶來(lái)平臺(tái)相關(guān)性的實(shí)現(xiàn)蔚出;第二是調(diào)用路徑較長(zhǎng)。所以在這個(gè)問(wèn)題上虫腋,我們最終使用了 JS Binding 的方案骄酗,將原先依賴平臺(tái)的實(shí)現(xiàn)直接下沉到 C++,去實(shí)現(xiàn) JS 對(duì)象的擴(kuò)展悦冀,既可以解決跨平臺(tái)的問(wèn)題也能帶來(lái)性能的提升趋翻。
b. Flutter 的通信
Flutter 官方提供了一種 Platform Channel 的方案,用于 Dart 和平臺(tái)之間相互通信盒蟆。主要的原理就是將傳遞的數(shù)據(jù)編碼成消息的形式踏烙,跨線程發(fā)送到平臺(tái)接口層,處理之后再將返回的數(shù)據(jù)通過(guò)同樣的方式原路返回历等√殖停基于消息和跨線程的處理使得這種方式的通信效率并不高,我們?cè)隍旪?45的機(jī)器上測(cè)了一組數(shù)據(jù)寒屯,一秒內(nèi)通過(guò) Platform Channel 只能大概完成四千次左右的相互調(diào)用荐捻。
所以我們對(duì) Flutter Engine 進(jìn)行了一些改造,增加了一個(gè) dart2cpp 的模塊寡夹,暴露出部分的 C++ 接口处面,使得外部的動(dòng)態(tài)庫(kù)可以基于這些接口通過(guò) DartVM 調(diào)用到 dart 的接口。在 Dart 的運(yùn)行環(huán)境中 C++ 和 Dart 之間就可以像調(diào)用自身的接口一樣調(diào)用彼此的接口菩掏。而且在 AOT 模式下 Dart 會(huì)被編譯成機(jī)器碼魂角,所以 C++ 和 Dart 的調(diào)用會(huì)非常的高效。不需要將數(shù)據(jù)編碼成消息和跨線程一系列的復(fù)雜流程智绸,而是直接在內(nèi)存棧上操作數(shù)據(jù)野揪。
dart2cpp 相比于 Platform Channel 的方案提升多少呢,同樣的測(cè)試案例传于,一秒內(nèi)通過(guò) dart2cpp 可以完成三十多萬(wàn)次的相互調(diào)用囱挑,可以說(shuō)是極大的提升了通信效率。
c. dart2cpp 實(shí)現(xiàn)原理
DartVM 提供了一種機(jī)制沼溜,可以在 Dart 的代碼中使用 native 關(guān)鍵字來(lái)表示調(diào)用的是一個(gè) C/C++ 的接口平挑。
// Dart 示例代碼
bool systemSrand(int seed) native "SystemSrand";
但這個(gè) C/C++ 接口必須要先注冊(cè)到 DartVM 當(dāng)中,不然就無(wú)法查找到符號(hào)。
DART_EXPORT Dart_Handle
Dart_SetNativeResolver(Dart_Handle library,
Dart_NativeEntryResolver resolver,
Dart_NativeEntrySymbol symbol);
注冊(cè)可以通過(guò) Dart_SetNativeResolver 來(lái)完成通熄,在 Dart 的運(yùn)行過(guò)程中會(huì)通過(guò)注冊(cè)的 Dart_NativeEntryResolver 根據(jù)函數(shù)信息來(lái)查找到 C/C++ 的函數(shù)地址唆涝。
通過(guò)以上的兩步就可以在 Dart 直接調(diào)用一個(gè)擴(kuò)展的 C/C++ 函數(shù),但是還沒(méi)完唇辨,Dart 的內(nèi)存模型和 C/C++ 的是有區(qū)別的廊酣,Dart 調(diào)到 C/C++ 的過(guò)程中傳遞的參數(shù)和函數(shù)返回值都使用了一個(gè) Dart_NativeArguments 來(lái)描述,可以通過(guò) Dart_GetNativeArgument/Dart_SetReturnValue 這兩個(gè)接口來(lái)從 Dart_NativeArguments 上獲取參數(shù)和設(shè)置返回值赏枚。
// C++ 示例代碼
void SystemSrand(Dart_NativeArguments arguments) {
Dart_EnterScope();
bool success = false;
Dart_Handle seed_object = HandleError(Dart_GetNativeArgument(arguments, 0));
if (Dart_IsInteger(seed_object)) {
bool fits;
HandleError(Dart_IntegerFitsIntoInt64(seed_object, &fits));
if (fits) {
int64_t seed;
HandleError(Dart_IntegerToInt64(seed_object, &seed));
srand(static_cast<unsigned>(seed));
success = true;
}
}
Dart_SetReturnValue(arguments, HandleError(Dart_NewBoolean(success)));
Dart_ExitScope();
}
d. cpp2dart 實(shí)現(xiàn)原理
以上介紹了 Dart 調(diào)用 C/C++ 接口的實(shí)現(xiàn)原理亡驰,那么在 C/C++ 如何的調(diào)用 Dart 的接口呢,別急饿幅,在 DartVM 中依然可以找到解決辦法凡辱。
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
Dart_Invoke(Dart_Handle target,
Dart_Handle name,
int number_of_arguments,
Dart_Handle* arguments);
可以在 dart_api.h 中找到一系列 API ,這些 API 就可以在 C/C++ 層操作到 Dart 的接口甚至是變量栗恩。
有了這些基礎(chǔ)的 API 就基本上可以做到 Dart 和 C/C++ 之間相互調(diào)用透乾,但你可能還需要知道一些 DartVM 的執(zhí)行機(jī)制,才能讓你的代碼正常的 work磕秤。
上面的 C/C++ 的示例代碼中乳乌,使用了 Dart_EnterScope/Dart_ExitScope這么兩個(gè) API,事實(shí)上在 C/C++ 持有的 Dart 對(duì)象都是用 Dart_Handle 句柄來(lái)描述的市咆,我們?cè)诤瘮?shù)內(nèi)創(chuàng)建的很多變量都是局部變量汉操,在離開作用域之后應(yīng)該釋放內(nèi)存,那么 Scope 的概念就相當(dāng)告訴 DartVM 當(dāng)前創(chuàng)建的都是局部變量床绪,在 ExitScope 之后應(yīng)該回收這里用到的內(nèi)存客情。
當(dāng)然還有一個(gè)重要的概念是 Isolate,Dart 的代碼是運(yùn)行在一個(gè)獨(dú)立的 Isolate 當(dāng)中的癞己,在 Flutter 的體系當(dāng)中膀斋,這個(gè)主 Isolate 一般是寄生在 UI Runner 的線程中,在 C/C++ 去調(diào)用 Dart 的接口必須要在 Isolate 的環(huán)境當(dāng)中痹雅,不然就會(huì)出現(xiàn)各種異常仰担。
這里就涉及到非常多細(xì)節(jié)以及繁瑣的 API 調(diào)用的問(wèn)題,對(duì)一般的開發(fā)者開講他只是要去調(diào)用一個(gè)外部的接口而已绩社,可能不了解這些具體的技術(shù)細(xì)節(jié)摔蓝,因此我們才開發(fā)了 dart2cpp 這么一套東西,使得開發(fā)者能夠正常的寫 Dart 和 C/C++ 的代碼愉耙,不需要去關(guān)注數(shù)據(jù)如何的傳遞贮尉、Scope 以及 Isolate 這些細(xì)節(jié)。
而且我們也不希望最終業(yè)務(wù)的動(dòng)態(tài)庫(kù)和 Flutter Engine 的動(dòng)態(tài)庫(kù)是綁定在一起的朴沿,它們可以是相互獨(dú)立的動(dòng)態(tài)庫(kù)猜谚,在需要用到的時(shí)候败砂,只需要通過(guò) Dart 的接口去加載這個(gè)動(dòng)態(tài)庫(kù),然后動(dòng)態(tài)庫(kù)將自己的信息注冊(cè)到 Flutter Engine 當(dāng)中魏铅,就可以做到 Dart 和外部動(dòng)態(tài)庫(kù)之間的 C/C++ 相互調(diào)用昌犹。
e. js2dart
這兩套解決方案呢,其實(shí)它的想象空間絕非僅此览芳,既然 JS 可以和 C++ 相互調(diào)用斜姥,C++ 又可以和 Dart 相互調(diào)用,他們結(jié)合在一起其實(shí)就可以間接的打通 JavaScript 和 Dart沧竟。雖然 JavaScript 和 Dart 有各自的執(zhí)行環(huán)境和機(jī)制铸敏,但通過(guò) C++ 的橋梁,依然可以構(gòu)建一個(gè)高效的通道屯仗,中間可以通過(guò)引用和一些轉(zhuǎn)換(類似 JNI)來(lái)完成大多數(shù)的調(diào)用操作和數(shù)據(jù)傳遞搞坝。
另外,F(xiàn)lutter 在動(dòng)態(tài)部署(Hot Patch)方面雖然沒(méi)有提供官方的支持魁袜,但是在借助于 js2dart 下能夠做的事情就很多了,但這并不在本文的討論范圍敦第。
至此不同語(yǔ)言環(huán)境中的調(diào)用通信問(wèn)題有了比較高效的解決方案峰弹。
6. Flutter 渲染優(yōu)化后的小程序整體架構(gòu)
來(lái)看一下到目前為止小程序的整體架構(gòu)調(diào)整。App Service 端依然保持原有的結(jié)構(gòu)芜果,處理用戶編寫的 JavaScript 邏輯鞠呈;而視圖端(PageView)則重新劃分為四個(gè)層級(jí),除了原有的 UI DSL 描述(WXML/WXSS)右钾、小程序前端公共庫(kù)(WXA Framework)外蚁吝,還有由 LV-CPP 為主的 UI 布局處理層(Layout+)和 Flutter 實(shí)現(xiàn)的渲染層(Renderer)。
使用簡(jiǎn)化的 WXML/WXSS 描述的 UI 信息舀射,經(jīng)過(guò)小程序前端公共庫(kù)處理成 DOM 描述窘茁,通過(guò) JS Binding 接口傳遞給 LV-CPP去解析 CSS 和 DOM 節(jié)點(diǎn)(Layout+)。LV-CPP 在完成布局計(jì)算之后將元素信息通過(guò) dart2cpp 的接口發(fā)送到 Flutter 端脆烟,F(xiàn)lutter Framework 層直接將布局計(jì)算好的元素描述成渲染節(jié)點(diǎn)山林,交給 Flutter Engine 去繪制。
整體上來(lái)講我們把代碼收斂在 JavaScript邢羔,C++ 和 Dart 上驼抹,所以在跨平臺(tái)方面會(huì)極大減少額外的負(fù)擔(dān)。對(duì)小程序的開發(fā)者也不會(huì)帶來(lái)任何的改變拜鹤,面向開發(fā)者的依然是原有的小程序技術(shù)體系框冀。
7. 從 RN-like 到 Flutter 渲染
從最初的 RN-like 方案再到基于 Flutter 方案的研究,本質(zhì)上都只是在不斷的解決我們遇到的問(wèn)題敏簿,對(duì)比 Web 的方案體驗(yàn)和性能也都有提升明也,而且在平臺(tái)維護(hù)方面也得到解決。
匯總 Flutter 渲染解決的問(wèn)題,基本上看是能夠滿足我們?cè)谛阅芎腕w驗(yàn)上的訴求的:
- 字體不一致問(wèn)題:通過(guò)自定義 Flutter Engine 實(shí)現(xiàn)跟隨系統(tǒng)原生視圖字體诡右;
- 視頻安岂、地圖等同層渲染:Flutter 官方提供了一種機(jī)制,通過(guò) Texture Widgets 的方式將 Native 平臺(tái)渲染的 Texture 同步到 Flutter 的渲染體系中來(lái)帆吻,保證同一時(shí)刻界面上僅存在一種視圖體系域那;
- 文本輸入框:Flutter 官方提供了較為完整的輸入框控件;
- 性能提升:相比 WebView 在低端機(jī)上有可見的性能指標(biāo)提升猜煮;
-
減少重復(fù)資源投入次员,多平臺(tái)維護(hù):基本上只需要維護(hù) Dart 和 C++ 代碼,平臺(tái)相關(guān)代碼可以最小化王带。
當(dāng)然淑蔚,目前階段在性能上還存在很大的進(jìn)步空間,相比 RN-like 方案的各項(xiàng)性能指標(biāo)并未達(dá)到最佳愕撰,仍需要充分的發(fā)揮 Flutter 的特性刹衫,提高這套框架整體的可用性。
注:由于開發(fā)階段方案變化較快搞挣,此處對(duì)比數(shù)據(jù)并未在同樣的設(shè)備下測(cè)定带迟,僅以相對(duì) WebView 渲染提升為例做為說(shuō)明。
五囱桨、總結(jié)與展望
回顧一下上下文仓犬,微信在客戶端跨平臺(tái)開發(fā)方案的探索從最早期的打造高質(zhì)量、開源化的基礎(chǔ)組件舍肠,到現(xiàn)在嘗試探索大前端技術(shù)棧的業(yè)務(wù)跨平臺(tái)開發(fā)方案搀继,始終是從提升研發(fā)團(tuán)隊(duì)效能和最終產(chǎn)品用戶體驗(yàn)兩個(gè)角度出發(fā),去思考如何能夠不斷地提高移動(dòng)研發(fā)技術(shù)水平翠语。如果把我們的視線重新拉回來(lái)這一根本出發(fā)點(diǎn)叽躯,今天我們所分享的渲染方案也并不一定是小程序技術(shù)棧作為跨平臺(tái)開發(fā)的唯一優(yōu)化方案選擇。WebView 渲染真的無(wú)法有突破性提升啡专?跨平臺(tái)開發(fā)只有大前端技術(shù)選擇险毁?隨著大前端技術(shù)不斷的發(fā)展和深入,相信未來(lái)一定會(huì)繼續(xù)出現(xiàn)新的技術(shù)方案去解決現(xiàn)有研發(fā)流程中的問(wèn)題们童,也歡迎大家繼續(xù)關(guān)注我們的最新進(jìn)展畔况。
Q & A
在 GMTC 2019 大會(huì)分享結(jié)束后,我們陸續(xù)收到了很多同學(xué)提出的疑問(wèn)慧库,這里也統(tǒng)一整理了一些具有代表性的問(wèn)題統(tǒng)一回答跷跪。
Q1. 小程序在產(chǎn)品上是否會(huì)有什么改變?是否會(huì)放棄 WebView 渲染轉(zhuǎn)向 Flutter 渲染齐板?
A1. 微信小程序是一個(gè)獨(dú)立的生態(tài)和產(chǎn)品吵瞻,使用 WebView 渲染具有極大的靈活性和前端兼容性葛菇,不會(huì)放棄 WebView 渲染。目前我們的嘗試僅限于微信客戶端內(nèi)部部分場(chǎng)景使用橡羞,對(duì)微信小程序的外部開發(fā)者不會(huì)有任何影響眯停。
Q2. 使用 Flutter 渲染的這套方案在遇到復(fù)雜 CSS 屬性的時(shí)候表現(xiàn)如何?
A2. 過(guò)于復(fù)雜的 CSS 屬性卿泽,我們不會(huì)支持莺债。當(dāng)前在 LV-CPP 上支持的 CSS 是一個(gè)比較小的子集(我們內(nèi)部稱之為 “WXSS-LITE”),從性能和復(fù)雜度角度去看签夭,也不會(huì)支持完整 CSS 屬性齐邦。針對(duì)內(nèi)部開發(fā)的業(yè)務(wù)來(lái)說(shuō),會(huì)根據(jù)性能第租、支持的復(fù)雜度和必要性等方面綜合決定是否納入 WXSS-LITE 支持范圍措拇,即也有可能大幅限制內(nèi)部開發(fā)同學(xué)能夠使用的 CSS 屬性。
Q3. js2dart 模塊是否支持傳遞對(duì)象和自定義數(shù)據(jù)慎宾,是否考慮開源或者開放出來(lái)供大家使用丐吓?
A3. JS 和 Dart 都有各自的執(zhí)行機(jī)制和對(duì)象模型,所以是無(wú)法直接的傳遞對(duì)象的趟据,事實(shí)上也不需要汰蜘,但是可以借助于引用或者其他的數(shù)據(jù)結(jié)構(gòu)來(lái)解決對(duì)象映射的問(wèn)題,以及自定義的數(shù)據(jù)結(jié)構(gòu)也可以在一定的協(xié)議之上來(lái)完成之宿,甚至可以基于共享內(nèi)存的方案來(lái)傳遞大塊的數(shù)據(jù)都沒(méi)有問(wèn)題。開放使用方面苛坚,我們也在考慮比被,但具體的方式還在討論中,希望我們的解決方案能夠?yàn)閺V大的開發(fā)者帶來(lái)更廣闊的想象空間冒掌。
Q4. iOS 端接入會(huì)帶來(lái)包大小的變化,以及無(wú)法 Hot Patch苍凛,你們對(duì) iOS 的接入是怎么做的想罕?
A4. 我們最初是從 Android 平臺(tái)去切入的,iOS 的接入會(huì)晚一點(diǎn)鸠蚪,根據(jù)我們實(shí)際的調(diào)研情況來(lái)看呢,iOS 的同學(xué)對(duì)開發(fā)工具酌摇、包大小埂息、動(dòng)態(tài)性等都比較關(guān)注,后續(xù)我們也準(zhǔn)備在這些方面去做一些研究砸彬,和 iOS 的同學(xué)一起來(lái)探討出一些解決方案,也希望大家積極的擁抱新技術(shù)磅摹,在社區(qū)當(dāng)中分享自己的解決方案。但無(wú)論怎么講霎奢,我們使用新技術(shù)的目的是為了解決我們遇到的問(wèn)題户誓,只要是對(duì)我們有益的技術(shù),我們一定會(huì)持續(xù)的跟進(jìn)幕侠。