Cordova 代码热更新 - 简书

Posted lonelyxmas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cordova 代码热更新 - 简书相关的知识,希望对你有一定的参考价值。

原文:Cordova 代码热更新 - 简书

Cordova 代码热更新

[图片上传失败...(image-a19be7-1521624289049)]

基于 Cordova 框架能将网页应用 (js, html, css, 图片等) 打包成 App。当 App 在应用商店上架后,如何快速更新是我们需要考虑的问题。??

  • 本地打包新版本 App 发布到应用商店,但这中发布流程耗费时间,尤其是 Apple Store
  • 应用加载网络资源,这样 App 展示的内容就可以保证是最新的,但当应用断网时,应用就无法正常使用

我们能想的这两种方式都存在的很大的弊端,不适合实际应用!

插件 Cordova Hot Code Push (CHCP)

插件 Cordova Hot Code Push 正是针对 Cordava 应用如何快速更新问题而提供的解决方案,可以自动更新 web 相关的静态文件。

该插件提供了详细的 wiki 文档,请参考:wiki 文档

App Store 支持么

近日,苹果App Store审核团队向一些开发者下最后通牒:2017年6月12日之前移除所有热更新相关代码、框架或SDK,并重新提交版本。如果不作调整,App可能会从App Store下架。

苹果应用商店已经禁止使用类似 JSPatch 等热修复的框架或者SDK,那么这个插件提供的代码热更新功能是否违法这一规定呢? ??
?? 答案是否定的!此插件提供的代码热更新是 web 静态文件,苹果是允许这一做法的。但有两点值得注意:

  • ① 不能明显告知用户有新版本可用,询问用户是否需要更新到最新代码。这一做法会使用户产生困惑,这种更新方式和通过 App Store 更新有何区别。所以正确的做法是,在应用启动的时候,下载和安装热更新代码;或者在某个时机下载热更新代码,在应用下次打开时进行安装
  • ② 如果通过此插件进行代码热更新后,应用功能发生巨大变化,譬如原来是一个计算器应用,代码热更新后,变成了一个音乐播放器,这种欺骗用户的做法也是会被苹果拒绝的

添加插件到项目中

1、下载插件
要求 Cordova 版本 5.0+

cordova plugin add cordova-hot-code-push-plugin --save

2、下载插件的命令行工具 cordova-hot-code-push-cli

npm install -g cordova-hot-code-push-cli

该命令行工具可帮助我们自动生成配置文件 chcp.jsonchcp.manifest,同时还提供了一些其他功能,详细可参考其 README。

3、下载插件后,执行 cordova platform add ios 时可能会遇到如下报错:

Error: Cannot find module ‘xml2js/lib/processors‘

参考 xml2js is not installed 解决方法很简单:npm install xml2js

热更新相关配置

Cordova Hot Code Push 下载安装到项目中后,需要对其进行相关的配置才能让其工作。

插件配置

Cordova Hot Code Push 热更新插件需要两个配置文件:

  • Application config:chcp.json 包含发布相关信息:热更新代码版本号,应用 native side 版本号等等
  • Content manifest:chcp.manifest 包含项目热更新代码(静态)文件信息:文件名和文件哈希值

这两个配置文件对于插件的运行缺一不可,前者描述了热更新代码的版本信息,后者提供了热更新代码文件的变更信息。借助 cordova-hot-code-push-cli 这个命令行工具可以辅助我们创建这两个配置文件。

Application config

Application config holds information about the current release of the web project.

chcp.json 置于 www 目录根目录,例子如下:

{

  "name": "wps-*****",

  "content_url": "https://kss.ksyun.com/*****/*****/",

  "ios_identifier": "326CN*****",

  "android_identifier": "com.**********.*****.*****.*****.*****",

  "update": "resume",

  "release": "2017.06.07-16.30.20",

  "min_native_interface": 1

}

1、配置项
name 项目名称
content_url web 项目文件在服务器上的存储路径(即 www 目录在云存储中的目录路径),热更新插件将此 URL 作为 base url,用于下载配置文件和项目更新文件(必需的配置项)
release 描述 web 项目版本号,每一次发布的版本号必须唯一(默认使用时间戳,格式为:yyyy.MM.dd-HH.mm.ss),插件是将版本号进行字符串相等比较来判断是否存在新版本(必需的配置项)
min_native_interface

Minimum version of the native side that is required to run this web content

  • cordova 项目主要包含两部分:web content 和 native side。前者是网页内容,后者是 cordova 插件,为网页提供原生 API 支持,web content 的运行是基于 native side。
  • 该配置项指明 web content 运行时 native side 的最低版本。在 native side 代码有变更后(cordova 插件新增/删除,native side 版本号更新),为了确保 web content 能正常运行,需要更新 min_native_interface 的值

在应用 config.xml 配置中可以定义了 native side 的版本号,例如

<chcp>

    <native-interface version="5" />

</chcp>

例如当前项目 native side 的版本号是5:

  • 如果服务器上配置文件 chcp.json 中的 min_native_interface 值为 5,那么符合要求,热更新后的 web content 能够在正常运行
  • 如果服务器上配置文件 chcp.json 中的 min_native_interface 值为 10,高于 config.xml文件中 <native-interface />,那么热更新将无法正常进行。此时,插件会提示错误 chcp_updateLoadFailed,提示应用需要更新升级

update 何时触发进行安装(install)代码热更新
代码热更新涉及两个主要过程:fetch update 和 install update。前者获取热更新变更文件,后者将获取到的更新文件安装到 App 中生效。此字段是针对后者,何时 install update,可选值:

  • start:应用启动,默认项(install update when application is launched)
  • resume:应用从后台恢复(install the update when application is resumed from background state)
  • now:下载更新后立即执行(install update as soon as it has been downloaded)

当然也可以禁用自动 install update,手动调用相关 API 进行 install
android_identifier / ios_identifier

  • android_identifier: Package name of the Android version of the application
  • ios_identifier: Identification number of the application
    用于跳转到 Google Play Store 或者 App Store 该应用页面

2、如何生成该文件:

  • 在 cordova 项目根目录执行 cordova-hcp init ,会通过命令行交互的方式,提示输入配置有关信息,创建该文件,会在项目根目录创建一个默认 Application config 文件 cordova-hcp.json
  • 然后在每次应用打包时,再执行 cordova-hcp build 即可在 web 项目 www 根目录生成一个 chcp.json 文件。

Content manifest

Content manifest describes the state of the files inside your web project.

通过执行 cordova-hcp buildwww 根目录自动生成 chcp.manifest 文件

[

  {

    "file": "import.html",

    "hash": "fc9301d4bd7381ba6033aa51884ed2dd"

  },

  {

    "file": "index.html",

    "hash": "f73630f62a531ab6c41cd067eb4f9b07"

  },

  {

    "file": "lib/lib.min.js",

    "hash": "6ecb0251f4c54f80586d9059dfc61de8"

  },

  ...

]

chcp.manifest 文件中包含的是 web content 静态文件信息,每一个项都包括两个字段:

  • file: 相对于 www 目录的文件路径
  • hash: 文件的 MD5 哈希值,用于判断文件是否发生变更

基于 chcp.manifest 文件

  • 在 fetch update 阶段,从服务器上获取新增、修改文件
  • 在 install update 阶段,移除被删除文件

Cordova config.xml 配置

Cordova 项目的 config.xml 文件用于设置项目配置选项,Cordova Hot Code Push 热更新插件的配置项也需要在该文件中进行相应的配置。

<chcp>

    <config-file url="https://kss.ksyun.com/********/chcp.json" />

    <auto-download enabled="false" />

    <auto-install enabled="false" />

    <native-interface version="1" />

</chcp>

</pre>
  • config-file:配置文件 chcp.json 从服务器上加载的路径(必须的配置项)
  • auto-download:是否自动下载热更新代码,默认是 true
  • auto-install:是否自动安装热更新代码,默认是 true
  • native-interface:当前 native side 的版本号

可以禁用自动下载,安装热更新代码,通过手动调用执行。

代码热更新原理

热更新流程

[图片上传失败...(image-e57e54-1521624148035)]

  • ① 应用启动
  • ② 热更新插件初始化,并在后台加载更新模块 (update loader)
  • ③ 更新模块 (update loader) 从 Cordova 项目配置 config.xml 文件中获取 config-file (热更新插件配置文件 chcp.json 的加载路径),然后加载配置文件 chcp.json,获取其中的 release 版本号,对比当前的版本号,若二者不同,说明有新版本,执行下一步
  • ④ 更新模块 (update loader) 从 chcp.json 配置文件中获取 content_url 作为 base url,然后加载 chcp.manifest 文件,或者新版本文件变更信息
  • ⑤ 更新模块 (update loader) 根据 content_url 作为 base url,下载所有变更、新增文件
  • ⑥ 如果一切顺利, 更新模块 (update loader) 发送通知,该更新已准备好进行安装
  • ⑦ 安装更新,应用重定向到新版本页面

Cordova web project 存储与更新

Cordova 项目中都包含一个 www 目录,存储网页静态文件,Cordova 打包移动应用时,会将其拷贝到各自的项目目录,同时会被打包到应用中。

  • Android: platforms/android/assets/www.
  • iOS: platforms/ios/www.

www 目录打包到应用中之后,我们就没办法对其进行更新了,因为只有可读权限。为了解决这一问题,在应用第一次启动的时候,从应用 bundle 中加载网页内容的同时,将 www 目录拷贝到外部目录中,在后续应用启动时,都从这个外部存储的静态文件中加载文件,而对于外部的这个存储目录,我们就有读写权限,这样就为我们动态更新网页代码提供了可能。

在 safari 调试页面执行 cordova.file.applicationStorageDirectory 可以得到应用的存储路径,点击可以打开 Finder 目录。
Library/Application Support 目录下就可以找到存储 web content 的外部目录。
[图片上传失败...(image-d0c218-1521624148035)]
[图片上传失败...(image-5ff6fd-1521624148035)]

Cordova Hot Code Push 插件为每一个版本内容都创建了一个对应的目录,以配置文件 chcp.jsonrelease 字段值为目录名,存放不同版本 www 目录中的静态文件,这种处理方式的好处是:

  • 避免了文件缓存问题。例如 iOS UIWebView 缓存 css 文件,即使刷新页面,也不会清除缓存,除非重启应用才能强制清除缓存。不同版本置于不同的目录,由于加载路径不同,这样就可以解决文件的缓存问题
  • 避免在更新代码文件时,和当前已有文件出现冲突
  • 方便回滚到前一个版本

?? 下面了解一下,获取更新内容和安装更新内容时都发生了什么?

1、获取更新内容

  • 根据 release 版本号,创建一个新的目录
  • 在新目录中,创建 update 目录,根据 chcp.manifest 文件,将所有变更、新增文件下载到该目录中
  • 新版本对应的 chcp.jsonchcp.manifest 文件也会置于 update 目录中

[图片上传失败...(image-b49207-1521624148035)]

2、安装更新内容

  • 将当前版本对应目录下的 www 目录拷贝到新版本对应的目录下
  • 在新版本对应目录下,将 update 目录中变更、新增文件拷贝到 www 目录中,同时根据 chcp.manifest 移除被删除文件
  • 移除 update 目录
  • 应用重定向到新版本目录下加载网页内容

插件 JS 接口

默认情况下,Cordova Hot Code Push (CHCP) 插件不需要额外的代码,就可以自动执行 checking->downloading->installation 这个更新循环。当然也可以通过其提供的接口来控制这更新流程,这时,我们需要在项目 config.xml 文件中配置 auto-downloadauto-installfalse

<chcp>

    <config-file url="https://kss.ksyun.com/******/chcp.json" />

    <auto-download enabled="false" />

    <auto-install enabled="false" />

    <native-interface version="1" />

</chcp>

事件监听

Cordova Hot Code Push 插件提供了一系列事件监听,方便我们对不同情况进行不同的处理。例如:chcp_updateInstalled 事件,当更新安装完成时会发出这个通知;chcp_updateInstallFailed 事件,当更新安装失败时发出这个通知,等等。

值得注意的是,需要在 deviceready 事件回调后,才进行 CHCP 插件的事件监听注册。

var app = {

  // 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(‘deviceready‘, this.onDeviceReady, false);

    document.addEventListener(‘chcp_updateIsReadyToInstall‘, this.onUpdateReady, false);

  },

  // deviceready Event Handler

  onDeviceReady: function() {

    console.log(‘Device is ready for work‘);

  },

  // chcp_updateIsReadyToInstall Event Handler

  onUpdateReady: function() {

    console.log(‘Update is ready for installation‘);

  }

};

app.initialize();

详细事件监听列表参考文档:Listen for update events

获取/安装更新

① fetch update chcp.fetchUpdate
调用 API 从服务器中获取更新

function fetchUpdate(cb) {

    var options = {

        ‘config-file‘: ‘https://kss.ksyun.com/******/chcp.json‘

    };

    chcp.fetchUpdate(updateCallback, options);

    function updateCallback(error, data) {

        if (error) {

            console.log(‘--fetchUpdate error--‘, error.code, error.description);

        }

        console.log(‘--fetchUpdate--‘, data, data.config);

        cb && cb(error, data);

    }

}

② install update chcp.installUpdate
调用 API 安装更新

function installUpdate(cb) {

    chcp.installUpdate(installationCallback);

    function installationCallback(error) {

        if (error) {

            console.log(‘Failed to install the update with error code: ‘ + error.code);

            console.log(error.description);

        } else {

            console.log(‘Update installed!‘);

        }

        cb && cb(error);

    }

}

在安装更新之前,还需要检测是否有更新可用于安装
chcp.isUpdateAvailableForInstallation

function checkIsUpdateAvailableForInstallation(cb) {

    chcp.isUpdateAvailableForInstallation(callbackMethod);

    function callbackMethod(error, data) {

        if (error) {

            console.log(‘No update was loaded => nothing to install‘);

        } else {

            console.log(‘Current content version: ‘ + data.currentVersion);

            console.log(‘Ready to be installed:‘ + data.readyToInstallVersion);

        }

        cb && cb(error, data);

    }

}

获取版本信息

function getVersionInfo(cb) {

    chcp.getVersionInfo((err, data) => {

        console.log(‘Current web version: ‘ + data.currentWebVersion);

        console.log(‘Previous web version: ‘ + data.previousWebVersion);

        console.log(‘Loaded and ready for installation web version: ‘ + data.readyToInstallWebVersion);

        console.log(‘Application version name: ‘ + data.appVersion);

        console.log(‘Application build version: ‘ + data.buildVersion);

        cb && cb(err, data);

    });

}

错误代码

在下载,安装更新过程中都有可能出现错误,详细的错误代码参考:Error codes

请求到应用商店进行 APP 升级

插件配置文件 chcp.jsonmin_native_interface 选项是网页内容执行时要求 native side 最低版本号。每一次热更新过程中,都会去检查这个逻辑,判断当前 native side 的版本是否符合要求。如果当前 APP 中的 native side 版本号低于 chcp.jsonmin_native_interface 的选项值,那么执行热更新就会提示错误:chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW,这个时候,我们应当提示用户前往应用商店对 APP 进行升级。

恰当的处理方式是,在出现 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW 错误时,弹框提示用户前往应用商店进行升级,弹框有两个按键:一个点击后跳转到应用商店该 APP 对应下载页面;另一个点击后关闭弹框。插件也提供了 API 处理这个过程,我们只需:

  • chcp.json 配置文件中设置 android_identifierios_identifier
  • 调用 chcp.requestApplicationUpdate 方法

监听 chcp_updateLoadFailed 事件,判断错误代码为 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW 时,调用 chcp.requestApplicationUpdate 方法。

var app = {

  // 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(‘deviceready‘, this.onDeviceReady, false);

    document.addEventListener(‘chcp_updateLoadFailed‘, this.onUpdateLoadError, false);

  },

  // deviceready Event Handler

  onDeviceReady: function() {

  },

  onUpdateLoadError: function(eventData) {

    var error = eventData.detail.error;

    if (error && error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {

        console.log(‘Native side update required‘);

        var dialogMessage = ‘New version of the application is available on the store. Please, update.‘;

        chcp.requestApplicationUpdate(dialogMessage, this.userWentToStoreCallback, this.userDeclinedRedirectCallback);

    }

  },

  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.

  }

};

app.initialize();

Usage Limitations

1、Don’t rename/delete/move your index page
Cordova 项目 config.xml 文件中都会定义一个入口页面 index.html

<content src="index.html" />

应用启动的时候,就会加载 index.html 页面作为入口,在代码热更新过程中,这是唯一不能删除,移动和重命名的文件,否则,代码热更新后,应用就无法正常加载到 index.html 入口页面,所以会出错。

诚然,如果你需要重命名,或者修改其存储路径,那么需要在 config.xml 文件中修改 content 配置。

2、Do not clean plugin’s inner preferences with cordova-plugin-nativestorage
cordova-plugin-nativestorage 插件提供了读写本地存储数据的能力,例如在 iOS 中对应的本地存储是 NSUserDefault,CHCP 热更新插件在其中存储了一些属性。

调用 cordova-plugin-nativestorage 插件中的 NativeStorage.clear() 方法会清除本地存储数据,这就会影响到 CHCP 插件的正常运行,导致下一次应用启动时加载的是应用 bundle 中 www 目录中的网页内容,而非外部目录存储的当前版本网页内容。

www 目录打包上传到服务器或者云存储目录

新版本发布时,都需要执行如下处理:

  • www 目录下的静态文件进行打包,包括代码压缩,合并等等
  • 执行 cordova-hcp build 生成 chcp.jsonchcp.manifest 文件
  • www 目录下的静态文件上传至服务器或者云存储目录

问题总结:
1 ERROR: Plugin ‘HotCodePush‘ not found

The problem is resolved. Looking at forums, below are the correct steps

1 Run cordova plugin add cordova-hot-code-push-plugin in the project
2 Install npm install -g cordova-hot-code-push-cli on the system
3 Run cordova-hcp init and enter details
4 Run cordova-hcp build
5 Add the following in config.xml

<chcp>
    <config-file url="https://example.com/chcp.json"/>
</chcp>
Run cordova build

2 创建模版cordova-hcp.json,放在项目根目录下,避免每次修改chcp.json文件

{

  "name": "wps-*****",

  "content_url": "https://kss.ksyun.com/*****/*****/",

  "ios_identifier": "326CN*****",

  "android_identifier": "com.**********.*****.*****.*****.*****",

  "update": "resume",

  "release": "2017.06.07-16.30.20",

  "min_native_interface": 1

}

之后每次修改代码后,在项目根目录运行 cordova-hcp build
就可以在www目录生成跟模版一样的chcp.json,只是release时间不一样

3 补充,手动更新

以上教程的热更新属于自动化的,在用户完全不知情的情况下,于是就有了可选择更新的需求:

    <chcp>
        <auto-download enabled="false" />
        <auto-install enabled="false" />
        <config-file url="http://120.24.77.175:8080/ehospital/views/MSUI/chcp.json" />
    </chcp>

在config.xml改成以上配置,取消自动下载,取消自动安装,然后通过插件提供的jsAPI进行手动更新,以下在deviceready事件触发中示例(仅供参考):

    document.addEventListener(‘deviceready‘, () => {
      let chcp = window.chcp;
      // 检测更新
      chcp.fetchUpdate((error, data) => {

        // 表示没有更新版本,或者其他错误,详情的信息参考上面的chcp error链接
        if (error) {
          console.log(‘--fetchUpdate error--‘, error.code, error.description);
          return;
        }

        // 这次更新的版本信息
        console.log(‘--fetchUpdate--‘, data, data.config);
        // 检测是否是否可以进行安装了,双重判断吧,有时候会出现有更新版本但是暂时无法安装的情况(也可以去掉这一层)
        chcp.isUpdateAvailableForInstallation((error, data) => {

          if (error) {
            console.log(‘No update was loaded => nothing to install‘);
          } else {
            // 询问用户是否更新
            if ( window.confirm(‘检测到新版本,是否更新‘) ) {
              // 更新中
              chcp.installUpdate((error) => {
                if (error) {
                  // 更新失败
                  console.log(‘Failed to install the update with error code: ‘ + error.code);
                  console.log(error.description);
                } else {
                  // 更新成功
                  console.log(‘Update installed!‘);
                }
              });
            } else {
              window.alert(‘您已拒绝更新‘);
            }

            // 对比版本号
            console.log(‘Current content version: ‘ + data.currentVersion);
            console.log(‘Ready to be installed:‘ + data.readyToInstallVersion);
          }

        });
      });
    });

#Cordova Hot Code Push
























以上是关于Cordova 代码热更新 - 简书的主要内容,如果未能解决你的问题,请参考以下文章

cordova热更新

Cordova - XCode10编译热更新插件错误解决方法!

Meteor cordova 应用程序不执行热代码推送

ionic app 热更新

Ionic APP 热更新 之 产品发布状态下的热更新搭建,去local-dev-addon插件

热更新有多重要?游戏代码热更新杂谈