小程序渲染架構設計

One 什麼是小程序

Ⅰ 小程序概念

微信小程序算是小程序的鼻祖了,2017年1月9日微信正式上線了小程序。在探究小程序技術架構以前,咱們先看看小程序到底是什麼,微信官網對微信小程序的產品定位及功能介紹是: 「微信小程序是一種全新的鏈接用戶與服務的方式,它能夠在微信內被便捷地獲取和傳播,同時具備出色的使用體驗。」前端

這個介紹有種看了跟沒看同樣的感受。網上對於微信小程序是什麼還有一個介紹的版本: 「小程序是一種不須要下載安裝便可使用的應用,它實現了應用「觸手可及」的夢想,用戶掃一掃或搜一下便可打開應用。也體現了「用完即走」的理念,用戶不用關心是否安裝太多應用的問題。應用將無處不在,隨時可用,但又無需安裝卸載。」web

這個概念就更清晰一些,能夠看出小程序是衆多實例運行在一個宿主應用中,小程序自己也是一種可插拔的外接應用。小程序

Ⅱ 用戶角度的小程序

下面從用戶使用交互角度來看一下小程序:微信小程序

(圖1)

iOS:從小程序獨立性角度(小程序與小程序之間,小程序與宿主應用之間切換)來講,BATT 的小程序與招商銀行的小程序基本交互類似。 Android:從交互上來看BATT 的小程序均可以看作是獨立的應用程序,獨立存在於後臺(多進程),能夠在小程序與小程序之間,小程序與宿主應用之間切換。能夠直觀的理解爲這類小程序爲小程序應用。招商銀行的的小程序是與宿主應用共存的,也就是在一個進程中,不能在小程序與小程序之間,小程序與宿主應用之間切換。這類小程序能夠直觀的理解爲小程序頁面。安全

BATT: 微信,支付寶,頭條,百度小程序。因爲交互類似,因此並稱。微信

因此,從用戶使用角度來看,BATT的交互體驗更有優點。從對小程序概念的理解來看,各app理解有所差別,但這並不影響功能層面的使用。babel

Ⅲ 平臺角度的小程序

最先應用小程序的微信爲何會創造出小程序這個東西呢?它到底有什麼做用?在我看來,主要目的仍是在於管控爲目的,使用了多個手段來實現,主要管控在於兩個方面:網絡

UI管控:以微信爲例,微信本身定義了一套DSL,而不是用HTML來開發頁面。這樣就不能讓開發者隨意開發,而是在微信的DSL框架中開發。開發者寫的DSL具體轉換成什麼,是經過什麼渲染,都是微信平臺來決定。基於自定義的這套DSL,能夠更好的作代碼管控方面的工做,好比:請求白名單,代碼掃描等。多線程

服務管控:仍是以微信爲例,微信中的宿主平臺提供的服務(好比:支付,微信運動,卡券,發票,用戶帳號信息等)對於不管是二方使用者仍是三方使用者,都有權限管控的需求。目的也是不能讓接入的小程序,在沒有受權機制的前提下,隨意調用微信基礎服務。架構

Two 小程序技術架構

基於從用戶角度的體驗需求,以及平臺角度的管控需求,咱們來看看BATT系小程序在技術上作了哪些達到了這些目的。

Ⅰ 渲染流程

下圖是小程序的渲染流程,裏面包含了部分技術選型,後面的部分會提到:

(圖2)

Ⅱ 主要技術點

  1. DSL(Domain-specific language):

在咱們聊DSL以前,咱們先看看編譯代碼須要作哪些工做。不管是解釋性語言(JS,Ruby,Python)仍是編譯型語言(Java,C++,C#),都會有一個共同的部分,將源代碼解析爲AST(抽象語法樹)。AST不只可以以結構化的方式呈現源代碼,並且在語義分析中起到關鍵做用。AST不只僅應用在解釋器和編譯器,並且在靜態代碼分析中也比較經常使用,好比:咱們在重構代碼的時候,但願提取出公共模塊,以便減小重複代碼方便複用。這時咱們單純的用字符串比對的方式會比較片面不能達到效果,這時生成AST就比較有用。另外一個應用例子對於咱們的DSL設計會比較有借鑑意義:代碼轉換器。下圖是一種語言代碼轉換爲另外一種語言代碼的主要步驟:

(圖3:圖片來源於網絡)

源代碼先解析成AST,解析以前它是遵循語言規則的文本,解析以後成爲與輸入文本徹底相同的樹形結構,這個過程是可逆的。而後再對AST遍歷以及替換,這個過程對於前端來講相似於DOM樹的生成,最後根據修改後的AST生成編譯後的代碼。咱們以JS爲例,用acorn生成的AST,一樣咱們也可使用其餘的解析器,例如:babylon,esprima等,下面是一個簡單的例子(限於篇幅,右側的AST樹沒有徹底展開,讀者能夠到astexplorer上生成結果):

(圖4)

因爲小程序的渲染容器有多是Webview容器,原生Native容器,Flutter容器(雖然Flutter也是Native渲染,爲了與原生Native區別,這裏把它單獨出來,下同),因此咱們能夠借鑑前面的代碼轉換器的思路,用AST抹平具體渲染容器的區別,下圖是DSL轉換的總體思路:

(圖5)

有了以上的的設計並非大功告成,還須要有不少須要作的工做要作。咱們能夠簡單的把DSL的處理分爲編譯時和運行時,編譯時負責把DSL代碼預編譯爲目標代碼,目標代碼能夠在相應的運行時環境執行。生成的目標代碼的做用是,能夠在具體的運行時經過當前環境的參數來執行出實際的代碼,簡單的理解就是爲了在多渲染環境運行的一個適配器。

對於編譯時來講,從零寫起確定是不現實的。首先咱們繼續上面AST的話題,上面已經提到了幾個AST的解析器:acorn,babylon,esprima。固然還有不少其餘的例如:cherow,espree,shift等。因此咱們不用再造一個輪子,下面用babylon舉例,由於babylon在babel中使用,會與最近的JS功能同步,而且API設計良好易於使用。babel是js編譯器(並不只僅是ES6支持的工具包,不然就變成了相似於Android裏面的support包了),能夠用於代碼壓縮,語法轉換等。對於生成目標代碼的過程:解析(babylon),轉換(babel-traverse)都有很好的支持。因爲不一樣的渲染容器有不一樣的組件庫和API,一樣功能的組件或API的使用也不盡相同,因此須要封裝出適配層代碼。

對於運行時來講,只須要把編譯生成的適配代碼生成具體渲染環境的代碼執行就能夠了。這裏比較相似babel把ECMAScript新版代碼轉換成舊版代碼的邏輯比較像。

  1. Native渲染

從性能角度出發,把小程序最終經過Native方式渲染會比用Webview做爲渲染容器獲得更好的效果。DSL的設計能夠很好的屏蔽底層實際渲染的實現,能夠用Native,也能夠用H5,也可使用二者結合的方式,底層渲染引擎的切換也不會影響到小程序開發者的外部接入。目前移動端跨平臺Native渲染的技術很是流行,比較經常使用的有Weex/RN/Flutter。市場上有基於Weex和RN進行小程序的案例,Flutter畢竟是後起之秀,目前尚未見到用Flutter做爲渲染引擎的案例。

  1. Android多進程:

前面咱們在從「用戶角度的小程序」部分看到,BATT的方案讓小程序真正能夠作到像應用同樣的體驗。因爲iOS應用沒法開啓新進程讓小程序自己在獨立進程中運行,因此iOS中的小程序只能與宿主應用共享同一進程。對於Android來講就不同了,小程序佔用獨立進程,從安全角度來講,二方後者三方的小程序應用與宿主應用進程隔離,小程序出現的問題不會影響宿主應用。並且,從性能角度來講,小程序不會共享宿主應用的內存。從BATT的小程序實際應用操做來看,基本都會控制五個後臺開啓的進程保活,能夠用五個容器Activity各自在本身的進程中渲染小程序,有的還會有後臺的保活時間限制。再開啓新的小程序,會關閉最先打開的小程序進程,這樣達到了高效熱啓動的目標。

  1. 多線程:

邏輯渲染隔離: 首先,在聊具體的多線程以前,先說一下小程序的邏輯和渲染的問題。小程序的邏輯和渲染是分離的,固然從功能層面,不分離同樣能夠實現。這裏說的邏輯和渲染分離是指小程序的邏輯運行在單獨JS環境的線程中,只須要JS引擎就能夠,渲染運行在Webview線程中。邏輯和渲染分離到兩個線程有幾點好處:第一個是能夠邏輯和渲染代碼分離沒有耦合,第二個是可讓邏輯線程和渲染線程並行執行,JS執行不會阻塞UI。第三是補充了前面所說的UI管控的目的,邏輯線程裏面JS在JS引擎中運行,而不是在Webview裏面,這樣就限制了經過注入JS代碼來操做dom的可能,任何與UI相關的API都沒有辦法經過JS來改變,這樣就與DSL一塊兒達到了UI管控的目的。第四個好處是多個小程序頁面共享同一個JS邏輯運行環境,能夠方便高效的共享數據。

(圖6)

上圖展現了邏輯層與視圖層的通訊過程,通訊經過Bridge中轉,利用發佈/訂閱模式。視圖層經過觸發UI事件,會把事件經過bridge傳遞到Native,Native再經過bridge把事件中轉給邏輯層,邏輯層處理事件完成後,把數據再經過bridge傳遞到Native,以後再由bridge返回給視圖層作渲染。

優化: 邏輯和渲染分離以後,邏輯線程須要把數據發送給渲染線程,渲染線程須要把事件發送給邏輯線程,這都須要序列化爲字符串進行傳輸。這樣會帶來一個問題,頻繁的數據傳輸,和單次大數據量傳輸都會帶來性能問題。針對這個問題,支付寶小程序的設計思路比較值得借鑑,支付寶小程序從新設計了V8虛擬機,讓邏輯和渲染都有本身的Local Runtime,存放私有的模塊和數據。又提供了共享的Global Runtime的Shared Heap來共享數據,這樣依然保證了邏輯和渲染的隔離,又減小了序列化和傳輸成本。

  1. 預加載:

小程序的開發者在小程序應用方面,作了不少優化,好比:數據的預加載。從用戶點擊頁面,到新頁面onload(),會延遲100ms-300ms,這個延遲時間,能夠作數據的預加載。這裏所說的小程序啓動預加載,是小程序渲染框架層面的。iOS的優化會預加載比實際渲染小程序數多一個wkwebview放在後臺,打開新的小程序會直接把預先加載的wkwebview直接渲染,節省了初始化時間;Android上實現稍微複雜一點,不過依然是空間換時間的思路,從Android宿主應用啓動開始,就會啓動一個預留進程,當開啓新的小程序,會佔用這個進程,並再預加載新的進程,直到開啓第五個進程的上限。

  1. 離線包(分包):

離線包機制的根本目的在於讓小程序打開的時候,可讓頁面資源從網絡IO替換爲本地IO。其實就是在app打開以前從網絡拉取或者推拉結合,預置等方式讓離線包能夠在打開小程序以前就已經在本地了。離線包模塊的職責包括:更新,解壓,存儲,讀取和校驗等,固然也能夠作二進制的差量包以。有了離線包機制,也要考慮把整個小程序總體做爲一個離線包會影響效率的問題,因此這裏須要增長分包的方案,能夠把離線包分爲一個主包和多個子包的形式,主包裏面主要包括:首屏資源,公共代碼,相關子包的信息等;子包能夠包含二級頁面的頁面資源。這樣就能夠提升首屏打開速度,能夠作到按需加載的目的,以下圖:

(圖7)

Ⅲ 技術選型

  1. IDE

小程序平臺都有本身的IDE,對於多系統平臺的現狀,選取跨平臺桌面技術開發小程序IDE,確定是最好的選擇,這裏選擇了Electron和NW.JS作了一下對比:

(圖8)

對比結果簡單的說,二者開源協議都比較友好,若是重視代碼安全性或者兼容XP需求,就選擇NW.JS,也是國內廠商的選擇;若是從開發支持角度來比較,就選擇Electron。

  1. JS引擎

前面已經說了,邏輯層具備單獨的JS環境,也從管控角度說明了這樣作也能夠防止js修改UI的風險。就技術選型角度來講,iOS可使用自帶的JScore,雖然iOS上wkwebview的JS引擎比JScore多了JIT優化,執行速度快不少,可是比起額外引入js引擎來講,使用自帶js引擎具備穩定且不增長包大小的先天優點。這塊可能有人會提到Flutter在iOS裏面引入了skia渲染引擎的問題,Flutter在iOS引入skia的好處是與Android自帶的skia引擎使用相同渲染引擎,這樣會在UI兼容性上有更好的提高。而js引擎兼容性問題就小的多。 Android方面,可選擇性多一下,如下是一個主流JS引擎對比:

(圖9)

微信小程序舊版本用的JScore,新版本用的V8;支付寶小程序用的從新設計的V8;頭條小程序也是使用的V8;能夠看到V8的中標率仍是很高的,並且開源協議也比較友好。

Three 結語

本文算是介紹了一種小程序渲染架構的一種實現方式,就小程序平臺自己來講,還有一些其餘的功能和優化點,好比:小程序路由,Debug包加載,埋點統計,虛擬Dom的優化等。文章只是介紹了一些主要流程和技術點,真正作一個完善的小程序平臺仍是須要不少細節須要考慮的,就小程序開發者的角度來講,也是有優化空間的,好比:骨架屏。作一個小程序平臺須要多平臺多種技術能力的綜合應用才能不斷完善,隨着新技術的涌現,將來會有更多的技術應用到小程序中。

做者簡介

王利航,2018年9月加入團隊,曾就任於阿里巴巴,搜狐暢遊,目前負責民生科技公司移動開發平臺建設。

相關文章
相關標籤/搜索