需要说明的是,ios已经申明禁止app中包含热更新插件。
2017年6月,AppStore审核团队针对AppStore中“热更新”的App开发者发送邮件,要求移除所有相关的代码、框架或SDK,并重新提交审核,否则就会在AppStore中下架该软件。
一、安装
npm install -g cordova-hot-code-push-cli
ionic cordova plugin add cordova-hot-code-push-plugin
网上还有教程建议安装本地测试插件,这里并没有使用。
ionic plugin add cordova-hot-code-push-local-dev-addon
二、配置config.xml
在widget闭合区域最底下,加上下面代码
<chcp>
<native-interface version="1" /> # 你的app 的当前版本
<auto-download enabled="false" />#默认是true,自动下载
<auto-install enabled="true" />#默认是true,下载完成后自动安装,就是下载完成之后立即重新加载页面
<config-file url="http://{你自己的服务器地址}/hotcode/chcp.json" /> #你服务器上面的地址
</chcp>
说明:
一般情况下,auto-download
和 auto-install
是设成false的,然后通过代码的方式调用,比如在设置功能中的检查更新,或者在应用启动后,如果有新的版本,给用户一个提示,是否安装新的更新(这个时候已经把改变的文件下载到本地了,插件会复制一份当前使用的 www 文件,然后把下载的增量文件复制到 www 中,然后把 webview 的地址指向这个新的)
三、项目根目录下新建两个文件: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的外壳版本;来判断当前app是直接下载web静态文件还是需要下载app进行外壳更新
"update": "now",
// start(app启动的时候触发, 默认是start);resume(app从后台切换回来的时候触发);now (web内容下载完毕)
"content_url": "http://www.abc.com/eapp/www"//配置你服务器的地址 用于后续 app触发更新时 和服务器上的文件进行比对 和下载更新用
}
四、生成热更新版本信息文件
corodva-hcp build
执行完上面命令,会在www
目录下生成两个文件:chcp.json
(当前版本信息)、chcp.manifest
(当前版本,所有文件清单,每次更改文件执行完本命令,都会更新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
,则执行热更新;如果release、min_native_interface
同时改变,则执行大版本更新。
五、将文件上传到服务器
http://www.abc.com/eapp/www
对应上面config.xml、cordova-hcp.json中的路径
注:每次打包时,必须先执行cordova-hcp build,然后再执行打包命令,保证最终apk中热更新版本信息和www中一致
六、热更新web文件和自动检测版本,下载新版本安装
创建热更新服务,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;
// 当检测出内核版本过小
if (error && error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {
// iOS端 直接弹窗提示升级,点击ok后自动跳转
if (this.plat.is('ios')) {
chcp.requestApplicationUpdate("有新的版本,请下载更新", this.userWentToStoreCallback, this.userDeclinedRedirectCallback);
} else if (this.plat.is('android')) {
let alert = this.alertCtrl.create({
title: '有新的版本,请下载更新',
enableBackdropDismiss: false,
// message: '发现新版本,是否下载新版本',
buttons: [
{
text: '下次再说',
role: 'cancel',
handler: () => {
}
},
{
text: '立即升级',
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) => {
// 打开下载下来的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('创建目录失败');
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('----------进入更新模块---------');
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);
// /!*插件开始在外部存储上安装应用程序资产之前立即调度事件*!/
// document.addEventListener('chcp_beforeAssetsInstalledOnExternalStorage', function (eventData) {
// alert('chcp_beforeAssetsInstalledOnExternalStorage');
// }, false);
// /!*插件无法拷贝app内置的web内容到外置存储中时触发. *!/
// document.addEventListener('chcp_assetsInstallationError', function (eventData) {
// alert('chcp_assetsInstallationError');
// }, false);
// document.addEventListener('chcp_assetsInstalledOnExternalStorage', function (eventData) {
// alert('chcp_assetsInstalledOnExternalStorage');
// }, false);
// /!*web内容已经下载并可以安装时触发.*!/
// 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);
}
//检测更新后的回调
fetchUpdateCallback(error, data) {
if (error) {
// alert('Failed to load the update with error code: ' + error.code);
} else {
chcp.installUpdate(this.installationCallback);
}
}
//安装后回调
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内容,具体原因不清楚。
亲测结果:
如果alertLoad()在addEventListener监听中被触发执行,则setContent无效;
如果不是在监听中被触发执行,则setContent有效;
this.LoadingProgress.setContent("正在下载:" + this.downloadProgress + "%");
app.component.ts中调用
//版本更新
new AppUpdateProvider(alertCtrl, fileOpener, file, platform, loading);
到这里就完工了。。。 。。。
(特别注意)
1、Android8以后 app自己下载的apk是需要用户信任;建议加上这个
<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自安装的时候会报错打不开
需要修改platform/android/mainfest.xml中修改uses-sdk的值,其中android:targetSdkVersion最大 值不能超过23,否则会出错.
<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",
测试过程,如果直接线上做测试,文件上传替换可能会有些麻烦,建议在手机设置代理,结合局域网站点host配置,做本地测试。
别忘了iis中mime添加类型:.json|application/x-javascript .apk|application/vnd.android.package-archive
手机端用域名访问局域网站点
可用事件
chcp_updateIsReadyToInstall - web内容已经下载并可以安装时触发.
chcp_updateLoadFailed - 插件无法下载web更新时触发. 详细错误信息在事件参数里.
chcp_nothingToUpdate - 无可用更新下载时触发.
chcp_updateInstalled - web内容安装成功时触发.
chcp_updateInstallFailed - web内容安装失败时触发. 详细错误信息在事件参数里.
chcp_nothingToInstall -无可用更新安装时触发.
chcp_assetsInstalledOnExternalStorage - 插件成功把app内置的web内容拷贝到外置存储中时触发. 你可能需要开发调试时用到这个事件,也许不会.
chcp_assetsInstallationError -插件无法拷贝app内置的web内容到外置存储中时触发. 如果此事件发生了 - 插件不再工作. 也许是设备没有足够的存储空间导致. 详细错误信息在事件参数里.
事件监听
document.addEventListener("chcp_updateLoadFailed",(event:any)={... ...},false);
参考文档
ionic3 热更新 填坑过程
【Ionic】Ionic实现iOS与Android端代码『热更新』与android升级下载功能 ( v1.3.x版本 )
Cordova Hot Code Push插件实现自动更新App的Web内容