需要說明的是淤年,ios已經(jīng)申明禁止app中包含熱更新插件。
2017年6月葵擎,AppStore審核團(tuán)隊(duì)針對(duì)AppStore中“熱更新”的App開發(fā)者發(fā)送郵件谅阿,要求移除所有相關(guān)的代碼、框架或SDK酬滤,并重新提交審核签餐,否則就會(huì)在AppStore中下架該軟件。
一盯串、安裝
npm install -g cordova-hot-code-push-cli
ionic cordova plugin add cordova-hot-code-push-plugin
網(wǎng)上還有教程建議安裝本地測(cè)試插件氯檐,這里并沒有使用。
ionic plugin add cordova-hot-code-push-local-dev-addon
二体捏、配置config.xml
在widget閉合區(qū)域最底下冠摄,加上下面代碼
<chcp>
<native-interface version="1" /> # 你的app 的當(dāng)前版本
<auto-download enabled="false" />#默認(rèn)是true,自動(dòng)下載
<auto-install enabled="true" />#默認(rèn)是true几缭,下載完成后自動(dòng)安裝,就是下載完成之后立即重新加載頁(yè)面
<config-file url="http://{你自己的服務(wù)器地址}/hotcode/chcp.json" /> #你服務(wù)器上面的地址
</chcp>
說明:
一般情況下河泳,auto-download
和 auto-install
是設(shè)成false的,然后通過代碼的方式調(diào)用年栓,比如在設(shè)置功能中的檢查更新拆挥,或者在應(yīng)用啟動(dòng)后,如果有新的版本某抓,給用戶一個(gè)提示纸兔,是否安裝新的更新(這個(gè)時(shí)候已經(jīng)把改變的文件下載到本地了惰瓜,插件會(huì)復(fù)制一份當(dāng)前使用的 www 文件,然后把下載的增量文件復(fù)制到 www 中汉矿,然后把 webview 的地址指向這個(gè)新的)
三崎坊、項(xiàng)目根目錄下新建兩個(gè)文件:chcpbuild.options、cordova-hcp.json
chcpbuild.options
{
"dev": {
"config-file": "http://www.abc.com/eapp/www/chcp.json"
},
"production": {
"config-file": "http://www.abc.com/eapp/www/chcp.json"
},
"QA": {
"config-file": "http://www.abc.com/eapp/www/chcp.json"
}
}
cordova-hcp.json
{
"name": "App名字",
"android_identifier":"",
"ios_identifier": "",
"min_native_interface": 1,//用于控制app的外殼版本负甸;來(lái)判斷當(dāng)前app是直接下載web靜態(tài)文件還是需要下載app進(jìn)行外殼更新
"update": "now",
// start(app啟動(dòng)的時(shí)候觸發(fā)流强, 默認(rèn)是start);resume(app從后臺(tái)切換回來(lái)的時(shí)候觸發(fā))呻待;now (web內(nèi)容下載完畢)
"content_url": "http://www.abc.com/eapp/www"http://配置你服務(wù)器的地址 用于后續(xù) app觸發(fā)更新時(shí) 和服務(wù)器上的文件進(jìn)行比對(duì) 和下載更新用
}
四、生成熱更新版本信息文件
corodva-hcp build
執(zhí)行完上面命令队腐,會(huì)在www
目錄下生成兩個(gè)文件:chcp.json
(當(dāng)前版本信息)蚕捉、chcp.manifest
(當(dāng)前版本,所有文件清單柴淘,每次更改文件執(zhí)行完本命令迫淹,都會(huì)更新hash字符串)
{
"name": "App名字",
"android_identifier":"",
"ios_identifier": "",
"min_native_interface": 1,
"update": "now",
"content_url": "http://www.abc.com/eapp/www",
"release":"2018.04.26-15.20.54"
}
如果僅改變release
,則執(zhí)行熱更新为严;如果release敛熬、min_native_interface
同時(shí)改變,則執(zhí)行大版本更新第股。
五应民、將文件上傳到服務(wù)器
http://www.abc.com/eapp/www
對(duì)應(yīng)上面config.xml、cordova-hcp.json中的路徑
注:每次打包時(shí)夕吻,必須先執(zhí)行cordova-hcp build诲锹,然后再執(zhí)行打包命令,保證最終apk中熱更新版本信息和www中一致
六涉馅、熱更新web文件和自動(dòng)檢測(cè)版本归园,下載新版本安裝
創(chuàng)建熱更新服務(wù),app-update.ts
import { Injectable } from '@angular/core';
import { AlertController, LoadingController } from 'ionic-angular';
import { File } from '@ionic-native/file';
import { FileOpener } from '@ionic-native/file-opener';
import { Platform } from 'ionic-angular';
declare var FileTransfer: any;
declare var cordova: any;
declare var chcp: any;
@Injectable()
export class AppUpdateProvider {
public downloadProgress = 0;
public LoadingProgress;
constructor(private alertCtrl: AlertController, public fileOpener: FileOpener, public file: File, public plat: Platform, private loading: LoadingController) {
this.bindEvents();
}
updateLoadFailed(eventData: any) {
const error = eventData.detail.error;
// 當(dāng)檢測(cè)出內(nèi)核版本過小
if (error && error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {
// iOS端 直接彈窗提示升級(jí)稚矿,點(diǎn)擊ok后自動(dòng)跳轉(zhuǎn)
if (this.plat.is('ios')) {
chcp.requestApplicationUpdate("有新的版本,請(qǐng)下載更新", this.userWentToStoreCallback, this.userDeclinedRedirectCallback);
} else if (this.plat.is('android')) {
let alert = this.alertCtrl.create({
title: '有新的版本,請(qǐng)下載更新',
enableBackdropDismiss: false,
// message: '發(fā)現(xiàn)新版本,是否下載新版本',
buttons: [
{
text: '下次再說',
role: 'cancel',
handler: () => {
}
},
{
text: '立即升級(jí)',
handler: () => {
alert.dismiss().then(() => {
this.alertLoad();
});
}
}
]
});
alert.present().then();
}
} else {
// alert('是新版本');
}
}
alertLoad() {
this.LoadingProgress = this.loading.create({
content: "正在下載:" + this.downloadProgress + "%"
});
this.LoadingProgress.present();
this.downloadfile();
}
downloadfile() {
//下載代碼
var fileTransfer = new FileTransfer();
const fs: string = cordova.file.externalRootDirectory;
this.file.createDir(fs, 'eschool', true).then((dir: any) => {
fileTransfer.download("http://www.abc.com/eapp/apk/123.apk", dir.nativeURL + '123.apk', (entry) => {
// 打開下載下來(lái)的APP
this.fileOpener.open(entry.toURL(), 'application/vnd.android.package-archive')
.then((data: any) => {
// console.log('open file success');
})
.catch(err => {
// console.log('open file error' + err);
console.log('打開安裝包失斢褂铡!');
});
}, function (err) {
console.log('下載失敗');
this.LoadingProgress.dismiss();
}, true);
}).catch(err => {
console.log('創(chuàng)建目錄失敗');
this.LoadingProgress.dismiss();
});
fileTransfer.onprogress = (progressEvent) => {
this.downloadProgress = Math.floor((progressEvent.loaded / progressEvent.total) * 100);
};
let timer = window.setInterval(() => {
// this.LoadingProgress.setContent("正在下載:" + this.downloadProgress + "%");
window.setInterval(() => {
let loadingcontent = this.LoadingProgress.pageRef().nativeElement.querySelector(".loading-content");
loadingcontent.innerHTML = "正在下載:" + this.downloadProgress + "%"
}, 300);
if (this.downloadProgress > 99) {
window.clearInterval(timer);
this.LoadingProgress.dismiss();
}
}, 1000);
}
bindEvents() {
console.log('----------進(jìn)入更新模塊---------');
document.addEventListener('chcp_updateLoadFailed', (eventData: any) => {
this.updateLoadFailed(eventData)
}, false);
document.addEventListener('deviceready', () => {
chcp.fetchUpdate(this.fetchUpdateCallback);
}, false);
// //沒有更新
// document.addEventListener('chcp_nothingToUpdate', function (eventData) {
// alert('是新版本');
// }, false);
// /!*插件開始在外部存儲(chǔ)上安裝應(yīng)用程序資產(chǎn)之前立即調(diào)度事件*!/
// document.addEventListener('chcp_beforeAssetsInstalledOnExternalStorage', function (eventData) {
// alert('chcp_beforeAssetsInstalledOnExternalStorage');
// }, false);
// /!*插件無(wú)法拷貝app內(nèi)置的web內(nèi)容到外置存儲(chǔ)中時(shí)觸發(fā). *!/
// document.addEventListener('chcp_assetsInstallationError', function (eventData) {
// alert('chcp_assetsInstallationError');
// }, false);
// document.addEventListener('chcp_assetsInstalledOnExternalStorage', function (eventData) {
// alert('chcp_assetsInstalledOnExternalStorage');
// }, false);
// /!*web內(nèi)容已經(jīng)下載并可以安裝時(shí)觸發(fā).*!/
// document.addEventListener('chcp_updateIsReadyToInstall', function (eventData) {
// alert('chcp_updateIsReadyToInstall');
// }, false);
// document.addEventListener('chcp_beforeInstall', function (eventData) {
// alert('chcp_beforeInstall');
// }, false);
// document.addEventListener('chcp_updateInstallFailed', function (eventData) {
// alert('chcp_updateInstallFailed');
// }, false);
// document.addEventListener('chcp_updateInstalled', function (eventData) {
// alert('chcp_updateInstalled');
// }, false);
// document.addEventListener('chcp_nothingToInstall', function (eventData) {
// alert('chcp_nothingToInstall');
// }, false);
}
//檢測(cè)更新后的回調(diào)
fetchUpdateCallback(error, data) {
if (error) {
// alert('Failed to load the update with error code: ' + error.code);
} else {
chcp.installUpdate(this.installationCallback);
}
}
//安裝后回調(diào)
installationCallback(error) {
if (error) {
// alert('Failed to install the update with error code: ' + error.code);
} else {
// alert('Update installed!');
}
}
userWentToStoreCallback() {
//user went to the store from the dialog
}
userDeclinedRedirectCallback() {
// User didn't want to leave the app.
// Maybe he will update later.
}
}
注意:下載過程不可以直接使用下面代碼更新百分比晤揣,只能使用原生this.LoadingProgress.pageRef().nativeElement.querySelector獲取dom后桥爽,操作dom內(nèi)容,具體原因不清楚碉渡。
親測(cè)結(jié)果:
如果alertLoad()在addEventListener監(jiān)聽中被觸發(fā)執(zhí)行聚谁,則setContent無(wú)效;
如果不是在監(jiān)聽中被觸發(fā)執(zhí)行滞诺,則setContent有效形导;
this.LoadingProgress.setContent("正在下載:" + this.downloadProgress + "%");
app.component.ts中調(diào)用
//版本更新
new AppUpdateProvider(alertCtrl, fileOpener, file, platform, loading);
到這里就完工了环疼。。朵耕。 炫隶。。阎曹。
(特別注意)
1伪阶、Android8以后 app自己下載的apk是需要用戶信任;建議加上這個(gè)
<platform name="android">
<config-file parent="/manifest" target="AndroidManifest.xml" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
</config-file>
</platform>
2处嫌、再就是apk自安裝的時(shí)候會(huì)報(bào)錯(cuò)打不開
需要修改platform/android/mainfest.xml中修改uses-sdk的值,其中android:targetSdkVersion最大 值不能超過23,否則會(huì)出錯(cuò).
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" />
注:用到的插件清單
<plugin name="cordova-hot-code-push-plugin" spec="^1.5.3" />
<plugin name="cordova-plugin-file-opener2" spec="^2.1.4" />
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
<plugin name="cordova-plugin-device" spec="^2.0.2" />
注:用到的npm安裝包清單
"cordova-hot-code-push-plugin": "^1.5.3",
"cordova-plugin-device": "^2.0.2",
"cordova-plugin-file": "^6.0.1",
"cordova-plugin-file-opener2": "^2.1.4",
"cordova-plugin-file-transfer": "^1.7.1",
測(cè)試過程栅贴,如果直接線上做測(cè)試,文件上傳替換可能會(huì)有些麻煩熏迹,建議在手機(jī)設(shè)置代理檐薯,結(jié)合局域網(wǎng)站點(diǎn)host配置,做本地測(cè)試注暗。
別忘了iis中mime添加類型:.json|application/x-javascript ?? .apk|application/vnd.android.package-archive
手機(jī)端用域名訪問局域網(wǎng)站點(diǎn)
可用事件
chcp_updateIsReadyToInstall - web內(nèi)容已經(jīng)下載并可以安裝時(shí)觸發(fā).
chcp_updateLoadFailed - 插件無(wú)法下載web更新時(shí)觸發(fā). 詳細(xì)錯(cuò)誤信息在事件參數(shù)里.
chcp_nothingToUpdate - 無(wú)可用更新下載時(shí)觸發(fā).
chcp_updateInstalled - web內(nèi)容安裝成功時(shí)觸發(fā).
chcp_updateInstallFailed - web內(nèi)容安裝失敗時(shí)觸發(fā). 詳細(xì)錯(cuò)誤信息在事件參數(shù)里.
chcp_nothingToInstall -無(wú)可用更新安裝時(shí)觸發(fā).
chcp_assetsInstalledOnExternalStorage - 插件成功把a(bǔ)pp內(nèi)置的web內(nèi)容拷貝到外置存儲(chǔ)中時(shí)觸發(fā). 你可能需要開發(fā)調(diào)試時(shí)用到這個(gè)事件坛缕,也許不會(huì).
chcp_assetsInstallationError -插件無(wú)法拷貝app內(nèi)置的web內(nèi)容到外置存儲(chǔ)中時(shí)觸發(fā). 如果此事件發(fā)生了 - 插件不再工作. 也許是設(shè)備沒有足夠的存儲(chǔ)空間導(dǎo)致. 詳細(xì)錯(cuò)誤信息在事件參數(shù)里.
事件監(jiān)聽
document.addEventListener("chcp_updateLoadFailed",(event:any)={... ...},false);
參考文檔
ionic3 熱更新 填坑過程
【Ionic】Ionic實(shí)現(xiàn)iOS與Android端代碼『熱更新』與android升級(jí)下載功能 ( v1.3.x版本 )
Cordova Hot Code Push插件實(shí)現(xiàn)自動(dòng)更新App的Web內(nèi)容