摘要: WEEX依舊採起傳統的web開發技術棧進行開發,同時app在終端的運行體驗不輸native app。其同時解決了開發效率、發版速度以及用戶體驗三個核心問題。那麼WEEX是如何實現的?目前WEEX已經徹底開源,並捐給Apache基金會,咱們能夠經過分析其源碼來一探究竟。html
點此查看原文:https://yq.aliyun.com/article...前端
做者:阿里-移動雲-大前端團隊vue
傳統的移動端開發,一個完整的業務須要維護三份終端代碼:Android、iOS、H5,這帶來了極大的開發成本以及維護成本。尤爲是對處於業務初創期須要快速試錯的業務以及須要支持按期運營活動的業務。因此業界也一直在探索跨平臺方案,旨在經過一套代碼完成各個終端的業務邏輯。相關方案通過不斷演化,從早期的H五、Hybrid到現在的Cloud Native(雲原生),在開發效率和用戶體驗上都在一點點逼近最初的設想。web
早期H5和Hybrid方案的核心是利用終端的內置瀏覽器(webview)功能,經過開發web應用知足跨平臺需求。該方案能夠解決跨平臺問題,同時能夠提高發版效率。但其最大的弊端在於用戶體驗相較於native開發的app存在較大差距,常常出現頁面卡頓,加載慢等問題。api
因而後來業界開始探索依舊利用web技術棧開發出媲美原生體驗app的方案,因而以WEEX爲表明雲原生開發框架開始出現。所謂雲原生(Cloud Native)指能夠經過雲端快速發佈(與遠程web應用發佈流程相似),同時還能夠達到媲美原生App體驗的方案。WEEX依舊採起傳統的web開發技術棧進行開發,同時app在終端的運行體驗不輸native app。其同時解決了開發效率、發版速度以及用戶體驗三個核心問題。那麼WEEX是如何實現的?目前WEEX已經徹底開源,並捐給Apache基金會,咱們能夠經過分析其源碼來一探究竟。瀏覽器
WEEX框架主要分爲兩部分:前端框架
前端JavaScript框架
Native SDK服務器
本文主要探討Native SDK的核心原理,其前端JavaScript框架會在後續的文章中進行介紹。weex
1 總體架構網絡
首先來看下WEEX開發的總體架構:
從上圖中能夠看到weex的大體工做流程:
研發人員利用web技術棧開發weex file,打包成JS Bundle,而後部署到服務器上
終端經過網絡獲取JS Bundle,而後在本地執行該JS Bundle
終端上提供了JS的執行引擎(JSCore)用於執行遠程加載到JS Bundle
JS執行引擎執行JS Bundle,並將相關渲染指令以及其餘須要利用native能力的指令經過JS-Native Bridge透出
JS-Native Bridge將渲染指令分發到native(Andorid、iOS)渲染引擎,由native渲染引擎完成最終的頁面渲染
看完上述總體架構後,能夠大體理解爲什麼WEEX能夠達到媲美原生的體驗,由於其頁面渲染並非像H5方案同樣接入瀏覽器的渲染能力,而是原生渲染,因此本質上渲染出來的頁面就是一個native頁面。
接下來咱們再來將端上的模塊進行詳細的拆分:
如上圖所示,WEEX NATIVE SDK大體能夠分爲以下幾個層級:
JS執行層:
JS執行引擎:JSCore,解釋並執行JS Bundle
main.js:提供WEEX runtime,SDK初始化,JS Core會首先加載main.js,爲js bundle提供weex runtime
Bridge層:提供JS和Native的雙向通訊能力
Dom層:維護頁面Dom結構
Render層:完成頁面渲染
native組件庫:本地UI組件庫,每個組件對應一個html標籤,因此當咱們在weex開發過程當中使用到的各類標籤:div、text、image等等,最終都被轉化成爲了一個native的控件
module manager、module庫:功能模塊管理層
WXSDKManger、WXSDKEngine:SDK全局環境維護
WXSDKInstance:weex 實例,一個js bundle對應一個weex實例
2 WEEX SDK初始化
有了上述大體架構和功能劃分後,咱們以一個實際的例子來分析WEEX NATIVE SDK的運行邏輯。首先來看下WEEX SDK在初始化階段都作了哪些準備工做。
這裏以Andorid代碼爲例進行分析:WEEX的初始化一般放在Application中,其初簡化的初始化邏輯入以下:
public class WXApplication extends Application { @Override public void onCreate() { super.onCreate(); initWeex(); ...... } private void initWeex() { // 自定義相關配置 InitConfig config=new InitConfig.Builder() .setImgAdapter(new ImageAdapter()) // 自定義圖片適配器 .build(); WXSDKEngine.initialize(this,config); // register module try { WXSDKEngine.registerModule("testmodule", TestModule.class); // 註冊自定義模塊 WXSDKEngine.registerModule("event", WXEventModule.class); WXSDKEngine.registerComponent("richtext", RichText.class); // 註冊自定義UI組件 ...... } catch (WXException e) { e.printStackTrace(); } } }
從代碼中能夠看到,weex的初始化比較簡單,主要完成兩件事:
完成初始化配置:好比指定相關適配器,好比圖片請求適配器
註冊自定義的UI組件和功能模塊
剩下的事情都交給WEEX SDK來完成了,那麼接下來就來看下WEEX SDK都作了些什麼?
具體代碼在WXSDKEngine.doInitInternal:
private static void doInitInternal(final Application application,final InitConfig config){ WXEnvironment.sApplication = application; WXEnvironment.JsFrameworkInit = false; WXBridgeManager.getInstance().post(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); WXSDKManager sm = WXSDKManager.getInstance(); sm.onSDKEngineInitialize(); if(config != null ) { sm.setInitConfig(config); if(config.getDebugAdapter()!=null){ config.getDebugAdapter().initDebug(application); } } WXSoInstallMgrSdk.init(application, sm.getIWXSoLoaderAdapter(), sm.getWXStatisticsListener()); boolean isSoInitSuccess = WXSoInstallMgrSdk.initSo(V8_SO_NAME, 1, config!=null?config.getUtAdapter():null); if (!isSoInitSuccess) { return; } sm.initScriptsFramework(config!=null?config.getFramework():null); WXEnvironment.sSDKInitExecuteTime = System.currentTimeMillis() - start; WXLogUtils.renderPerformanceLog("SDKInitExecuteTime", WXEnvironment.sSDKInitExecuteTime); } }); register(); }
這是WEEX SDK的初始化邏輯,其主要作了如下幾件事:
初始化WXBridge,同時啓動WXBridge線程,待接收指令。WXBridge在Android的實現本質上是一個基於HandlerThread的異步任務處理線程
initSo:加載so文件,即JS執行引擎
initScriptsFramework:加載SDK中的main.js,完成weex runtime的初始化
register:註冊SDK自帶的UI組件和功能模塊
3 頁面渲染
WEEX SDK在完成了初始化以後,便可開始渲染頁面了。接下來咱們以以下這JS代碼爲例,來介紹頁面的渲染邏輯:
JS代碼比較簡單,邏輯就不介紹了。接下來重點介紹,當終端獲取到如上圖右側的js bundle後,如何進行加載、渲染以及後續的相關邏輯執行。
3.1 weex實例建立
實際上當WEEX SDK獲取到JS Bundle後,第一時間並非立馬渲染頁面,而是先建立WEEX的實例:
這幅時序圖中有兩個主要邏輯:
建立createInstance:建立一個weex實例,每個JS bundle對應一個實例,同時每個實例都有一個instance id。因爲全部的js bundle都是放入到同一個JS執行引擎中執行,那麼當js執行引擎經過WXBridge將相關渲染指令傳出的時候,須要經過instance id才能知道該指定要傳遞給哪一個weex實例
execJs:在建立實例完成後,接下來纔是真正將js bundle交給js執行引擎執行
3.2 頁面渲染
在實例建立完成後,接下來就是頁面渲染了。首先來看下頁面渲染的總體流程:
js bundle涉及dom操做的執行都會被weex-vue-framework轉化成native dom api, 前端框架vue是基於virtual dom api,而weex的前端框架:weex-vue-framework的核心邏輯就是將vue的virtual-dom轉換成Native DOM API
weex終端的執行引擎在執行到Native DOM API後,則會將其轉化爲Platform API,說白了就是經過WXBridge將Native DOM API以約定的方式轉發給native渲染引擎,完成頁面渲染
能夠看到,在js執行引擎建立好weex實例後,會執行對應的JS Bundle,並在執行到platform api的時候將其經過wxbridge,發送給DomManager。相關代碼可參考:com.taobao.weex.bridge.WXBridge
3.2.1 createbody
一個頁面的DOM結構最外層是body,因此建立頁面一開始就是createbody,整個create body的過程大體能夠分爲如下幾個步驟:
WXBridge將create body指令發送給WXDom模塊。WXDom是另外一個異步線程,負責維護頁面的Dom樹
WXDom建立一個新的dom樹,同時建立body節點
WXDom將create body指令傳遞給WXRenderManager渲染引擎,渲染引擎主要完成以下幾件事:
初始化一個組件實例,稱爲mGodComponent
generateComponentTree:因爲一個WEEX頁面就是由多個UI組件(Component)構成的一棵樹,因此渲染引擎會初始化組件樹
建立view
3.3.2 addElement
建立完body後,須要在body中添加一個text組件,指向該操做的Native DOM API爲addElement,其具體操做爲:
WXDomManager:更新本地dom樹,添加text節點
WXRenderManager:本地渲染引擎添加相關組件:
從已註冊的組件中找到text對應的組件,並實例化
將初始化完成的text組件添加到body所對應的view之上
給text組件設定佈局、添加監聽事件
加入數據綁定
在此一個帶有一個text標籤的簡單頁面纔算是渲染完成。值得一提的是,在WXRenderManager建立組件時,須要在本地已註冊的組件中須要標籤對應的組件,此處<text>標籤對應的組件爲com.taobao.weex.ui.component.WXText,其本質上是一個TextView。從這裏能夠發現,其實咱們在JS Bundle中指定的各類標籤,其實都最終被轉化爲了一個native的控件。這也就是爲何用WEEX開發出來的app,本質上仍是一個Native App。
其餘的對應關係還有:
div 對應WXDiv
image 對應WXImage
list對應WXListComponent
a對應WXA
…...
4 總結
經過前文的介紹,相信你們對WEEX有了一個初步的系統認識。簡單來講,WEEX放棄了傳統的Webview,而是搭建了一個native化的瀏覽器,由於用native的方式實現了一個瀏覽器的大部分核心組成成分:
JS 執行引擎
渲染引擎
DOM樹管理
網絡請求,持久層存儲等等能力
...
另外爲了保證整個SDK的運行效率,SDK維護了三個線程:
bridge線程:完成js到native之間的通訊
dom線程:完成dom結構的構建
渲染線程:完成UI渲染,也就是UI線程
以上就是WEEX SDK的大體框架和核心邏輯,篇幅有限,沒法面面俱到,只是但願經過該文想你們展現WEEX基於WEB技術棧開發native app的原理。文章內容若有偏頗,歡迎你們指正。