目錄:
1.為什麼要使用熱釋出;
2.行業現狀;
3.熱釋出整體設計方案;
4.上線功能和資料情況;
5.使用過程中遇到的問題;
6.之後需要做的事情;
一、為什麼使用熱釋出
1.實時性限制
考拉作為一個跨境電商類的App,從最開始就註定會受到政策類的條款限制,從而導致經常會出現一些實時變更的需求。而目前這些實時性的需求又必須通過App的直接發版本來解決,不僅釋出週期長,應用市場的稽核很浪費時間,而且使用者升級率也不高。
2.新功能依賴升級
當產品策劃提到一些新功能場景時,都必須通過一個完整的迭代流程來進行開發,最終通過釋出新版本來讓使用者使用到新的功能,開發和測試周期長。同時,使用者如果想使用新功能,必須依賴升級。無法讓舊版本的使用者使用到新功能。
3.Bug無法熱修復
移動開發過程中經常會出現因為考慮不周導致的一些線上邏輯問題或者崩潰問題,一般情況下都會通過重新打包釋出應用市場來解決,目前也有更方便的通過補丁來解決問題。通過動態化方案也可以更好的通過熱發的方式來修復bug。簡單高效。
4.多端協作成本高
目前整個移動市場是分為android和ios,當需求提出的時候,會同時通知到android和ios兩位開發,進行需求評審和功能迭代,在需求溝通和實現過程中總會出現各種溝通問題導致需求實現效果不統一,同時兩位開發也會很浪費人力。通過動態化方案,兩端可以共用同一套程式碼,來解決各自的邏輯,能夠節省很多人力。
二、行業現狀
目前比較流行的兩種熱釋出解決方案是,facebook的React Native和阿里的weex。
兩種方案的比較:
相同點:
1.兩者的思想是一樣的,都是通過將js轉換成Virtual-dom再對映到Native的對應view元件;
2.遵循 W3C 標準實現了統一的 JSEngine 和 DOM API,能夠以web的方式(HTML+CSS+JS)來開發native頁面和應用;
3.都能夠完成三端渲染,以及功能模組的動態下發;
不同點:
1.上層DSL語言不同,前端流行的兩大框架就是React和Vue,React Native使用的自家的React,而weex使用的是vue;
2.RN支援ios和Android兩個平臺,web端需要自己支援和相容;Weex號稱支援到三端,但是還是有很多的相容任務;
3.RN在打包的時候會將基礎解析部分和業務邏輯一起打包,所以js檔案會比較大,而weex只是將業務部分打包,具體的解析過程是在sdk中;所以bundle會比較小;
使用感受:兩種方案我們都進行過調研,發現RN在編寫過程中要求還是比較高,沒有vue上手快,而且RN更像是一門新語言,他為不同的端提供了不同的元件,而weex更希望這些工作由native做,在weex頁面中三端使用都是相同的;而且當時RN提供的list元件效能並不是很好,weex提供的是RecyclerView效能相對好很多; 因此我們移動端動態化採用了weex的方案進行。其實這兩種方案都是採用virtual-dom渲染的方式來實現的,下面簡單介紹一下weex方案的原理.
weex的原理:
上面這張圖就是weex的原理圖,首先最開始是通過上層的DSL語言來實現業務功能,實現完成後會通過一些構建方式來進行打包,通過打包可以將整個業務邏輯打包成一個jsBundle,jsBundle裡面就包含了所實現的功能,以及可以被下層jsFramework識別的程式碼。
有了jsBundle之後就可以將它放在後臺系統中,這個就是我們需要熱釋出的功能。
下面一層就是sdk做的事情,sdk在拿到jsBundle之後會通過jscore進行解析,轉換成一個虛擬dom樹,不同的端在各自的sdk中解析虛擬的dom樹再渲染成各自端的元件進行頁面的展示。瞭解了weex的功能原理之後,下面介紹下我們整體的熱釋出應用方案。
三、動態化方案整體設計
整體的動態化方案功能涉及到前端,後端和移動端,三端的功能需求實現,具體的結構圖如下。
1.整體流程的思維導圖
主要包括三大部分:
weex層,weex工程負責業務頁面的編寫,打包和釋出;
後臺層,熱釋出的後臺負責配置相對應的功能頁面,提供App檢測更新的能介面和推送下發更新包的能力;
app層,App端負責檢測更新,快取Bundle,以及渲染展示的策略;
下面可以看下整體的熱釋出流程:
最開始是編寫weex的程式碼實現功能需求,之後通過webpack打包構建jsBundle,將打包構建好的jsBundle釋出到自己的伺服器上,目前使用的是ndp平臺和nginx伺服器,在ndp平臺配置好cdn規則,這樣每一個js檔案就會有一個單獨的靜態資源地址。之後會把這個靜態地址和頁面路由資訊配置在我們的熱釋出後臺。使用者在使用App的時候就可以拿到熱發的頁面渲染。當然再App端也是有兩種方式,一種是使用者主動開啟頁面觸發更新介面拉取新資料,另一種是後臺釋出後推送給App進行後臺更新。 因為我們整個策略是分為三部分,所以下面詳細介紹一下各個部分所做的工作。
a.weex頁面開發部署流程
因為我本身是客戶端開發,對前端的開發流程不甚瞭解,所以在開發過程中走了很多彎路。
其實前端已經有很多年的歷史,很多開發工具都很完善,目前我在專案中使用的也是webpack來進行打包構建。
因為weex上層的DSL選擇的是vue,所以具體需求頁面的程式碼都建議採用vue來編寫(遇到的問題可以參考vue的官方文件vuejs.org/index.html,不過weex由於是在客戶端渲染,所以有些vue中俄功能無法使用到。
程式碼編寫不是重點,重點說下webpack打包時候的配置,我目前是在package.json中配置了兩種打包方案。區分了開發和生產環境。在生產環境下可以對js檔案進行壓縮和混淆。
通過webpack在build檔案的入口處可以設定一些公共的元件,減少vue程式碼中的引入:
const App = require("${relativePath}")
//全域性註冊元件
Vue.component('wrapperLayout', require("components/wrapper-layout.vue"))
App.el = '#root'
new Vue(App)
複製程式碼
我們這裡配置了一個通用的頁面框架,包括導航條和網路請求loading效果。
在vue檔案build的過程中要設定"weex-loader":
let weexConfig = getBaseConfig()
weexConfig.output.filename = 'weex/[name].js'
weexConfig.module.loaders[1].loaders.push('weex-loader')
module.exports = [weexConfig]
複製程式碼
build完成之後會生成對應頁面的js檔案。與java不同,這些js檔案會將引入的元件和方法全部包含進來,所以一個頁面只有一個js檔案,這樣也就導致了這個js檔案可能會比較大。目前來看,我們開發的複雜頁面最大在2-300k上下。
打包完成後會將所有的js檔案和資原始檔通過構建平臺部署在negix伺服器上,並且關聯到cdn,這樣在頁面設定的時候可以直接使用cdn的連結。
b.資源圖片處理過程
資源圖片目前瞭解到的可以採用五種方式:local,base64,CDN,iconfont,SVG。目前我們採用的主要是base64和CDN的方式,而前端活動頁採用的方式是iconfont。
2.後臺介面和資料庫設計
a.後臺資料庫表結構設計
後臺資料庫中存放bundle資訊的表結構引數為:
bundleId,minSupportVersion,bundleVersion,bundleModule,fileDownloadUrl,loadType,desc,person,time
bundleId:當前頁面的唯一標識;
minSupportVersion:當前頁面最低可以支援到的App版本號;
bundleVersion:當前bundle的釋出版本;
bundleModule:當前bundle所屬模組;
fileDownLoadUrl:bundle的下載地址;
loadType:當前bundle頁面可以使用的載入方式,1:網路檢查,先使用本地,二次載入最新;2:網路檢查更新,當此即載入,使用最新;
desc:當前bundle更新情況的的描述資訊;
person:更新者;
time:更新和上傳時間;
資料庫中的表結構為:
bundleId | minSupportVersion | bundleVersion | bundleModule | fileDownloadUrl | loadType | desc | person | time |
---|---|---|---|---|---|---|---|---|
recommendDetail | 3.9 | 1 | seeding | http://gala/3.9/recommend/recommenddetail.js | 1 | 更新了xxx,changeLog... | 張三 | 2017-10-16 |
** 其中bundleId和minSupportVersion一起作為唯一標識 **;一旦兩個有一個有變化,就做為一條新的資料
** 資料庫的操作有兩種情況: **
** 1.native無改動 **
某次更新的內容,並不依賴於WeexSdk和Native的更改,所以之前的所有版本都支援,則後臺介面直接資料庫中根據bundleId和appVersion查詢到當前這條資料,更新bundleVersion(加一),替換fileDownloadUrl為新的地址即可。
例如:原來資料庫中存的是:
bundleId | minSupportVersion | bundleVersion | bundleModule | fileDownloadUrl | loadType | desc | person | time |
---|---|---|---|---|---|---|---|---|
recommendDetail | 3.9 | 1 | seeding | http://gala/3.9/recommend/recommenddetail.js | 1 | 更新了xxx,changeLog... | 張三 | 2017-10-16 |
則更新成:
bundleId | minSupportVersion | bundleVersion | bundleModule | fileDownloadUrl | loadType | desc | person | time |
---|---|---|---|---|---|---|---|---|
recommendDetail | 3.9 | 2 | seeding | http://gala/3.9/recommend/recommenddetail.js | 1 | 更新了xxx,changeLog... | 張三 | 2017-10-16 |
只是檔案和版本變了,其他都不變;這樣3.9和4.0版本的App都可以使用此次開發的新功能。
** 2.native有改動 **
之前的某個版本在資料庫中有一條當前頁面的資料。新版本這次更新的內容,最新版本才可以用,之前所有的舊版本都無法使用,則在資料庫中插入一條新資料。
例如:原來資料庫中存的是:
bundleId | minSupportVersion | bundleVersion | bundleModule | fileDownloadUrl | loadType | desc | person | time |
---|---|---|---|---|---|---|---|---|
recommendDetail | 3.9 | 1 | seeding | http://gala/3.9/recommend/recommenddetail.js | 1 | 更新了xxx,changeLog... | 張三 | 2017-10-15 |
則更新成:
bundleId | minSupportVersion | bundleVersion | bundleModule | fileDownloadUrl | loadType | desc | person | time |
---|---|---|---|---|---|---|---|---|
recommendDetail | 3.9 | 1 | seeding | http://gala/3.9/recommend/recommenddetail.js | 1 | 更新了xxx,changeLog... | 張三 | 2017-10-15 |
recommendDetail | 4.0 | 1 | seeding | http://gala/4.0/recommend/recommenddetail.js | 1 | 更新了xxx,changeLog... | 張三 | 2017-10-16 |
此次更新之後,資料庫中針對recommendDetail的bundleId會存在兩條資訊,第一條給3.9到4.0版本之間的App使用,第二條給4.0版本之後的App使用。
b.後臺介面設計
app中檢測更新的介面:
requestParams:bundleId,appVersion;
後臺介面需根據bundleId,appVersion在資料庫中查詢得到不大於appVersion的minSupportVersion最大的一條資料返回;
response:
{
"bundleId":"recommendDetail",
"minSupportVersion":3.9,
"bundleVersion":1,
"fileDownloadUrl":"http://gala/3.9/recommend/recommendDetail.js",
"loadType":1
}
複製程式碼
例如: 1.如果當前App的版本是3.9,
針對上面的第一種情況,後臺要返回給我最新的bundleVersoin為2的recommendDetail.js檔案
針對第二種情況,後臺要返回給我3.9版本可用的recommendDetail.js檔案,
2.如果當前App的版本是4.0,
針對上面的第一種情況,後臺要返回給我minSupportVersion為3.9,
針對第二種情況,後臺要返回給我4.0版本可用的recommendDetail.js檔案。
c.後臺配置管理系統
3.App端更新展示策略
a.更新邏輯整體架構
通過熱發後臺我們可以根據對應的bundleId拿到需要顯示的Bundle資訊,那麼我們的native頁面又是如何知道要用那個bundleId去請求,以及顯示的呢?下面看下我們App端的策略,這個圖是我們整個App端的架構,其中又分為兩大部分,一部分是路由策略,一部分是檢測更新策略。
路由策略
整個weex頁面都是基於路由功能來進行展示和跳轉的。也就是說每個weex頁面都會有一個唯一的uri。舉一個例子,比如在App中的A頁面,這時候點了一個按鈕跳轉到B頁面,而B頁面是一個weex頁面,那麼A就會將B的路由資訊傳遞給Router,Router會解析這個路由資訊將他對應的bundleId傳給weexVersionManager,即weex版本管理器,在管理器裡拿到bundle進行顯示,如果B頁面再出現了跳轉邏輯,就會通過weex和Native的橋接器再將路由資訊傳遞給Router進行下一次跳轉。
顯示更新策略
上面這部分就是Bundle載入和檢測更新的部分,具體流程可以看下下面的流程圖,這裡主要分為兩部分,一部分是顯示策略,一部分是檢測更新策略。 先看一下顯示的流程,根據之前的流程,路由中將頁面對應的bundleId傳給VersionManager,versionManager在拿到bundleId之後會先在本地檢查是否有可以使用的快取,如果有快取會先取快取來載入,如果本地沒有快取,則會去熱釋出後臺請求最新的bundle,請求回來之後再判斷是否需 要重新整理當前頁面來進行顯示。如果網路失敗了也會給使用者一個網路錯誤的提示。 右邊這部分就是檢測更新的流程,檢測更新的過程是在後臺執行的,不會影響使用者前臺的互動。在開始檢測的時候會去後臺查詢是否有新的bundle可用,拿到返回的資料後會判斷當前這個bundle是否已經下載過了,如果已經下載過了說明本地就是最新的,則不會再做其他事情,也不會影響使用者的使用。如果 本地沒有下載過,說明需要更新和覆蓋之前的快取,那麼就會去下載這個jsBundle,下載成功後將jsBUndle快取起來。再根據之前協議規則中設定的loadType進行區分。之前提到了loadType是分為當次載入和二次載入,如果是當次載入會直接重新整理頁面,如果是二次載入則等到使用者第二次進入 的時候才會使用這個最新的。
四、線上頁面及資料情況
在考拉AndroidApp中,目前weex頁面在考拉App中的佔比達到了10%。目前上線的功能是種草社群和小考拉問答絕大部分頁面。如下圖:
效能情況:整個頁面的渲染時長可以維持在1秒內。其中下載js時長是:100-200ms。正常頁面渲染時長在150-300ms左右
五、一些經驗總結
1.關於.9圖片的使用: 在img標籤裡可以使用.9圖片,但是要將resize屬性設定為stretch,如下:
<img class="comment-reply-bg" src="local: //res/bg_comment_reply" resize="stretch"/>
複製程式碼
2.關於class樣式切換:
<div class="focus-common" :class="[focusStatus?'focused-container':'focus-container']" >
複製程式碼
3.style樣式切換:
:style="{color: focusStatus?'#999999':'#8CAA5E'}"`
複製程式碼
4.父元件呼叫子元件的方法和data:
在子元件中設定ref即id,在父元件中即可通過ref來獲取到對應的子view呼叫子view的方法:
父vue:
<recommendDetailPage ref="detailPage"></recommendDetailPage>
this.$refs.detailPage.setData(this.$refs.detailPage[0].recommendDetail)
複製程式碼
5.子元件給父元件回撥:
//子元件通過emit通知父元件,event中傳引數
showGoBack(event) {
this.$emit('showgoback',event);
},
//父元件使用子元件時,html中宣告這個方法,js中呼叫方法
<bridgeWebview :loadUrl="url" class="webview_style" @showgoback="showGoBack"></bridgeWebview>
showGoBack(event) {
console.log("event.canGoBack2:"+event.canGoBack);
},
複製程式碼
6.text顯示問題
text標籤中的內容不能換行,否則在手機上顯示會出現換行;下面程式碼中第二個會顯示兩行。例如:
1. <text >hello world</text>
2. <text >hello world
</text>
複製程式碼
六、之後還需要做的事情
1.三端共建,提高效率 我們考拉移動端目前也在三端通用性上進行了進一步的調研和實現。因為ios和android兩端之前的底層支援情況不一樣,導致在共建底層介面和共建元件,以及溝通上會花費一些時間。之後的目的主要是可以完善底層的共建框架,這樣上層的業務才可以在三端通用,提高開發效率。
2.元件weex的熱釋出 目前我的開發的weex都是頁面級別的,後續會在元件級別上進行一些嘗試。並且抽離這些公共元件,打包成npm包,可以在多個專案中共用。