加推Weex實踐之路(上)

1、背景

1.爲何是Weex

在公司快速發展的大環境下,App的更新迭代高速、高頻,技術團隊平均兩週即可誕生一款中型App,但App團隊只有6我的(iOS 、Android各3人),在確保效率、質量的前提下,單純依靠Native的能力顯得步履蹣跚——咱們亟需提高團隊效率,但願單人可完成本來2~3人的工做量。javascript

其一,接入Web頁面,一個頁面適配兩端;html

其二,選擇WeexReact NativeFlutterChameleon等跨平臺開發框架,主流框架對好比下:前端

對比內容vue

React Nativejava

Flutterreact

Weexgit

上手難度程序員

通常github

通常web

容易

接入特色

適合開發總體App

適合開發總體App

適合單頁面

維護難度

通常

通常

容易

開發語言

React

Dart

Vue、Rax

框架體量

較重

較輕

Bundle大小

較大

不須要

較小

社區

豐富

新起之秀

不夠完善

支持終端

Android、iOS

Android、iOS、Web等

Android、iOS、Web

引擎

JSCore、V8

Flutter Engine

JSCore、V8

經過對比,最終選擇了Weex,有如下幾個主要緣由:

  1. Weex的上手成本較低,且單頁面的支持更符合項目規劃;
  1. Vue框架,契合團隊的大前端環境;
  1. Weex承接了淘寶、飛豬等App的大量頁面,給予外界充足的信心。

2.Weex與Web

雖然Web頁面的上手、維護成本更低,但與Weex相比,同等頁面的Web包體積要比Weex大,Web頁面沒法作到純Native的體驗,且頁面加載速度很難極致化,在某些設備上容易出現白屏。Weex所具有的下列優點,足以讓一個追求極致的團隊所青睞。

2、Weex基本原理

Weex支持 VueRax兩個前端框架,因爲前端團隊使用Vue進行平常開發,爲了下降上手成本,咱們選擇Vue框架進行Weex開發。Weex的基本工做流程以下:

Weex we 文件 --------------前端(we源碼)
↓ (轉換) ------------------前端(構建過程)
JS Bundle -----------------前端(JS Bundle代碼)
↓ (部署) ------------------服務器或本地
在服務器或本地上的JS bundle ----服務器或本地
↓ (編譯) ------------------ 客戶端(JS引擎)
虛擬 DOM 樹 --------------- 客戶端(Weex JS Framework)
↓ (渲染) ------------------ 客戶端(渲染引擎)
Native視圖 --------------- 客戶端(渲染引擎)

除去前端頁面的編寫,Weex可劃分爲

DOM
Render
JSBridge
三大塊(線程)。
DOM
負責
JSBundle
的解析、綁定、映射等處理,並通知
Render
線程進行UI的更新。而
Render
線程,即UI線程,負責
Component
的渲染,例如前端編寫的 <text>的標籤,將被渲染成原生封裝的UI組件
TextComponent。JSBridge
負責
JS
端與
Native
端的雙向通訊,通訊的具體邏輯處理則由原生實現的一個個
Module來完成
。下圖是一個頁面被渲染的完整流程:

3、客戶端系統架構

依託於Native,藉助於前端,演變成大前端的架構,總體結構以下圖。咱們但願App做爲終端,提供容器的能力,作好底層服務,完美融合Weex、Web等跨平臺技術。

4、實踐與解決方案

如下記錄了實踐中遇到的一些較爲重要、常見的問題,及其解決方案,它們貫穿了Weex頁面的生命週期。

  1. 頁面間通訊

  1. 路由

Weex自身提供的navigator只支持簡單的在線資源,而不支持本地文件的加載,同時沒法知足動態化的呈現方式和複雜參數傳遞,須要自行實現一套完整的頁面跳轉規則。

在大量使用了

Web、Weex
技術的前提下,爲了保證頁面跳轉、頁面間參數傳遞、回調等的 統一便捷性,以及模塊間的 解耦,跳轉目標 可配置化,採用了 路由的形式來做爲頁面間跳轉的技術方案。基本的路由形式以及完整的過程以下:

// 打開Web頁面
scheme:/web/open?bundleUrl=xxx


// 打開Weex頁面
scheme:/weex/open?bundleUrl=xxx


// 打開native頁面
scheme:/native/open?url=xxx


// 備註:這種形式的路由除了完成App內的跳轉業務外,也可以完美的支持應用間的複製代碼

Weex中發起一次頁面跳轉的示例:

navigator.openUrl({
  url: 'scheme://weex/open',
  params: {
    bundleUrl: '/dist/about.js',
    name: '這是下一個頁面所需的參數',
  },
})


// 備註: 而下一個頁面只須要在data中聲明一個與參數同名的屬性來接收具體的參數便可。複製代碼

  1. 反向傳值

咱們在考慮反向傳值的問題時,主要涉及兩種場景,一是Weex頁面反向傳值給Weex頁面,二是Native反向傳值給Weex頁面。咱們可使用Weex提供的基於W3C 規範

來輕鬆知足第一種場景。可是目前並無現成的API能知足第二種場景,咱們須要不斷的嘗試:

  • 在頁面跳轉時將
    WXModuleKeepAliveCallback
    傳入下一個頁面,而後在合適的時候執行該回調。這對於iOS客戶端很容易實現,但因爲Android在頁面間傳值時須要將參數序列化到內存中,而後在對應的頁面從內存中反序列化出來,這會致使生成一個新的對象,從而沒法完成回調。
  • 咱們有嘗試借鑑
    BroadcastChannel
    的實現,經過Weex項目全局的JSContext對象來觸發廣播完成反向傳值,但最後無疾而終。

咱們最後選擇使用

fireGlobalEvent
,步驟以下:

1.Weex添加事件監聽
const globalEvent = weex.requireModule('globalEvent');
globalEvent.addEventListener("eventName", (e) => {
  // ...
});


2.原生頁面發送事件
weexInstance.fireGlobalEventCallback("eventName", params); // Android


[weexInstance fireGlobalEvent:seventName params:params]; // iOS複製代碼

代碼中的weexInstance,能夠理解爲一個頁面的實例對象,該流程須要在發送監聽的頁面須要獲取上一個

Weex
頁面對應的
weexInstance
,能夠以一種相對優雅的方式告知下一級
----
在跳轉的路由中添加一個
needListen
來告知下一個頁面:

navigator.openUrl({
  url: 'scheme://weex/open',
  params: {
    bundleUrl: '/dist/about.js',
    needListen: true,
  },
})複製代碼

2. 頁面配置文件

起初Weex頁面導航欄、跳轉方式等配置,均在自建Module中進行處理,例如一個頁面要設置導航欄,咱們須要在mounted方法中加入以下代碼:

created () {
    navigator.setTitle('導航欄標題')
    navigator.setItems([{
      title: '按鈕',
    }])
}複製代碼

這種不夠工程化、標準化的方式給後期的維護、跨項目移植、架構升級形成了極大的干擾。

咱們參考小程序的設計思路進行了優化升級,爲每個須要特有化配置的Weex頁面添加一個json格式的配置文件,配置文件包括導航欄的配置、頁面級別的配置、跳轉的配置等,將配置工程化、標準化

例如我爲about頁面添加了配置文件,json文件的類容爲:

{
    "navigationBarTitle": "導航欄標題",
    "navigationItems": [{
        "title": "按鈕"
    }]
}複製代碼

打包後的資源文件目錄以下,about.js、about.json文件在同級目錄:

那麼一個完整的頁面打開步驟以下:

通過擴展,配置文件變得更加豐富,先前麻煩的跳轉處理、彈框等均可以經過配置文件實現,下面是一些經常使用的屬性介紹:

1.部分屬性

屬性

類型

默認值

描述

backgroundColor

HexColor

同項目配置

窗口背景色

navigationBarBackgroundColor

HexColor

同項目配置

導航欄背景色

navigationBarHidden

Bool

false

隱藏導航欄

navigationBarTitle

String



導航欄標題

navigationBarTitleColor

HexColor

同項目配置

導航欄標題顏色

2.iOS特殊形式

針對於iOS客戶端存在頁面須要Present等狀況,能夠設置如下屬性:

present

Bool

false

Present頁面

presentWithNavigationBar

Bool

false

Present頁面,並攜帶導航欄

transition

Map

false

以轉場的形式呈現,並規定轉場的動畫表達式(默認背景alpha從0到1)

優先級:transition > presentWithNavigationBar > present

transition中需定義動畫的表達式,Native則須要解析該表達式,並按照表達式執行動畫。

3.設置導航欄按鈕

navigationItems

Array

[]

包含按鈕樣式的數組

經過fireEvent完成按鈕事件的回調。

按鈕樣式說明:

{
     "type": "TEXT", // "TEXT", "IMG", "TEXT_IMG",必傳




     "title": "標題", // 文字標題或者




     "image": "刷新", // 是圖片地址,圖片地址支持本地圖片和網絡圖片




     "textColor": "FFFFFF", // 16進制, 默認爲白色,可不傳




     "backgroundColor": "", // 16進制, 默認透明色,可不傳




     "borderColor": "", // 16進制, 默認無邊框,可不傳




     "borderWidth": 1, // 默認無邊框,可不傳




     "cornerRadius": 1, // 默認無圓角,可不傳




     "font": 16, // 默認16號字體,可不傳
  
      "position": 0, // 默認0,可不傳, 0-左側顯示,1-右側顯示  
       
     "imagePosition": 0, // 0-圖片在左,文字在右,默認, 1- 圖片在右,文字在左,  2-圖片在上,文字在下,複製代碼

4.自定義導航欄

例如知足導航欄分段選擇的需求:

navigationBarTitleComponent

String

對應的自定義Component的名稱

Component由原生自行實現,並暴露API與Weex進行交互

3. 陰影處理

Weex對於iOS的支持比較友好,然而Android 端沒法顯示陰影。 雖然文檔有明確指出此問題,可是Android sdk卻提供相關方法。也許是阿里的工程師嘗試解決,但效果並不理想。比較明顯的一點是,若是列表中的item使用陰影, 在列表滑動的時候會把陰影殘留在最初繪製的位置。Android的同窗一直在嘗試解決此問題,最終也沒達到一個理想的效果。最後的降級方案是經過圖片來替代陰影,如下是Weex官方的註釋:

目前僅 iOS 支持 box-shadow 屬性,Android 暫不支持,可使用圖片代替。
每一個元素只支持設置一個陰影效果,不支持多個陰影同時做用於一個元素。

4. 網絡請求

Weex提供了

模塊來完成網絡請求,若是依賴於該模塊,請求頭、簽名等配置以及請求結果校驗都必須在Weex端完成,這對於一個全量Weex App而言無可厚非。但不少App的核心業務是使用Native完成,甚至會嵌套不少Web頁面,咱們必須將全部的請求統一至Native,讓Weex更關心的是UI的呈現而非底層業務。

所以咱們提供了本身的網絡請求模塊,Weex端調用Native提供的方法,並經過參數來決定請求,一些可選的參數:

參數

類型

必填

描述

path

String

請求的路徑

method

String

請求的方式,默認爲’GET‘,支持’POST‘、’DELETE‘等

params

Map

請求所需的參數

timeout

Number

請求超時時間

customHost

String

自定義請求的Host

callback

Function

請求的回調

請求示例:

// 1. 獲取原生Module
const nativeStream = weex.requireModule('nativeStream')


// 2. 設置基本參數
const options = {
  path:'/....',
  method: 'POST',
  params: {
    id: '123'
  }
}


// 3. 發起請求
nativeStream.fetch(options, (res) => {
  if (res.code === 0) {
    succesCallback(res)
    return
  }
  failCallback(res)
}複製代碼

5. 圖片加載

<image>標籤圖片的加載須要客戶端提供handler,目前支持遠程連接和打包生成的Bundle資源,並不直接支持相冊圖片以及拍照生成的圖片。其對Base64的支持,爲咱們顯示相冊圖片提供了一種思路。下圖代表了Weex頁面中選擇相冊圖片、拍照並進行顯示的流程:

經過上圖咱們能夠知道,一個簡單的圖片顯示流程,其實並不簡單,其中最爲關鍵的是在進行第5步時所選擇方案。先上傳圖片對於程序員而言是最爲便捷的方案,可是比較影響用戶體驗,圖片本應該在須要上傳的時候進行上傳,而非由於技術隘口而干擾業務。

轉換爲Base64能夠提高用戶體驗,可是卻比較影響性能。在iOS端,將一張1M的圖片轉換爲Base64所須要的時間≥45ms,第六、7步所消耗的時間則是30ms左右,這種時間消耗隨圖片大小以倍數增長。

綜上所述,咱們設計了一種本地化方案,爲每個添加的圖片生成一個惟一性的ID,Native負責圖片的存儲、加載。

6. 刷新組件

因爲Weex所提供的<refresh>組件形式較單一,且存在交互Bug,咱們自實現了一套刷新組件。經過屬性來肯定刷新的顯示與否,並提供相應的接口實現Weex與Native之間的交互。

1.屬性

屬性

類型

必填

描述

showRefresh

Bool

是否添加下拉刷新

showLoading

Bool

是否添加上拉刷新

refreshAtCreated

Bool

是否在初次顯示的時候,自動進行下拉刷新

2.事件

  • refresh 事件:當 <scroller>、<list>、<waterfall> 被下拉完成時觸發,該事件由原生回調到Weex。
  • loading 事件:當 <scroller>、<list>、<waterfall> 被上拉時觸發,該事件由原生回調到Weex。
<list ref="list"
      c
      :show-refresh="true"
      class="list"
      @refresh="refreshList"
      @loading="loadMoreList">
 
</list>複製代碼
  • beginRefresh 事件:開始下拉刷新,由Weex調用。
  • beginLoading 事件:開始上拉刷新,由Weex調用。
  • endRefresh 事件:結束下拉刷新,由Weex調用。
  • endLoading 事件:結束上拉刷新,由Weex調用。
this.$refs.list.beginRefresh()
this.$refs.list.endRefresh()複製代碼

3.refreshAtCreated屬性

若是不使用該屬性,則須要在created或者mounted函數中手動調用刷新的方法以觸發下拉刷新,然而在某些Android設備上面出現了白屏的狀況。Weex對此作出瞭解釋:

和瀏覽器不一樣的是,Weex 的渲染流程是異步的,並且渲染出來的結果都是原生系統中的 View,這些數據都沒法被 javascript 直接獲取到。所以在 Weex 上,Vue 的 mounted 生命週期在當前組件的 virtual-dom (Vue 裏的 VNode) 構建完成後就會觸發,此時相應的原生視圖未必已經渲染完成。

7. 截屏

Weex中完成截屏,只須要獲取到對應的視圖層便可,Weex頁面在渲染時會爲每個組件生成一個惟一的ID,在JavaScript中更直觀的體現是ref,儘管Weex並不存在真正的DOM,但其依然支持ref的使用。具體的作法以下:

// 1. 標籤生命ref
<div ref="poster"></div>


// 2. 獲取到該element,其實是一個Map
const poster = this.$refs.poster


// 3. 獲取Map中對應的ref
saveViewShot({ ref: poster.ref })


// 4. 獲取Component
// iOS
WXComponent *component = [weexInstance componentForRef:ref];
// Android
WXComponent component = WXSDKManager.getInstance().getWXRenderManager().getWXComponent(mWXSDKInstance.getInstanceId(), ref);


// 5. 獲取View,進行複製代碼

8. 優雅的彈窗

這是一個很簡單的彈框需求,視圖漸漸變大最後全屏展現。然而基於Weex現有的能力是沒法實現的,第一點:Weex頁面默認的佈局是從導航欄下面開始的,第二點:路由的跳轉方式並不能直接支持此種彈出方式,頁面默認是從右向左推出。

爲了實現彈框的功能,咱們須要四個步驟:

1. Native定義PopView組件

1. Weex搭建頁面,並以PopView爲基礎進行佈局

2. 全屏呈現頁面並隱藏導航欄

3. 執行動畫

原生定義好PopView組件後,Weex頁面能夠這樣佈局:

<template>
  <pop-view class="pop-view">
    <text>測試彈框</text>
  </pop-view>
</template>複製代碼

結合第三點所提出的配置文件,咱們將二、3步驟的控制放在配置文件當中,最後寫出的配置文件爲:

{
  navigationBarHidden: true, // 隱藏導航欄
  transition: {
    property:'scale', // popView的尺寸將由(0,0)變爲顯示大小
    duration: 2, // 動畫時間,單位(秒)
  },// 轉場顯示,並規定popView的顯示動畫
}複製代碼

這只是最爲簡單的例子,更復雜的動畫須要客戶端支持便可。

5、To Be Continued

以上歸納了Weex接入的心路歷程,以及在實踐中遇到的基本問題,代表了Weex在團隊中的運用已經暢通並日趨規範化,可是更深刻的性能優化、熱更新等須要咱們繼續前行,如下是下一期文章將涉及的知識點:

  • 熱更新
  • 資源預加載
  • 配置文件動態化
  • Weex資源打包自動化,自動加入終端倉庫

加推科學院公衆號

mp.weixin.qq.com/s/LxdQ6Eq2R…

相關文章
相關標籤/搜索