目錄:
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的官方文檔https://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包,可以在多個項目中共用。