ionic热更新的实现以及引导用户下载新版应用功能实现和注意事项

在app上线的第一个版本中,就需要加入热更新和外壳更新功能,这样才能尽可能避免损失用户,热更新无需新的apk,直接更新web项目,而外壳更新,通过给用户友好提示,引导用户下载最新版本。两者结合其实是一个功能,两者缺一不可。
建议通读全文之后,再进行实践。

热更新实现步骤

  • 全局安装cordova-hot-code-push-cli插件:$npm install -g cordova-hot-code-push-cli

  • 在项目根目录下安装cordova-hot-code-push-plugin插件:$cordova plugin add cordova-hot-code-push-plugin

  • 在项目根目录下执行 $cordova-hcp server , 然后ctrl / command + z 结束服务器,我们不使用它的服务器来测试。只为了使用它生成的一个配置文件,名为:.chcpenv

  • 好的,我们现在可以开启一个本地服务器,随便找一个地方新建一个文件夹,然后在文件夹根目录下执行 $hs -o -p 1111, (备注,hs命令是全局安装了http-server之后才能使用,$npm i -g http-server), 这样我们可以通过访问 http://127.0.0.1:1111 来访问我们这个本地服务器了。我们在目录中建立几个文件夹目录结构如下:

    /update/hot/app1
    /update/hot/app2
    /update/apks

    说明,app1是你的一个项目用于放置热更新代码的文件夹,而app2则是另一个项目的文件夹,这里假设我们有很多个移动应用项目,而apks文件夹则用于放置我们所有应用的新版apk。(备注:这里的app1和下面用到的app1.apk都只做示例。)

  • 查看电脑ip地址,mac上使用ifconfig命令, windows上使用ipconfig命令,找到我们的电脑ip地址,比如是192.168.1.102, 此处 192.168.1.102127.0.0.1是等同的,但为了手机可访问必须使用192的地址。(手机和电脑需保持在同一个网段上)

  • 开始修改上面的.chcpenv文件,编辑如下:

    {
      "content_url": "http://192.168.1.102:1111/update/hot/app1",
      "config_url": "http://192.168.1.102:1111/update/hot/app1/chcp.json"
    }
    

    说明: 其中app1文件夹里的代码就是你将要放置的热更新后的代码,也就是我们本地项目中的www目录的代码,同样的,www目录中存在chcp.json文件,这个我们即将说道。

  • 我们在项目中新建一个cordova-hcp.json文件,这个文件是生成配置文件的模板,编辑如下:

    {
        "autogenerated": true,
        "content_url": "http://192.168.1.102:1111/update/hot/app1",
        "min_native_interface": 1,
        "update": "now",
        "ios_identifier": ""
    }
    
  • 在项目根目录执行$cordova-hcp build, 这样就会按照上一步的cordova-hcp.json文件为模板在www目录中生成两个文件,chcp.json 和 chcp.manifest 两个文件。chcp.json 文件内容如下:

    {
      "autogenerated": true,
      "content_url": "http://192.168.1.102:1111/update/hot/app1",
      "min_native_interface": 1,
      "update": "now",
      "ios_identifier": "",
      "release": "2017.07.10-22.18.08"
    }

    chcp.json 文件就是按照上面cordova-hcp.json模板生成的。
    里面的字段分别都有意义的,我们在这里做下说明:
    ①. autogenerated 表示是否自动生成的配置
    ②. content_url 表示我们线上热更代码的文件目录
    ③. min_native_interface 表示目前外壳的最小版本号
    ④. update有三个值,分别是 :

    start - app启动时安装更新,默认值
    resume - app从后台切换过来的时候安装更新
    now - web内容下载完毕即安装更新

    ⑤. ios_identifier 则是ios在appStore的应用标识
    ⑥. release 则是build时的时间戳,用于对比更新时间

    chcp.manifest文件则是www项目中的每一个文件打下一个hash印记,如下:

    [
      {
        "file": "css/style.css",
        "hash": "84a33cf00d95bc1e3617cc35262f7e94"
      },
      {
        "file": "img/adam.jpg",
        "hash": "b445da3cc203f97bce534fdad93b3931"
      },
      ...
    ]

    用以区分改动的文件,通过对比不同hash值而拉下不同的文件,用以热更新。

  • 修改项目根目录中的config.xml文件,添加如下代码:

    <chcp>
     <native-interface version="1" />
     <config-file url="http://192.168.1.102:1111/update/hot/app1/chcp.json" />
    </chcp>
    
  • 目前配置已经完成,开始打包一个新的apk安装到手机,然后修改我们的源码文件,用于标识改动,再次重新运行命令$cordova-hcp build, 将新生成的www目录中的代码放到http://192.168.1.102:1111/update/hot/app1,
    我们退出应用,重新打开后,就会出现闪一下的效果,然后就会看到修改后的代码。这就是热更新。(备注:确保手机和电脑在同一网段访问,使用192.168.1.102只是先作为测试使用)

外壳更新实现步骤

  • 为什么要进行外壳更新,比如我们新功能需要加入一些新的cordova插件,而这些插件是内置于apk中的,并非基于www目录,不能进行热更新,新的基于cordova插件的应用逻辑不能被用于旧的apk上,如果强制更新,则会
    出现严重的错误,所以我们需要引导用户下载新的apk或引导用户进入appStore更新应用。

  • 安装必要的cordova插件,如下:

    • $cordova plugin add cordova-hot-code-push-plugin
    • $cordova plugin add cordova-plugin-file-opener2
    • $cordova plugin add cordova-plugin-file-transfer
    • 备注:cordova-plugin-file-transfer安装的过程中会同时安装cordova-plugin-file插件
  • 原理说明:通过min_native_interface属性的设置,如果插件检查发现用户安装的外壳app版本比服务端新的web内容要求的版本要低,就会触发错误事件,错误码:chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW 通过这个错误码可通过弹窗提示用户去升级,跳转到AppStore或下载新安装包

  • 编写引导用户更新,下载逻辑,作为一项服务,包含引导提示界面,和下载功能。

      .factory('setHotPush', [
          '$ionicPopup',
          '$ionicLoading',
          '$timeout',
          'setUtils',
          function($ionicPopup, $ionicLoading, $timeout, setUtils) {
              var AppUpdate = function() {};
              AppUpdate.prototype = {
                  // Application Constructor
                  initialize: function() {
                      this.bindEvents();
                  },
                  // Bind any events that are required.
                  // Usually you should subscribe on 'deviceready' event to know, when you can start calling cordova modules
                  bindEvents: function() {
                      document.addEventListener('chcp_updateLoadFailed', this.onUpdateLoadError, false);
                  },
                  onUpdateLoadError: function(eventData) {
                      var error = eventData.detail.error;
                      // 当检测出内核版本过小
                      if (error && error.code === chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {
                          var dialogMessage = '有新的版本,请下载更新!';
                          // iOS端 直接弹窗提示升级,点击ok后自动跳转
                          if (ionic.Platform.isIOS()) {
                              chcp.requestApplicationUpdate(dialogMessage, this.userWentToStoreCallback, this.userDeclinedRedirectCallback);
                              // Android端 提示升级下载最新APK文件
                          } else if (ionic.Platform.isAndroid()) {
                              var confirmPopup = $ionicPopup.confirm({
                                  title: '版本更新',
                                  template: '<span class="updatePopup">' + dialogMessage + '</span>',
                                  cssClass: 'popup',
                                  cancelText: '取消',
                                  okText: '<span class="ionicPopupUpdateBtn">' + '升级' + '</span>'
                              });
                              confirmPopup.then(function(res) {
                                  if (res) {
                                      $ionicLoading.show({
                                          template: "已经下载:0%"
                                      });
                                      window.resolveLocalFileSystemURL(cordova.file.externalRootDirectory, function(fileEntry) {
                                          fileEntry.getDirectory("com.xxx.app1", {
                                              create: true,
                                              exclusive: false
                                          }, function(fileEntry) {
                                              //下载代码
                                              var fileTransfer = new FileTransfer();
                                              fileTransfer.download("http://192.168.1.102:1111/update/hot/app1/app1.apk", fileEntry.toInternalURL() + "app1.apk", function(entry) {
                                                  // 打开下载下来的APP
                                                  cordova.plugins.fileOpener2.open(
                                                      entry.toInternalURL(), //下载文件保存地址
                                                      'application/vnd.android.package-archive', { //以APK文件方式打开
                                                          error: function(err) {
                                                              setUtils.tips('下载失败!', 1);
                                                          },
                                                          success: function() {
                                                              setUtils.tips('下载成功,请安装后重新打开应用!', 1);
                                                          }
                                                      });
                                              }, function(err) {}, true);
                                              fileTransfer.onprogress = function(progressEvent) {
                                                  $timeout(function() {
                                                      var downloadProgress = (progressEvent.loaded / progressEvent.total) * 100;
                                                      $ionicLoading.show({
                                                          template: "已经下载:" + Math.floor(downloadProgress) + "%"
                                                      });
                                                      if (downloadProgress > 99) {
                                                          $ionicLoading.hide();
                                                      }
                                                  });
                                              };
                                          }, function(err) {
                                              setUtils.tips("创建目录失败原因:" + err, 1)
                                          });
                                      });
                                  }
                              });
                          }
                      }
                  },
                  userWentToStoreCallback: function() {
                      // user went to the store from the dialog
                  },
                  userDeclinedRedirectCallback: function() {
                      // User didn't want to leave the app.
                      // Maybe he will update later.
                  }
              };
              return new AppUpdate();
          }
      ]);
    

    说明,其中setUtils是自己实现的一些通用的函数封装,setUtils.tips则是封装了$cordovaToast服务的提示功能,可自己进行实现。下载文件夹的创建目录com.xxx.app1可以用公司名称的倒序然后加上apk的名称,也可自定义。上文中还加入了一些自定义的样式用于修改提示组件。

  • 将此项服务在应用run方法中注入,并调用上面的initialize方法,下面我们来开始测试。

  • 执行cordova-hcp build 命令,然后安装应用到手机,此处作为原始apk。

  • 修改项目根目录下的cordova-hcp.json文件中的min_native_interface属性,其值加1。

  • 修改项目根目录下的config.xml文件中,找到native-interface标记,其属性version值加1。

  • 修改项目源码,做个标记,然后再次执行cordova-hcp build 命令, 将新的www目录源码更新到http://192.168.1.102:1111/update/hot/app1 目录中,打包新的apk,放到http://192.168.1.102:1111/update/apks中。

  • 打开旧的应用,就会出现提示信息,确认下载,则会走进度条,下载完成则会提示您安装,安装好之后,则更新完毕。

更换线上服务器和安全问题

  • 将上述步骤中的所有192.168.1.102的相关配置替换成自己的服务器地址,请注意访问目录。

  • 热更新和外壳更新是同一项功能,打包apk之前确保两者都可用。

  • 设置服务器访问权限,目录可访问,但替换和删除则需要密码,以确保安全。

  • 在此用linux服务器举例,在更新www目录中的源码时需要删除之前的代码, 在服务端上的相应目录执行rm -rf * 删除服务器上热更新上的所有文件。

  • 然后在上传新的热更新代码, 本地cd到www目录,然后执行scp -r * hot@xxx.xxx.xxx.xxx:update/hot/app1 此处只是作为举例,hot是你服务器上的用户名,xxx.xxx.xxx.xxx代表服务器ip,使用该命令将所有新的代码部署到服务端。

注意事项

  • 在上述测试时,请保持手机和电脑保持在同一网段,确保可访问。

  • 命令cordova-hcp build可以在自定义目录中生成配置文件,如cordova-hcp build www 也可用其他目录,但没有必要,我们使用的就是www目录。

  • 注意开发环境和生产环境的区别,注意src和www的不同,注意结合构建过程。

  • 项目中没有用到 cordova-hot-code-push-local-dev-addon 测试插件,这个只作为测试使用,实际上没有什么价值。

不足之处

  • 在热更新的时候,闪一下之后,原来基于cookie的存储全部被删除,一些数据的存储尽量不要使用cookie,可以使用PouchDBSQLite
  • 在项目运行时安装新的apk,没有重新打开,而是退出了,需要手动重新打开应用,这时候会看到新的内容被更新了。

目前环境

在项目根目录下执行$ ionic info 之后

 global packages:

     @ionic/cli-plugin-proxy : 1.3.1
     @ionic/cli-utils        : 1.4.0
     Gulp CLI                : CLI version 3.9.1 Local version 3.9.1
     Ionic CLI               : 3.4.0

 local packages:

     @ionic/cli-plugin-gulp   : 1.0.1
     @ionic/cli-plugin-ionic1 : 2.0.0
     Ionic Framework          : 1.3.2

 System:

     Node       : v7.2.1
     OS         : OS X El Capitan
     Xcode      : Xcode 8.2.1 Build version 8C1002
     ios-deploy : 1.8.6
     ios-sim    : 5.0.8
     npm        : 4.3.0

参考博客

官方文档参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值