ionic3热更新详细步骤

需要说明的是,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-downloadauto-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内容

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容