最近公司做了手机app需要使用到热更新,先对热更新进行一个简单的介绍吧;`
热更新
热更新是一种app的常用更新方式。简单说就是当你的手机上已经用了app之后,打开app的时候及时更新。
对于热更新的重大打击
2017年6月,AppStore审核团队针对AppStore中“热更新”的App开发者发送邮件,要求移除所有相关的代码、框架或SDK,并重新提交审核,否则就会在AppStore中下架该软件。
该文章主要是分享基于 ionic3 的热更新
第一步 安装插件
安装热更新cli
npm install -g cordova-hot-code-push-cli
安装热更新插件
ionic cordova plugin add cordova-hot-code-push-plugin
第二步 打包编译代代码
一、在项目根目录 创建一个 cordova-hcp.json
解释一下每个key的意思 :
1.autogenerated自动生成的意思 默认是 true
2. update 热更新的触发方式 目前有三种
start(app启动的时候触发, 默认是start);
resume(app从后台切换回来的时候触发);
now (web内容下载完毕)
3. min_native_interface 用于控制app的外壳版本;来判断当前app是直接内壳更新还是 需要下载app进行外壳更新(下面会有详细的介绍)
4.content_url 来配置你服务器的地址 用于后续 app触发更新时 和服务器上的文件进行比对 和下载更新用(下面也会有详细的介绍)
二、配置config.xml
基本配置
<chcp>
<native-interface version="5" /> # 你的app 的当前版本
<config-file url="http://{你自己的服务器地址}/hotcode/chcp.json" /> #你服务器上面的地址
</chcp>
自动下载(默认是true 只要触发了就好自动下载)
<chcp>
<auto-download enabled="false" />
</chcp>
改成false 可以通过代码去触发 更新下载
自动安装(默认是true 只要下载好了就会安装)
<chcp>
<auto-install enabled="false" />
</chcp>
改成false 可以通过代码去触发 安装
可用事件
chcp_updateIsReadyToInstall - web内容已经下载并可以安装时触发.
chcp_updateLoadFailed - 插件无法下载web更新时触发. 详细错误信息在事件参数里.
chcp_nothingToUpdate - 无可用更新下载时触发.
chcp_updateInstalled - web内容安装成功时触发.
chcp_updateInstallFailed - web内容安装失败时触发. 详细错误信息在事件参数里.
chcp_nothingToInstall -无可用更新安装时触发.
chcp_assetsInstalledOnExternalStorage - 插件成功把app内置的web内容拷贝到外置存储中时触发. 你可能需要开发调试时用到这个事件,也许不会.
chcp_assetsInstallationError -插件无法拷贝app内置的web内容到外置存储中时触发. 如果此事件发生了 - 插件不再工作. 也许是设备没有足够的存储空间导致. 详细错误信息在事件参数里.
三、corodva-hcp build
根目录下的www 会生成两个文件 chcp.json文件 和chcp.manifest文件
每次运行corodva-hcp build chcp.json 都会更根据 cordova-hcp.json 文件的配置进行更新 release是当时运行的时间戳(下面会详细说release的用途)
四、配置 content_url
这里就需要自己去配置 我就以我的情况来简单说明一下,我有个远程服务器 在该服务器上安装了nginx,我配置了一个http://{你的服务器地址}/hotcode 的路径 然后我把根目录下的www你们的文件拷贝到服务器上
五、打包一个apk 安装到手机
这样基本配置就ok了
第三步 测试热更新
先介绍一下热更新的更新机制我通常分为外壳更新和内壳更新
外壳更新就是 当app添加新的插件和配置的时候无法通过更新html、js、css来实现的需要使用外壳更新 下载新的app来覆盖安装
内壳更新就是 app内部做的样式图片代码逻辑bug的修复更新可以直接推送到手机上进行实时更新(现在只能android版本使用)
先说内壳更新
每次运行corodva-hcp build 之后 chcp.json 都会更根据 cordova-hcp.json 文件的配置进行更新 release是当时运行的时间戳
然后你重新编译你的代码 ionic-app-scripts build 生成 www文件后 同步到你配置好的服务器上
这个时候 开app
建议可以使用Android Studio连接手机来调试 如下
如果chcp.json文件中min_native_interface 一样 但是 release不一样 时会判断为内壳更新会从服务器上开始更新代码的你的手机上
提示:
如果没有没有像我图片中的效果 有可能是你的服务器配置有问题 先测试一下 你服务器是否能访问,再就是可能你app中的chcp.json和服务器上min_native_interface 、release一样 所以不用更新 log会提示
再来说外壳更新
外壳更新主要用于你更新了新的插件和一些app配置的时候使用,需要注意的有 你app的版外壳版本就是min_native_interface,只有当app中chcp.json的min_native_interface 比 服务器中的chcp.json中的min_native_interface小的时候 他会出发一个报错 chcp_updateLoadFailed , chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
特别注意如果 在config.xml中 要配置 android-versionCode的版本要和 native-interface保持一致 这样才能你下载好app安装后 不会去自动更新以前老的不合适的代码;
再来说如何去再app中触发提示框下载新版本的app让用户安装,下面是源码
import {Injectable }from '@angular/core';
import {File }from '@ionic-native/file';
import {FileOpener }from '@ionic-native/file-opener';
import {AlertController, LoadingController }from 'ionic-angular';
import {VERSION_NUMBER}from '../providers';
declare var FileTransfer:any;
declare var cordova:any;
declare var chcp:any;
@Injectable()
export class AppUpdate {
public downloading;
public firstFlag =true;
public timer;
public downloadProgress =0;
constructor(private alertCtrl:AlertController,
public loadingCtrl:LoadingController,
public fileOpener:FileOpener,
public file:File
) {
this.storage.set('isUpdate', false);
this.bindEvents();
}
bindEvents() {
console.log('----------进入更新模块---------');
document.addEventListener('deviceready', () => {
console.log('onDeviceReady');
}, false);
document.addEventListener('chcp_updateLoadFailed', (eventData:any) => {
console.log('chcp_updateLoadFailed');
const error =eventData.detail.error;
console.log('123' +error.code +',' +chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW);
// 当检测出内核版本过小
if (error &&error.code ==chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {
if (this.firstFlag) {
this.firstFlag =false;
// 提示
this.alertUpdate();
}
}else {
console.log('是新版本');
}
}, false);
//没有更新
document.addEventListener('chcp_nothingToUpdate', function(eventData){
console.log('chcp_nothingToUpdate');
alert('是新版本');
}, false);
document.addEventListener('chcp_nothingToUpdate', function(eventData){
console.log('chcp_nothingToUpdate');
alert('chcp_nothingToUpdate');
}, false);
/!*插件开始在外部存储上安装应用程序资产之前立即调度事件*!/
document.addEventListener('chcp_beforeAssetsInstalledOnExternalStorage', function(eventData){
console.log('chcp_beforeAssetsInstalledOnExternalStorage');
alert('chcp_beforeAssetsInstalledOnExternalStorage');
}, false);
/!*插件无法拷贝app内置的web内容到外置存储中时触发. *!/
document.addEventListener('chcp_assetsInstallationError', function(eventData){
console.log('chcp_assetsInstallationError');
alert('chcp_assetsInstallationError');
}, false);
document.addEventListener('chcp_assetsInstalledOnExternalStorage', function(eventData){
console.log('chcp_assetsInstalledOnExternalStorage');
alert('chcp_assetsInstalledOnExternalStorage');
}, false);
/!*web内容已经下载并可以安装时触发.*!/
document.addEventListener('chcp_updateIsReadyToInstall', function(eventData){
console.log('chcp_updateIsReadyToInstall');
alert('chcp_updateIsReadyToInstall');
}, false);
document.addEventListener('chcp_beforeInstall', function(eventData){
console.log('chcp_beforeInstall');
alert('chcp_beforeInstall');
}, false);
document.addEventListener('chcp_updateInstallFailed', function(eventData){
console.log('chcp_updateInstallFailed');
alert('chcp_updateInstallFailed');
}, false);
document.addEventListener('chcp_updateInstalled', function(eventData){
console.log('chcp_updateInstalled');
alert('chcp_updateInstalled');
}, false);
document.addEventListener('chcp_nothingToInstall', function(eventData){
console.log('chcp_nothingToInstall');
alert('chcp_nothingToInstall');
}, false);
}
// 提示安装
alertUpdate() {
let alert =this.alertCtrl.create({
title:'有新的版本,请下载更新',
message:'您当前版本为' +VERSION_NUMBER +',发现新版本,是否下载新版本',
buttons:[
{
text:'下次再说',
role:'cancel',
handler:() => {
this.firstFlag =true;
this.storage.set('isUpdate', true);
console.log('Cancel clicked');
}
},
{
text:'立即升级',
handler:() => {
this.firstFlag =true;
console.log('Update App');
this.presentLoadingDefault();
}
}
]
});
alert.present().then();
}
userWentToStoreCallback() {
//user went to the store from the dialog
}
userDeclinedRedirectCallback() {
// User didn't want to leave the app.
// Maybe he will update later.
}
downloadfile(loading) {
console.log('downloadfile');
//下载代码
var fileTransfer =new FileTransfer();
const fs:string =cordova.file.externalRootDirectory ;
this.file.createDir(fs,'fawo',true).then((dir:any) =>{
console.log('create dir success'+JSON.stringify(dir));
fileTransfer.download("http://{自己服务器地址}/download/{app名字}.apk",dir.nativeURL+'{自己定义}.apk', (entry) => {
// 打开下载下来的APP
this.fileOpener.open(
dir.nativeURL+'自己定义.apk',//下载文件保存地址
'application/vnd.android.package-archive').then((data:any) => {
console.log('open file success');
}).catch(err =>{
console.log('open file error' +err);
alert('打开安装包失败!');
});
}, function(err) {
console.log('下载失败'+JSON.stringify(err));
alert('下载失败');
loading.dismiss();
},true);
}).catch(err =>{
console.log('create dir err'+err);
});
fileTransfer.onprogress =(progressEvent) => {
this.downloadProgress =(progressEvent.loaded /progressEvent.total) *100;
console.log('已经下载:' +this.downloadProgress);
};
}
presentLoadingDefault() {
this.downloading =this.loadingCtrl.create({
content:'已经下载:0%'
});
this.downloading.present();
this.downloadfile(this.downloading);
this.timer =setInterval(() => {
this.downloading.setContent('已经下载' +this.downloadProgress.toString().split('.')[0] +'%');
this.downloading.present().then();
if (this.downloadProgress >99) {
clearInterval(this.timer);
this.downloading.dismiss();
}
}, 300)
}
}
这个文件引入 app.module.ts 如果在调试中不会打印出console.log('----------进入更新模块---------'); 说明没有触发这个模块 可以试着在app.component.ts中当app启动的时候主动触发一次
(特别注意)
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” />
---------------------
非常感谢这个作者 解决了的一个大麻烦
作者:cangahi09025566
来源:CSDN
原文:https://blog.csdn.net/cangahi09025566/article/details/80322830
到这里热更新的坑算是填了差不多了,还有很多的细节我自己也还没全部搞明白,也是记录我自己这几天捣鼓的心路历程,希望能对你有些许帮助;
最后感谢几个作者