出於對開發效率和動態化的要求,無線端的開發框架也一直在更新,從 Hybrid、結構化 Native View、React Native、Weex,再到現在正在大受關注的 Flutter。什麼樣的框架才是適合自己的團隊?不僅要有技術追求,而且要考慮實際業務需要。最近,有贊移動選擇了 weex 作為無線開發框架,搭建了從開發、Debug、構建、釋出、資料一個閉環的流程。本文將對此進行分享。
一、什麼是 weex
Weex 是阿里巴巴開源的一套構建高效能、可擴充套件的原生應用跨平臺開發方案。首先總結一下 weex 的特點:
-
Weex 也不是隻支援 Vue 和 Rax,你也可以把自己喜歡的前端框架整合到 Weex 中,有一個文件擴充套件前端框架描述瞭如何實現,但是這個過程仍然非常複雜和棘手,你需要了解關於 js-native 之間通訊和原生渲染引擎的許多底層細節。
-
一次編寫,三端(Android、iOS、前端)執行
前提是都整合了 weex sdk,另外視覺表現做不到完全一樣,有的會有一些差異,需要做一下適配。所以寫 weex 頁面的時候,如果支援三端,便需要在三端都進行自測。
-
UI 的繪製通過 native 的元件,JavaScript 邏輯在 JS 引擎裡執行,兩者通過 JavaScriptCore 通訊
weex 裡使用元件都需要在 native 端註冊,這樣 weex 裡才可以使用,執行的時候通過註冊時記錄的 map 進行查詢。weex sdk 內建註冊了一些基礎的元件,包括 list、text、input 等。WXJSCoreBridge 封裝了 JavaScriptCore 實現 native 和 js 之間的通訊。
-
支援 Native 擴充套件
可以將 native 的 UI 元件封裝成 component,將 native 的邏輯程式碼封裝成 module。從而在 weex 裡可以進行使用。這裡的 natiev UI 元件包括 modal、webview、image 等,這裡的 native 邏輯程式碼包括 storage、network 等。
-
每個 weex 頁面會被打包成一個 js 檔案,weex sdk 將 js 檔案渲染成一個 viewweex 的打包通過 webpack,將每個頁面打包成獨立的一個 js 檔案,weex sdk 會將 js 進行解析,將 UI 部分繪製成一個 view, 再繫結 view 的事件與 js 程式碼繫結。
二、為什麼要使用weex進行無線開發
1. 效率問題
1)開發的人力成本
如果不算 web 端,一個頁面本來需要 Android 和 iOS 2 個人開發;使用 weex 後只需要 1 個開發頁面。
2)開發的編譯速度
隨著專案漸漸變得龐大,Android 專案一次編譯需要 2-3 分鐘,機器不好的還需要 10 分鐘,iOS 可能會快一點,也需要 1-2 分鐘。使用 weex 後,介面修改,只需要十幾秒。
3)測試效率
提測之後,發現 bug,修復完成,測試總需要重新下載一個包進行安裝;使用 weex 後,跟原生無關的 bug,只要測試重啟 App 就可以進行驗證。
2. 動態化
weex 頁面最後打包完是一個 js 檔案,只要能做到動態下發 JavaScript,那便可以實現動態化,可以熱修復,甚至可以熱部署,完全替換或者新增頁面。
3. 成熟度
在 2016 年阿里雙十一中,Weex 在阿里雙十一會場中的覆蓋率接近 99%,頁面數量接近 2000,覆蓋了包括主會場、分會場、分分會場、人群會場在內幾乎所有的阿里雙十一會場業務。阿里雙十一主會場秒開率97%,全部會場頁面達到 93%。2016 年 12 月 15 日,阿里巴巴宣佈將移動開源專案 Weex 捐贈給 Apache 基金會開始孵化。2017 年,weex 在阿里業務裡增長如下圖,來自 WeexConf 2018。
4. 接入成本
經過實踐,一個移動端開發,一週時間就可以開始進行使用 weex 進行業務開發。
三、如何使用 weex 進行無線開發
weex 其實是一套方案,各個流程很多東西需要自己建設,把它建設得讓小夥伴可以以較小成本開始使用 weex,把它建設得融入已有的系統。這方面,我們目前做了下面這幾個方面,還任重道遠。
1. 開發工具 zweex-toolkit
這是一個腳手架工具,基於 weex 官方的 weex-toolkit,用於新建 weex 工程,目前只支援 vue。
隨著頁面的增多,業務的複雜,工程會慢慢變得龐大,每次執行的時候如果全部頁面都執行起來比較慢。為了解決這個問題,使用 zweex-toolkit 建立建的工程模板支援執行的時候,支援只執行指定目錄下的頁面,只要在 npm start 後加上引數即可,如:
npm run start hi,helloworld複製程式碼
這樣就表示只執行 hi 目錄下和 helloworld 下的頁面。另外,我們支援:
- 新增頁面
zweex page
- 開啟除錯
zweex debug
2. ZanWeex SDK 的實現
官方 weex sdk 做的事情,就是輸入一個 js 檔案,然後返回一個view。考慮到每個應用的路由和個性化的需要,這一點,ZanWeex SDK 沒有做其他工作,也還是返回了一個view,業務方可以根據自己的需要將view新增到自己想要展示的地方。ZanWeex SDK 做的事情主要有如下幾方面:
1)支援下發配置,支援動態化,可以完成整個頁面的替換
weex 頁面打包後的結果是一個 js 檔案,所以可以進行下發進行動態更新,那麼就需要有一份配置,來關聯頁面路由和 js 檔案的關係,於是我們設計了這樣的資料結構:
h5:頁面路由地址,可以直接使用釋出平臺生成的 h5 地址
js:打包後的 js 檔案地址
version:支援的最低 App 版本,因為新頁面如果需要 native 擴充套件,那就需要釋出新版本進行支援
md5:為了校驗完整性,我們在配置裡新增每個 js 檔案的 md5。
2)支援多模組獨立配置,互不影響一個App裡會有多個模組,每個模組可能由獨立的團隊進行負責,所以為了減少耦合,我們將配置獨立,每個模組可以獨立管理自己的配置,獨立接入weex,不依賴於宿主App。
3)預載入頁面模板,支援頁面模板快取和配置快取
- 如果沒有快取,每次都從服務端拉取頁面模板,那麼是不可能達到秒開的,跟沒有做快取的H5頁面就區別不大了。我們SDK會預載入頁面模板到本地,開啟過的頁面會快取到記憶體。這樣渲染的時間就更接近原生的渲染時間了。
4)支援開發時的hot reloading,前端開發般的體驗
- 如果沒有hot reloading,那麼每次修改完頁面,都得退出頁面重新進入。為了省去這個操作,hot reloading是必須的。
- weex 工程裡本地開發時候,通過webpack-dev-server來啟動一個websocket,zan weex sdk 開啟一個weex頁面後,去與它建立連線。webpack-dev-server將工程的編譯狀態傳送給ZanWeex SDK,當接收到渲染完成的指令時,就重新渲染頁面,從而達到 hot reloading的目的。
5)支援頁面的適配,提供環境變數ZanWeex SDK 會提供以下四個變數共 weex 頁面使用,方便完成頁面配置。
- 容器的高度:weex.config.yzenv.viewHeight
- 容器的寬度:weex.config.yzenv.viewWidth
- 狀態列高度:weex.config.yzenv.statusBarHeight
- 底部欄高度(針對iPhone X,其他為0):weex.config.yzenv.bottomHeight
6)開發階段日誌的檢視在開發階段,weex sdk 原始碼裡輸出的日誌以及 js 裡通過 console.log 輸出的日誌,還有 js 執行的報錯,都只能通過 XCode 和 Android Studio 進行檢視。這對於一個只瞭解一端的開發人員是非常不方便的。於是我們做了一個入口,在開啟 weex 頁面的時候,會顯示該入口,點選即可檢視所輸出的日誌。
7)引數傳遞正向傳參:從 A 頁面跳轉到 B 頁面,引數傳遞是開發過程肯定會遇見的一個場景。SDK 對外提供的渲染介面 renderByH5 的引數包括 url,params,data。業務方進行渲染的時候,可以將引數直接跟在 url 後面,或者通過 params、data 傳入,不同方式,取的方式也不一樣:
-
url 後面的引數,會傳入 data,weex 頁面裡直接在 data 裡定義引數就會自動賦值;
-
params的引數,在 weex 頁面裡可以通過 weex.config.name 來獲取;
-
data 傳入的引數,獲取方式同第一種。
-
反向傳參:從 B 頁面返回到 A 頁面的時候,攜帶引數返回也是很常見的一個場景。SDK 提供了統一的儲存類 ZParamStorage 來臨時儲存引數。頁面 B 要返回的時候先把資料存入儲存區,A 頁面顯示的時候再從儲存區獲取,然後清空儲存區。
-
非跳轉的引數傳遞:weex 頁面之間,可以採用 BroadcastChannel 進行傳參,weex 與 native 之間的傳遞可以通過自己封裝 Module 進行實現。
3. 頁面的開發
前面有提到,weex 的頁面目前可以採用 vue 或者 Rax 編寫。對於 Vue 和 Rax 的語法這裡不做陳述。這裡主要總結了容易在實際開發中卡住小夥伴的幾個問題。
1)如何判斷一個頁面是否用 weex 來實現?
可以認為所有的新頁面都可以採取 weex 來開發,區別在於這個頁面使用的 native 能力有多少。可以通過自定義 Module 來呼叫 native 的能力,通過自定義 component 來使用 native 的元件;
2)什麼時候需要自定義 Module?
- 需要原生的能力的時候,比如:
- 要呼叫系統選擇圖片的介面
- 呼叫打電話、發簡訊的功能
- 開啟其他應用
- 呼叫已有的業務邏輯,比如:
- 加密、解密邏輯
- 登入邏輯
3)什麼時候需要自定義 component?
- 如果一個元件已經使用 native 實現,為了保持統一一致,那麼可以將原有的元件封裝成 component
- 如果一個元件不能使用 weex 實現,比如地圖元件、超長圖顯示等
4)多個彈層的佈局如何實現?
weex 頁面渲染的層級,是從上而下的,越在下面的佈局,顯示越上層。所以要作為彈層的佈局,就把它放到最下面。
5)頁面的動畫如何實現?
官方 weex sdk 已經封裝了 animation 的 module 可以直接使用,複雜的動畫可以使用 BindingX 實現。
6)weex 的程式碼如何複用?
程式碼都可以抽離出元件。
- 作為一個 UI 元件,抽離成一個元件,向外暴露屬性引數和事件介面;
- 作為獨立的 js 函式,抽離成一個 js 供其他頁面引入;
- css 樣式也可以抽離成一個 css 檔案,供其他頁面引入;
- 如果包含多個元件形式,可以通過 mixins 來引入。
4. 構建和打包平臺
我們開發了以專案為單位的構建平臺:
- 每個專案可以新增多個分支,可以是不同倉庫的分支。因為一個專案有可能是跨團隊跨模組的,但是需要一起釋出。
- 構建通過 webpack 構建,構建之後,支援釋出線下儲存和線上 cdn
我們還開發了以應用為單位的 weex 釋出平臺:
- 這裡的應用是一個抽象概念,不是傳統的“應用”,可以理解成模組
- 業務方可以在構建平臺構建完成後,一鍵跳轉到釋出平臺進行釋出,除了需要第一次填寫最低支援的版本號,其他均無需操作。
- 釋出平臺支援灰度釋出、全量釋出和回滾。
- 釋出平臺會展示 weex 在端上的使用情況,渲染時間、渲染錯誤、下載時間等
四、遇到的問題以及解決方案
在開發過程中,很多問題,可以通過閱讀原始碼來解決,比如:
-
使用 iconfont 的時候,是否已支援快取?
答:已支援,包括記憶體快取和檔案快取,記憶體快取使用 familyname 來做 key,檔案快取使用 md5(url) 來做本地檔名
-
module實現的函式能不能返回引數?
答:module 的函式氛圍 UIThread 和 JSThread,JSThread 對於 js 執行緒來說是同步的,支援直接返回引數;UIThread 對於 JS 執行緒來說是非同步的,不支援直接返回引數,只能使用 callback
另外,很多常見的問題,我們已經在 ZanWeexSDK 進行了解決,包括實現動態化、多模組的支援、快取管理、Hot Reloading、日誌檢視、頁面適配、引數傳遞等。
此外,還會有一些常見的問題,在此羅列一下:
-
配置的更新機制是怎樣的?更新失敗,如何開啟 weex 頁面?
答: 配置的更新介面開放給業務方呼叫,由業務方決定什麼時候呼叫更新介面;SDK 裡做了三種處理,來儘量保證配置可以更新成功:
1)配置介面拉取失敗後,會有三次重試;
2)網路從無網變成有網時,sdk 會檢查配置是否已拉取,如果未拉取就主動拉取
3)允許業務方內建配置和 js 檔案,當拉取失敗後,SDK裡會從內建配置裡讀取
-
配置的版本管理是怎樣的?
答:配置每次釋出的時候,都會指定該釋出支援的 App 最低版本號。每次請求,會攜帶 App 版本號,服務端只會返回符合該版本號的最新配置。
-
支援不支援螢幕旋轉?
答:答案是支援的。旋轉之後,螢幕變成了橫屏,weex 就按照橫屏的尺寸來渲染,問題是隻要你寫的頁面符合這種變化就可以了,跟 native 來實現頁面沒有什麼區別。
五、未來還要繼續做的事情
- 元件庫的建設
- 效能統計,比如幀率、記憶體、CPU
- 配置和js檔案的增量更新、推送更新
- 降級處理