Cordova 代碼熱更新 - 簡書
Cordova 代碼熱更新
[圖片上傳失敗...(image-a19be7-1521624289049)]
基於 Cordova 框架能將網頁應用 (js, html, css, 圖片等) 打包成 App。當 App 在應用商店上架後,如何快速更新是我們需要考慮的問題。??
- 本地打包新版本 App 發布到應用商店,但這中發布流程耗費時間,尤其是 Apple Store
- 應用加載網絡資源,這樣 App 展示的內容就可以保證是最新的,但當應用斷網時,應用就無法正常使用
我們能想的這兩種方式都存在的很大的弊端,不適合實際應用!
插件 Cordova Hot Code Push (CHCP)
插件 Cordova Hot Code Push 正是針對 Cordava 應用如何快速更新問題而提供的解決方案,可以自動更新 web 相關的靜態文件。
該插件提供了詳細的 wiki 文檔,請參考:wiki 文檔
App Store 支持麽
近日,蘋果App Store審核團隊向一些開發者下最後通牒:2017年6月12日之前移除所有熱更新相關代碼、框架或SDK,並重新提交版本。如果不作調整,App可能會從App Store下架。
蘋果應用商店已經禁止使用類似 JSPatch 等熱修復的框架或者SDK,那麽這個插件提供的代碼熱更新功能是否違法這一規定呢? ??
?? 答案是否定的!此插件提供的代碼熱更新是 web 靜態文件,蘋果是允許這一做法的。但有兩點值得註意:
- ① 不能明顯告知用戶有新版本可用,詢問用戶是否需要更新到最新代碼。這一做法會使用戶產生困惑,這種更新方式和通過 App Store 更新有何區別。所以正確的做法是,在應用啟動的時候,下載和安裝熱更新代碼;或者在某個時機下載熱更新代碼,在應用下次打開時進行安裝
- ② 如果通過此插件進行代碼熱更新後,應用功能發生巨大變化,譬如原來是一個計算器應用,代碼熱更新後,變成了一個音樂播放器,這種欺騙用戶的做法也是會被蘋果拒絕的
添加插件到項目中
1、下載插件
要求 Cordova 版本 5.0+
cordova plugin add cordova-hot-code-push-plugin --save
2、下載插件的命令行工具 cordova-hot-code-push-cli
npm install -g cordova-hot-code-push-cli
該命令行工具可幫助我們自動生成配置文件 chcp.json
和 chcp.manifest
,同時還提供了一些其他功能,詳細可參考其 README。
3、下載插件後,執行 cordova platform add ios
時可能會遇到如下報錯:
Error: Cannot find module ‘xml2js/lib/processors‘
參考 xml2js is not installed 解決方法很簡單:npm install xml2js
熱更新相關配置
Cordova Hot Code Push 下載安裝到項目中後,需要對其進行相關的配置才能讓其工作。
插件配置
Cordova Hot Code Push 熱更新插件需要兩個配置文件:
-
Application config:
chcp.json
包含發布相關信息:熱更新代碼版本號,應用 native side 版本號等等 -
Content manifest:
chcp.manifest
包含項目熱更新代碼(靜態)文件信息:文件名和文件哈希值
這兩個配置文件對於插件的運行缺一不可,前者描述了熱更新代碼的版本信息,後者提供了熱更新代碼文件的變更信息。借助 cordova-hot-code-push-cli
這個命令行工具可以輔助我們創建這兩個配置文件。
Application config
Application config holds information about the current release of the web project.
chcp.json
置於 www
目錄根目錄,例子如下:
{
"name": "wps-*****",
"content_url": "https://kss.ksyun.com/*****/*****/",
"ios_identifier": "326CN*****",
"android_identifier": "com.**********.*****.*****.*****.*****",
"update": "resume",
"release": "2017.06.07-16.30.20",
"min_native_interface": 1
}
1、配置項
① name
項目名稱
② content_url
web 項目文件在服務器上的存儲路徑(即 www 目錄在雲存儲中的目錄路徑),熱更新插件將此 URL 作為 base url,用於下載配置文件和項目更新文件(必需的配置項)
③ release
描述 web 項目版本號,每一次發布的版本號必須唯一(默認使用時間戳,格式為:yyyy.MM.dd-HH.mm.ss),插件是將版本號進行字符串相等比較來判斷是否存在新版本(必需的配置項)
④ min_native_interface
Minimum version of the native side that is required to run this web content
- cordova 項目主要包含兩部分:web content 和 native side。前者是網頁內容,後者是 cordova 插件,為網頁提供原生 API 支持,web content 的運行是基於 native side。
- 該配置項指明 web content 運行時 native side 的最低版本。在 native side 代碼有變更後(cordova 插件新增/刪除,native side 版本號更新),為了確保 web content 能正常運行,需要更新
min_native_interface
的值
在應用 config.xml
配置中可以定義了 native side 的版本號,例如
<chcp>
<native-interface version="5" />
</chcp>
例如當前項目 native side 的版本號是5:
- 如果服務器上配置文件
chcp.json
中的min_native_interface
值為 5,那麽符合要求,熱更新後的 web content 能夠在正常運行 - 如果服務器上配置文件
chcp.json
中的min_native_interface
值為 10,高於config.xml
文件中<native-interface />
,那麽熱更新將無法正常進行。此時,插件會提示錯誤chcp_updateLoadFailed
,提示應用需要更新升級
⑤ update
何時觸發進行安裝(install)代碼熱更新
代碼熱更新涉及兩個主要過程:fetch update 和 install update。前者獲取熱更新變更文件,後者將獲取到的更新文件安裝到 App 中生效。此字段是針對後者,何時 install update,可選值:
-
start
:應用啟動,默認項(install update when application is launched) -
resume
:應用從後臺恢復(install the update when application is resumed from background state) -
now
:下載更新後立即執行(install update as soon as it has been downloaded)
當然也可以禁用自動 install update,手動調用相關 API 進行 install
⑥ android_identifier
/ ios_identifier
-
android_identifier
: Package name of the Android version of the application -
ios_identifier
: Identification number of the application
用於跳轉到 Google Play Store 或者 App Store 該應用頁面
2、如何生成該文件:
- 在 cordova 項目根目錄執行
cordova-hcp init
,會通過命令行交互的方式,提示輸入配置有關信息,創建該文件,會在項目根目錄創建一個默認 Application config 文件cordova-hcp.json
- 然後在每次應用打包時,再執行
cordova-hcp build
即可在 web 項目www
根目錄生成一個chcp.json
文件。
Content manifest
Content manifest describes the state of the files inside your web project.
通過執行 cordova-hcp build
在 www
根目錄自動生成 chcp.manifest
文件
[
{
"file": "import.html",
"hash": "fc9301d4bd7381ba6033aa51884ed2dd"
},
{
"file": "index.html",
"hash": "f73630f62a531ab6c41cd067eb4f9b07"
},
{
"file": "lib/lib.min.js",
"hash": "6ecb0251f4c54f80586d9059dfc61de8"
},
...
]
chcp.manifest
文件中包含的是 web content 靜態文件信息,每一個項都包括兩個字段:
-
file
: 相對於www
目錄的文件路徑 -
hash
: 文件的 MD5 哈希值,用於判斷文件是否發生變更
基於 chcp.manifest
文件
- 在 fetch update 階段,從服務器上獲取新增、修改文件
- 在 install update 階段,移除被刪除文件
Cordova config.xml
配置
Cordova 項目的 config.xml
文件用於設置項目配置選項,Cordova Hot Code Push 熱更新插件的配置項也需要在該文件中進行相應的配置。
<chcp>
<config-file url="https://kss.ksyun.com/********/chcp.json" />
<auto-download enabled="false" />
<auto-install enabled="false" />
<native-interface version="1" />
</chcp>
</pre>
-
config-file
:配置文件chcp.json
從服務器上加載的路徑(必須的配置項) -
auto-download
:是否自動下載熱更新代碼,默認是 true -
auto-install
:是否自動安裝熱更新代碼,默認是 true -
native-interface
:當前 native side 的版本號
可以禁用自動下載,安裝熱更新代碼,通過手動調用執行。
代碼熱更新原理
熱更新流程
[圖片上傳失敗...(image-e57e54-1521624148035)]
- ① 應用啟動
- ② 熱更新插件初始化,並在後臺加載更新模塊 (update loader)
- ③ 更新模塊 (update loader) 從 Cordova 項目配置
config.xml
文件中獲取config-file
(熱更新插件配置文件chcp.json
的加載路徑),然後加載配置文件chcp.json
,獲取其中的release
版本號,對比當前的版本號,若二者不同,說明有新版本,執行下一步 - ④ 更新模塊 (update loader) 從
chcp.json
配置文件中獲取content_url
作為 base url,然後加載chcp.manifest
文件,或者新版本文件變更信息 - ⑤ 更新模塊 (update loader) 根據
content_url
作為 base url,下載所有變更、新增文件 - ⑥ 如果一切順利, 更新模塊 (update loader) 發送通知,該更新已準備好進行安裝
- ⑦ 安裝更新,應用重定向到新版本頁面
Cordova web project 存儲與更新
Cordova 項目中都包含一個 www
目錄,存儲網頁靜態文件,Cordova 打包移動應用時,會將其拷貝到各自的項目目錄,同時會被打包到應用中。
- Android: platforms/android/assets/www.
- iOS: platforms/ios/www.
www
目錄打包到應用中之後,我們就沒辦法對其進行更新了,因為只有可讀權限。為了解決這一問題,在應用第一次啟動的時候,從應用 bundle 中加載網頁內容的同時,將 www
目錄拷貝到外部目錄中,在後續應用啟動時,都從這個外部存儲的靜態文件中加載文件,而對於外部的這個存儲目錄,我們就有讀寫權限,這樣就為我們動態更新網頁代碼提供了可能。
在 safari 調試頁面執行 cordova.file.applicationStorageDirectory
可以得到應用的存儲路徑,點擊可以打開 Finder 目錄。
從 Library/Application Support
目錄下就可以找到存儲 web content 的外部目錄。
[圖片上傳失敗...(image-d0c218-1521624148035)]
[圖片上傳失敗...(image-5ff6fd-1521624148035)]
Cordova Hot Code Push 插件為每一個版本內容都創建了一個對應的目錄,以配置文件 chcp.json
中 release
字段值為目錄名,存放不同版本 www
目錄中的靜態文件,這種處理方式的好處是:
- 避免了文件緩存問題。例如 iOS UIWebView 緩存 css 文件,即使刷新頁面,也不會清除緩存,除非重啟應用才能強制清除緩存。不同版本置於不同的目錄,由於加載路徑不同,這樣就可以解決文件的緩存問題
- 避免在更新代碼文件時,和當前已有文件出現沖突
- 方便回滾到前一個版本
?? 下面了解一下,獲取更新內容和安裝更新內容時都發生了什麽?
1、獲取更新內容
- 根據
release
版本號,創建一個新的目錄 - 在新目錄中,創建
update
目錄,根據chcp.manifest
文件,將所有變更、新增文件下載到該目錄中 - 新版本對應的
chcp.json
和chcp.manifest
文件也會置於update
目錄中
[圖片上傳失敗...(image-b49207-1521624148035)]
2、安裝更新內容
- 將當前版本對應目錄下的
www
目錄拷貝到新版本對應的目錄下 - 在新版本對應目錄下,將
update
目錄中變更、新增文件拷貝到www
目錄中,同時根據chcp.manifest
移除被刪除文件 - 移除
update
目錄 - 應用重定向到新版本目錄下加載網頁內容
插件 JS 接口
默認情況下,Cordova Hot Code Push (CHCP) 插件不需要額外的代碼,就可以自動執行 checking->downloading->installation
這個更新循環。當然也可以通過其提供的接口來控制這更新流程,這時,我們需要在項目 config.xml
文件中配置 auto-download
和 auto-install
為 false
<chcp>
<config-file url="https://kss.ksyun.com/******/chcp.json" />
<auto-download enabled="false" />
<auto-install enabled="false" />
<native-interface version="1" />
</chcp>
事件監聽
Cordova Hot Code Push 插件提供了一系列事件監聽,方便我們對不同情況進行不同的處理。例如:chcp_updateInstalled
事件,當更新安裝完成時會發出這個通知;chcp_updateInstallFailed
事件,當更新安裝失敗時發出這個通知,等等。
值得註意的是,需要在 deviceready
事件回調後,才進行 CHCP 插件的事件監聽註冊。
var app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind any events that are required.
// Usually you should subscribe on ‘deviceready‘ event to know, when you can start calling cordova modules
bindEvents: function() {
document.addEventListener(‘deviceready‘, this.onDeviceReady, false);
document.addEventListener(‘chcp_updateIsReadyToInstall‘, this.onUpdateReady, false);
},
// deviceready Event Handler
onDeviceReady: function() {
console.log(‘Device is ready for work‘);
},
// chcp_updateIsReadyToInstall Event Handler
onUpdateReady: function() {
console.log(‘Update is ready for installation‘);
}
};
app.initialize();
詳細事件監聽列表參考文檔:Listen for update events
獲取/安裝更新
① fetch update chcp.fetchUpdate
調用 API 從服務器中獲取更新
function fetchUpdate(cb) {
var options = {
‘config-file‘: ‘https://kss.ksyun.com/******/chcp.json‘
};
chcp.fetchUpdate(updateCallback, options);
function updateCallback(error, data) {
if (error) {
console.log(‘--fetchUpdate error--‘, error.code, error.description);
}
console.log(‘--fetchUpdate--‘, data, data.config);
cb && cb(error, data);
}
}
② install update chcp.installUpdate
調用 API 安裝更新
function installUpdate(cb) {
chcp.installUpdate(installationCallback);
function installationCallback(error) {
if (error) {
console.log(‘Failed to install the update with error code: ‘ + error.code);
console.log(error.description);
} else {
console.log(‘Update installed!‘);
}
cb && cb(error);
}
}
在安裝更新之前,還需要檢測是否有更新可用於安裝
chcp.isUpdateAvailableForInstallation
function checkIsUpdateAvailableForInstallation(cb) {
chcp.isUpdateAvailableForInstallation(callbackMethod);
function callbackMethod(error, data) {
if (error) {
console.log(‘No update was loaded => nothing to install‘);
} else {
console.log(‘Current content version: ‘ + data.currentVersion);
console.log(‘Ready to be installed:‘ + data.readyToInstallVersion);
}
cb && cb(error, data);
}
}
獲取版本信息
function getVersionInfo(cb) {
chcp.getVersionInfo((err, data) => {
console.log(‘Current web version: ‘ + data.currentWebVersion);
console.log(‘Previous web version: ‘ + data.previousWebVersion);
console.log(‘Loaded and ready for installation web version: ‘ + data.readyToInstallWebVersion);
console.log(‘Application version name: ‘ + data.appVersion);
console.log(‘Application build version: ‘ + data.buildVersion);
cb && cb(err, data);
});
}
錯誤代碼
在下載,安裝更新過程中都有可能出現錯誤,詳細的錯誤代碼參考:Error codes
請求到應用商店進行 APP 升級
插件配置文件 chcp.json
中 min_native_interface
選項是網頁內容執行時要求 native side 最低版本號。每一次熱更新過程中,都會去檢查這個邏輯,判斷當前 native side 的版本是否符合要求。如果當前 APP 中的 native side 版本號低於 chcp.json
中 min_native_interface
的選項值,那麽執行熱更新就會提示錯誤:chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
,這個時候,我們應當提示用戶前往應用商店對 APP 進行升級。
恰當的處理方式是,在出現 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
錯誤時,彈框提示用戶前往應用商店進行升級,彈框有兩個按鍵:一個點擊後跳轉到應用商店該 APP 對應下載頁面;另一個點擊後關閉彈框。插件也提供了 API 處理這個過程,我們只需:
- 在
chcp.json
配置文件中設置android_identifier
和ios_identifier
- 調用
chcp.requestApplicationUpdate
方法
監聽 chcp_updateLoadFailed
事件,判斷錯誤代碼為 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
時,調用 chcp.requestApplicationUpdate
方法。
var app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind any events that are required.
// Usually you should subscribe on ‘deviceready‘ event to know, when you can start calling cordova modules
bindEvents: function() {
document.addEventListener(‘deviceready‘, this.onDeviceReady, false);
document.addEventListener(‘chcp_updateLoadFailed‘, this.onUpdateLoadError, false);
},
// deviceready Event Handler
onDeviceReady: function() {
},
onUpdateLoadError: function(eventData) {
var error = eventData.detail.error;
if (error && error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {
console.log(‘Native side update required‘);
var dialogMessage = ‘New version of the application is available on the store. Please, update.‘;
chcp.requestApplicationUpdate(dialogMessage, this.userWentToStoreCallback, this.userDeclinedRedirectCallback);
}
},
userWentToStoreCallback: function() {
// user went to the store from the dialog
},
userDeclinedRedirectCallback: function() {
// User didn‘t want to leave the app.
// Maybe he will update later.
}
};
app.initialize();
Usage Limitations
1、Don’t rename/delete/move your index page
Cordova 項目 config.xml
文件中都會定義一個入口頁面 index.html
:
<content src="index.html" />
應用啟動的時候,就會加載 index.html
頁面作為入口,在代碼熱更新過程中,這是唯一不能刪除,移動和重命名的文件,否則,代碼熱更新後,應用就無法正常加載到 index.html
入口頁面,所以會出錯。
誠然,如果你需要重命名,或者修改其存儲路徑,那麽需要在 config.xml
文件中修改 content
配置。
2、Do not clean plugin’s inner preferences with cordova-plugin-nativestorage
cordova-plugin-nativestorage 插件提供了讀寫本地存儲數據的能力,例如在 iOS 中對應的本地存儲是 NSUserDefault
,CHCP 熱更新插件在其中存儲了一些屬性。
調用 cordova-plugin-nativestorage 插件中的 NativeStorage.clear()
方法會清除本地存儲數據,這就會影響到 CHCP 插件的正常運行,導致下一次應用啟動時加載的是應用 bundle 中 www
目錄中的網頁內容,而非外部目錄存儲的當前版本網頁內容。
將 www
目錄打包上傳到服務器或者雲存儲目錄
新版本發布時,都需要執行如下處理:
- 對
www
目錄下的靜態文件進行打包,包括代碼壓縮,合並等等 - 執行
cordova-hcp build
生成chcp.json
和chcp.manifest
文件 - 將
www
目錄下的靜態文件上傳至服務器或者雲存儲目錄
問題總結:
1 ERROR: Plugin ‘HotCodePush‘ not found
The problem is resolved. Looking at forums, below are the correct steps
1 Run cordova plugin add cordova-hot-code-push-plugin
in the project
2 Install npm install -g cordova-hot-code-push-cli
on the system
3 Run cordova-hcp init
and enter details
4 Run cordova-hcp build
5 Add the following in config.xml
<chcp>
<config-file url="https://example.com/chcp.json"/>
</chcp>
Run cordova build
2 創建模版cordova-hcp.json,放在項目根目錄下,避免每次修改chcp.json文件
{
"name": "wps-*****",
"content_url": "https://kss.ksyun.com/*****/*****/",
"ios_identifier": "326CN*****",
"android_identifier": "com.**********.*****.*****.*****.*****",
"update": "resume",
"release": "2017.06.07-16.30.20",
"min_native_interface": 1
}
之後每次修改代碼後,在項目根目錄運行 cordova-hcp build
就可以在www目錄生成跟模版一樣的chcp.json,只是release
時間不一樣
3 補充,手動更新
以上教程的熱更新屬於自動化的,在用戶完全不知情的情況下,於是就有了可選擇更新的需求:
<chcp>
<auto-download enabled="false" />
<auto-install enabled="false" />
<config-file url="http://120.24.77.175:8080/ehospital/views/MSUI/chcp.json" />
</chcp>
在config.xml改成以上配置,取消自動下載,取消自動安裝,然後通過插件提供的jsAPI進行手動更新,以下在deviceready事件觸發中示例(僅供參考):
document.addEventListener(‘deviceready‘, () => {
let chcp = window.chcp;
// 檢測更新
chcp.fetchUpdate((error, data) => {
// 表示沒有更新版本,或者其他錯誤,詳情的信息參考上面的chcp error鏈接
if (error) {
console.log(‘--fetchUpdate error--‘, error.code, error.description);
return;
}
// 這次更新的版本信息
console.log(‘--fetchUpdate--‘, data, data.config);
// 檢測是否是否可以進行安裝了,雙重判斷吧,有時候會出現有更新版本但是暫時無法安裝的情況(也可以去掉這一層)
chcp.isUpdateAvailableForInstallation((error, data) => {
if (error) {
console.log(‘No update was loaded => nothing to install‘);
} else {
// 詢問用戶是否更新
if ( window.confirm(‘檢測到新版本,是否更新‘) ) {
// 更新中
chcp.installUpdate((error) => {
if (error) {
// 更新失敗
console.log(‘Failed to install the update with error code: ‘ + error.code);
console.log(error.description);
} else {
// 更新成功
console.log(‘Update installed!‘);
}
});
} else {
window.alert(‘您已拒絕更新‘);
}
// 對比版本號
console.log(‘Current content version: ‘ + data.currentVersion);
console.log(‘Ready to be installed:‘ + data.readyToInstallVersion);
}
});
});
});
#Cordova Hot Code Push
Cordova 代碼熱更新 - 簡書