最近公司做了手機(jī)app需要使用到熱更新,先對(duì)熱更新進(jìn)行一個(gè)簡(jiǎn)單的介紹吧;`
熱更新
熱更新是一種app的常用更新方式。簡(jiǎn)單說(shuō)就是當(dāng)你的手機(jī)上已經(jīng)用了app之后,打開app的時(shí)候及時(shí)更新。
對(duì)于熱更新的重大打擊
2017年6月,AppStore審核團(tuán)隊(duì)針對(duì)AppStore中“熱更新”的App開發(fā)者發(fā)送郵件,要求移除所有相關(guān)的代碼、框架或SDK,并重新提交審核,否則就會(huì)在AppStore中下架該軟件。
該文章主要是分享基于 ionic3 的熱更新
第一步 安裝插件
安裝熱更新cli
npm install -g cordova-hot-code-push-cli
安裝熱更新插件
ionic cordova plugin add cordova-hot-code-push-plugin
第二步 打包編譯代代碼
一、在項(xiàng)目根目錄 創(chuàng)建一個(gè) cordova-hcp.json

解釋一下每個(gè)key的意思 :
1.autogenerated自動(dòng)生成的意思? 默認(rèn)是 true
2. update 熱更新的觸發(fā)方式 目前有三種?
? ? start(app啟動(dòng)的時(shí)候觸發(fā), 默認(rèn)是start);
? ? resume(app從后臺(tái)切換回來(lái)的時(shí)候觸發(fā));
? ? now (web內(nèi)容下載完畢)
3. min_native_interface 用于控制app的外殼版本;來(lái)判斷當(dāng)前app是直接內(nèi)殼更新還是 需要下載app進(jìn)行外殼更新(下面會(huì)有詳細(xì)的介紹)
4.content_url 來(lái)配置你服務(wù)器的地址 用于后續(xù) app觸發(fā)更新時(shí) 和服務(wù)器上的文件進(jìn)行比對(duì) 和下載更新用(下面也會(huì)有詳細(xì)的介紹)
二、配置config.xml
基本配置
? ? <chcp>
? ? <native-interface version="5" />? # 你的app 的當(dāng)前版本
? ? <config-file url="http://{你自己的服務(wù)器地址}/hotcode/chcp.json" /> #你服務(wù)器上面的地址
? ?</chcp>
自動(dòng)下載(默認(rèn)是true 只要觸發(fā)了就好自動(dòng)下載)
<chcp>
<auto-download enabled="false" />
?</chcp>
改成false 可以通過(guò)代碼去觸發(fā) 更新下載
自動(dòng)安裝(默認(rèn)是true 只要下載好了就會(huì)安裝)
<chcp>
<auto-install enabled="false" />
?</chcp>
改成false 可以通過(guò)代碼去觸發(fā) 安裝
可用事件
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ù)里.
三、corodva-hcp build
根目錄下的www 會(huì)生成兩個(gè)文件 chcp.json文件 和chcp.manifest文件

每次運(yùn)行corodva-hcp build? ?chcp.json? 都會(huì)更根據(jù)?cordova-hcp.json 文件的配置進(jìn)行更新? release是當(dāng)時(shí)運(yùn)行的時(shí)間戳(下面會(huì)詳細(xì)說(shuō)release的用途)
四、配置 content_url?
這里就需要自己去配置 我就以我的情況來(lái)簡(jiǎn)單說(shuō)明一下,我有個(gè)遠(yuǎn)程服務(wù)器 在該服務(wù)器上安裝了nginx,我配置了一個(gè)http://{你的服務(wù)器地址}/hotcode 的路徑 然后我把根目錄下的www你們的文件拷貝到服務(wù)器上

五、打包一個(gè)apk 安裝到手機(jī)
這樣基本配置就ok了?
第三步 測(cè)試熱更新
先介紹一下熱更新的更新機(jī)制我通常分為外殼更新和內(nèi)殼更新
外殼更新就是 當(dāng)app添加新的插件和配置的時(shí)候無(wú)法通過(guò)更新html、js、css來(lái)實(shí)現(xiàn)的需要使用外殼更新 下載新的app來(lái)覆蓋安裝
內(nèi)殼更新就是 app內(nèi)部做的樣式圖片代碼邏輯bug的修復(fù)更新可以直接推送到手機(jī)上進(jìn)行實(shí)時(shí)更新(現(xiàn)在只能android版本使用)
先說(shuō)內(nèi)殼更新
每次運(yùn)行corodva-hcp build 之后 chcp.json? 都會(huì)更根據(jù)?cordova-hcp.json 文件的配置進(jìn)行更新? release是當(dāng)時(shí)運(yùn)行的時(shí)間戳
然后你重新編譯你的代碼?ionic-app-scripts build 生成 www文件后 同步到你配置好的服務(wù)器上
這個(gè)時(shí)候 開app
建議可以使用Android Studio連接手機(jī)來(lái)調(diào)試 如下

如果chcp.json文件中min_native_interface 一樣 但是 release不一樣 時(shí)會(huì)判斷為內(nèi)殼更新會(huì)從服務(wù)器上開始更新代碼的你的手機(jī)上
提示:
如果沒有沒有像我圖片中的效果 有可能是你的服務(wù)器配置有問(wèn)題 先測(cè)試一下 你服務(wù)器是否能訪問(wèn),再就是可能你app中的chcp.json和服務(wù)器上min_native_interface 、release一樣 所以不用更新 log會(huì)提示

再來(lái)說(shuō)外殼更新
外殼更新主要用于你更新了新的插件和一些app配置的時(shí)候使用,需要注意的有 你app的版外殼版本就是min_native_interface,只有當(dāng)app中chcp.json的min_native_interface 比 服務(wù)器中的chcp.json中的min_native_interface小的時(shí)候 他會(huì)出發(fā)一個(gè)報(bào)錯(cuò)?chcp_updateLoadFailed ,?chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW

特別注意如果 在config.xml中 要配置 android-versionCode的版本要和 native-interface保持一致 這樣才能你下載好app安裝后 不會(huì)去自動(dòng)更新以前老的不合適的代碼;

再來(lái)說(shuō)如何去再app中觸發(fā)提示框下載新版本的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('----------進(jìn)入更新模塊---------');
? ? 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);
? ? ? // 當(dāng)檢測(cè)出內(nèi)核版本過(guò)小
? ? ? 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);
? ? /!*插件開始在外部存儲(chǔ)上安裝應(yīng)用程序資產(chǎn)之前立即調(diào)度事件*!/
document.addEventListener('chcp_beforeAssetsInstalledOnExternalStorage', function(eventData){
console.log('chcp_beforeAssetsInstalledOnExternalStorage');
alert('chcp_beforeAssetsInstalledOnExternalStorage');
}, false);
? ? /!*插件無(wú)法拷貝app內(nèi)置的web內(nèi)容到外置存儲(chǔ)中時(shí)觸發(fā). *!/
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內(nèi)容已經(jīng)下載并可以安裝時(shí)觸發(fā).*!/
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:'有新的版本,請(qǐng)下載更新',
? ? ? message:'您當(dāng)前版本為' +VERSION_NUMBER +',發(fā)現(xiàn)新版本,是否下載新版本',
? ? ? buttons:[
? ? ? ? {
text:'下次再說(shuō)',
? ? ? ? ? role:'cancel',
? ? ? ? ? handler:() => {
this.firstFlag =true;
? ? ? ? ? ? this.storage.set('isUpdate', true);
? ? ? ? ? ? console.log('Cancel clicked');
? ? ? ? ? }
},
? ? ? ? {
text:'立即升級(jí)',
? ? ? ? ? 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://{自己服務(wù)器地址}/download/{app名字}.apk",dir.nativeURL+'{自己定義}.apk', (entry) => {
// 打開下載下來(lái)的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('已經(jīng)下載:' +this.downloadProgress);
? ? };
? }
presentLoadingDefault() {
this.downloading =this.loadingCtrl.create({
content:'已經(jīng)下載:0%'
? ? });
? ? this.downloading.present();
? ? this.downloadfile(this.downloading);
? ? this.timer =setInterval(() => {
? ? ?this.downloading.setContent('已經(jīng)下載' +this.downloadProgress.toString().split('.')[0] +'%');
? ? ? this.downloading.present().then();
? ? ? if (this.downloadProgress >99) {
? ? ? ?clearInterval(this.timer);
? ? ? ? this.downloading.dismiss();
? ? ? }
}, 300)
? }
}
這個(gè)文件引入 app.module.ts 如果在調(diào)試中不會(huì)打印出console.log('----------進(jìn)入更新模塊---------'); 說(shuō)明沒有觸發(fā)這個(gè)模塊 可以試著在app.component.ts中當(dāng)app啟動(dòng)的時(shí)候主動(dòng)觸發(fā)一次
(特別注意)
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最大 值不能超過(guò)23,否則會(huì)出錯(cuò).
<uses-sdk android:minSdkVersion=”16” android:targetSdkVersion=”23” />
---------------------
非常感謝這個(gè)作者 解決了的一個(gè)大麻煩
作者:cangahi09025566
來(lái)源:CSDN
原文:https://blog.csdn.net/cangahi09025566/article/details/80322830
到這里熱更新的坑算是填了差不多了,還有很多的細(xì)節(jié)我自己也還沒全部搞明白,也是記錄我自己這幾天搗鼓的心路歷程,希望能對(duì)你有些許幫助;
最后感謝幾個(gè)作者?