上圖爲微信小程序的項目結構,pages下面包含了小程序中的每個頁面,每個頁面由頁面結構,頁面樣式,頁面配置和邏輯代碼四部分組成。web
頁面結構文件爲index.wxml,經過微信自定義的標籤來寫。編程
頁面邏輯經過JavaScript來書寫。小程序
相似CSS文件,來定義頁面內元素的樣式。微信小程序
頁面內的權限等配置信息。瀏覽器
小程序的定位特色是輕,快,針對這兩個特色,在技術選型上,微信進行了一些考量。緩存
缺點:沒法動態打包,動態下發。安全
缺點:若是咱們用純 Web 技術來渲染小程序,在一些有複雜交互的頁面上可能會面臨一些性能問題,這是由於在 Web 技術中,UI渲染跟 JavaScript 的腳本執行都在一個單線程中執行,這就容易致使一些邏輯任務搶佔UI渲染的資源。服務器
從渲染底層來看,PhoneGap與微信 JS-SDK 是相似的,它們最終都仍是使用瀏覽器內核來渲染界面。而 RN 則不一樣,雖然是用 Web 相關技術來編寫,一樣是利用了 JavaScript 解釋執行的特性,但 RN 在渲染底層是用客戶端原生渲染的。咱們選擇相似於微信 JSSDK 這樣的 Hybrid 技術,即界面主要由成熟的 Web 技術渲染,輔之以大量的接口提供豐富的客戶端原生能力。同時,每一個小程序頁面都是用不一樣的WebView去渲染,這樣能夠提供更好的交互體驗,更貼近原生體驗,也避免了單個WebView的任務過於繁重。微信
在安卓則是往 WebView 的 window 對象注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個兼容層,主要提供了調用(invoke)和監聽(on)這兩種方法。開發者插入一個原生組件,通常而言,組件運行的時候被插入到 DOM 樹中,會調用客戶端接口,通知客戶端在哪一個位置渲染一塊原生界面。在後續開發者更新組件屬性時,一樣地,也會調用客戶端提供的更新接口來更新原生界面的某些部分。網絡
因爲JavaScript的靈活性和瀏覽器的功能豐富,會致使不少不可控的隱私,所以,微信提供了一個單純的JS執行環境,經過對於其中的控件也進行了自定義。所以徹底採用這個沙箱環境不能有任何瀏覽器相關接口,只提供純JavaScript 的解釋執行環境,那麼像HTML5中的ServiceWorker、WebWorker特性就符合這樣的條件,這二者都是啓用另外一線程來執行 JavaScript。可是考慮到小程序是一個多 WebView 的架構,每個小程序頁面都是不一樣的WebView 渲染後顯示的,在這個架構下咱們很差去用某個WebView中的ServiceWorker去管理全部的小程序頁面。得益於客戶端系統有JavaScript 的解釋引擎(在iOS下是用內置的 JavaScriptCore框架,在安卓則是用騰訊x5內核提供的JsCore環境),咱們能夠建立一個單獨的線程去執行 JavaScript,在這個環境下執行的都是有關小程序業務邏輯的代碼,也就是咱們前面一直提到的邏輯層。而界面渲染相關的任務全都在WebView線程裏執行,經過邏輯層代碼去控制渲染哪些界面,那麼這一層固然就是所謂的渲染層。這就是小程序雙線程模型的由來。
爲了防止標籤訂義帶來的一些問題,微信自定義了一套標籤語言,WXML,這套標籤語言通過編譯以後,最終會生成Html。
上面是小程序的渲染技術的選型,在選型以後,因爲渲染和邏輯再也不同一個瀏覽器執行,一個在純JS環境中,一個經過WebView渲染,所以小程序的運行環境分紅渲染層和邏輯層,WXML 模板和 WXSS 樣式工做在渲染層,JS 腳本工做在邏輯層。
小程序的渲染層和邏輯層分別由2個線程管理:渲染層的界面使用了WebView 進行渲染;邏輯層採用JsCore線程運行JS腳本。一個小程序存在多個界面,因此渲染層存在多個WebView線程,這兩個線程的通訊會經由微信客戶端作中轉,邏輯層發送網絡請求也經由Native轉發,小程序的通訊模型如圖所示。
在開發UI界面過程當中,程序須要維護不少變量狀態,同時要操做對應的UI元素。隨着界面愈來愈複雜,咱們須要維護不少變量狀態,同時要處理不少界面上的交互事件,整個程序變得愈來愈複雜。一般界面視圖和變量狀態是相關聯的,若是有某種「方法」可讓狀態和視圖綁定在一塊兒(狀態變動時,視圖也能自動變動),那咱們就能夠省去手動修改視圖的工做。
小程序的邏輯層和渲染層是分開的兩個線程。在渲染層,宿主環境會把WXML轉化成對應的JS對象,在邏輯層發生數據變動的時候,咱們須要經過宿主環境提供的setData方法把數據從邏輯層傳遞到渲染層,再通過對比先後差別,把差別應用在原來的Dom樹上,渲染出正確的UI界面。
經過setData把msg數據從「Hello World」變成「Goodbye」,產生的JS對象對應的節點就會發生變化,此時能夠對比先後兩個JS對象獲得變化的部分,而後把這個差別應用到原來的Dom樹上,從而達到更新UI的目的,這就是「數據驅動」的原理。
UI界面的程序須要和用戶互動,例如用戶可能會點擊你界面上某個按鈕,又或者長按某個區域,這類反饋應該通知給開發者的邏輯層,須要將對應的處理狀態呈現給用戶。因爲WebView如今具有的功能只是進行渲染,所以對於事件的分發處理,微信進行了特殊的處理,將全部的事件攔截後,丟到邏輯層交給JavaScript進行處理。
事件的派發處理,具有事件捕獲和冒泡兩種機制。經過native傳遞給JSCore,經過JS來響應響應的事件以後,對Dom進行修改,改動會體如今虛擬Dom上,而後再進行真實的渲染。
小程序是基於雙線程模型,那就意味着任何數據傳遞都是線程間的通訊,也就是都會有必定的延時。這不像傳統Web那樣,當界面須要更新時,經過調用更新接口UI就會同步地渲染出來。在小程序架構裏,這一切都會變成異步。
異步會使得各部分的運行時序變得複雜一些。好比在渲染首屏的時候,邏輯層與渲染層會同時開始初始化工做,可是渲染層須要有邏輯層的數據才能把界面渲染出來,若是渲染層初始化工做較快完成,就要等邏輯層的指令才能進行下一步工做。所以邏輯層與渲染層須要有必定的機制保證時序正確,
在每一個小程序頁面的生命週期中,存在着若干次頁面數據通訊。邏輯層向視圖層發送頁面數據(data和setData的內容),視圖層向邏輯層反饋用戶事件。
經過Json的方式進行數據的傳遞,提升性能的方式就是減小交互的數據量。
小程序宿主環境會管理不一樣小程序的數據緩存,不一樣小程序的本地緩存空間是分開的,每一個小程序的緩存空間上限爲10MB,若是當前緩存已經達到10MB,再經過wx.setStorage寫入緩存會觸發fail回調。
小程序的本地緩存不只僅經過小程序這個維度來隔離空間,考慮到同一個設備能夠登陸不一樣微信用戶,宿主環境還對不一樣用戶的緩存進行了隔離,避免用戶間的數據隱私泄露。
因爲本地緩存是存放在當前設備,用戶換設備以後沒法從另外一個設備讀取到當前設備數據,所以用戶的關鍵信息不建議只存在本地緩存,應該把數據放到服務器端進行持久化存儲。
支付寶小程序的實現和微信小程序的實現方式大體是相同的,所以這裏主要針對二者的差別性的地方。
支付寶小程序目錄結構
支付寶小程序業務架構圖
在渲染引擎上面,支付寶小程序不只提供 JavaScript+Webview 的方式,還提供 JavaScript+Native 的方式,在對性能要求較高的場景,能夠選擇 Native 的渲染模式,給用戶更好的體驗。
小程序編程模型是分爲多個頁面,每一個頁面有本身的 template、CSS 和 JS,實際在運行的時候,業務邏輯的 JS 代碼是運行在獨立的 JavaScript 引擎中,每一個頁面的 template 和 CSS 是運行在各自獨立的 webview 裏面,頁面之間是經過函數 navigateTo 進行頁面的切換。
每一個 webview 裏面的頁面和公共的 JavaScript 引擎裏面的邏輯的交互方式是經過消息服務,頁面的一些事件都會經過這個消息通道傳給 JavaScript 引擎運行環境,這個運行環境會響應這個事件,作一些 API 調用,可調到客戶端支付寶小程序提供的一些能力,處理以後會把這個數據再從新發送給對應的頁面渲染容器來處理,把數據和模板結合在一塊兒來,在產生最終的用戶界面。
一般的作法是在 WebView 裏面運行 render 的代碼,而後另起一個線程運行 serviceworker,當 serviceworker 須要更新 dom 的時候把事件和數據經過 messagechannel 發送給 render 線程來執行,當業務須要傳遞到 render 層數據量較大,對象較複雜時,交互的性能就會比較差,所以針對這種狀況咱們提出一個優化的解決方案。
該方案將原始的 JS 虛擬機實例 (即 Isolate) 從新設計成了兩個部分:Global Runtime 和 Local Runtime。
在新的隔離模型下,webview 裏面的 v8 實例就是一個 Local Runtime,worker 線程裏面的 v8 實例也是一個 Local Runtime,在 worker 層和 render 層交互時,setData 對象的會直接建立在 Shared Heap 裏面,所以 render 層的 Local Runtime 能夠直接讀到該對象,而且用於 render 層的渲染,減小了對象的序列化和網絡傳輸,極大的提高了啓動性能和渲染性能。
因爲小程序啓動是受到生命週期的控制,從 onLaunch -> onLoad -> onShow -> onReady -> 用戶操做 -> 離開首頁這個流程,在這個過程當中的任意一個環節都有可能被客觀或者主觀的緣由打斷,也就有可能致使保存的離線頁面不許確,在啓動的時候給用戶呈現錯誤的頁面。
因此對於首頁離線緩存渲染的效果,保存頁面的時機很重要,咱們提供讓開發者能夠配置的時機,配置的時機有兩個:渲染完成和離開首頁前。對於渲染完成就是首頁渲染完成,用戶還未執行任何的操做前把頁面保存下來做爲離線緩存的頁面。離開首頁前就是指用戶在首頁執行了一系列的操做後,跳轉到其餘頁面前用戶看到的頁面保存下來做爲離線緩存的頁面。
對於閃屏問題發生的場景是由於緩存頁面和真實渲染的頁面是分離的,是兩個獨立的頁面,緩存頁面是靜態的頁面,真實的頁面是經過 js 動態建立的頁面,因此常規的作法就是當真實頁面建立完成後替換緩存的頁面,這樣的狀況下就會發生閃屏。
針對這個問題,咱們是採用虛擬 dom 來解決,在加載緩存頁面的時候把緩存頁面放入初始的虛擬 dom 裏面,真實頁面建立後產生的虛擬 dom 跟緩存頁面的虛擬 dom 進行 dom diff,把變化的內容經過 patch 傳給瀏覽器內核,渲染對應的頁面,這樣就能夠只更新局部有變化的頁面內容,避免了整個頁面的更新,也保證內容的準確性和實時性。
1.圖片內存:針對低端機,作了更嚴格的圖片緩存限制,在保持性能體驗的狀況下,進一步限制圖片緩存的使用;多個 webview 共用圖片緩存池;全面支持 webp、apng 這種更節省內存和 size 的圖片格式。
2.渲染內存:Webview 在不可見的狀態下,原生的內存管理沒有特殊處理,UC 內核會將不可見 webview 的渲染內存釋放;渲染內存的合理設置與調優,避免滾動性能的降低和佔用過多內存。
3.JS 內存:更合理地處理 v8 內存 gc,在啓動時延時執行 full gc,避免影響啓動的耗時。
4.峯值內存管理:系統在內存緊張時,會通知內核,UC 內核可以在系統低內存時釋放非關鍵內存佔用的模塊,避免出現 oom,也避免過分釋放帶來的渲染黑塊;在部分 oom 的狀況,規避原生內核主動崩潰的邏輯,在內存極低的狀況,部分功能不可用,而不是崩潰。
增長小程序的存儲,包括內存和磁盤,能夠緩存部分數據,增長頁面直出速度。同時對於磁盤的管理,按照小程序帳號雙重維度進行劃分。
在支持第三方的接入以後,按照現有方式將會致使對於安全和第三方的行爲徹底不可控,能夠參考微信,支付寶方式採用自定義標記語言的方式對標記語言作限制,並提供純淨的JS環境來進行JS環境的執行,WebView只負責渲染。
參考支付寶方案,在加載的時候,現將老的頁面呈現給用戶,而後在新頁面完成以後,計算差值,再進行顯示。
Native繪製採用經過JS和Native通訊的方式,將Native控價加入到佈局的制定區域。
網絡請求等所有交由Native託管,更好的控制網絡請求,監控網絡請求。