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.json
和 chcp.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 build
在 www
根目录自动生成 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.json
中 release
字段值为目录名,存放不同版本 www
目录中的静态文件,这种处理方式的好处是:
- 避免了文件缓存问题。例如 iOS UIWebView 缓存 css 文件,即使刷新页面,也不会清除缓存,除非重启应用才能强制清除缓存。不同版本置于不同的目录,由于加载路径不同,这样就可以解决文件的缓存问题
- 避免在更新代码文件时,和当前已有文件出现冲突
- 方便回滚到前一个版本
?? 下面了解一下,获取更新内容和安装更新内容时都发生了什么?
1、获取更新内容
- 根据
release
版本号,创建一个新的目录 - 在新目录中,创建
update
目录,根据chcp.manifest
文件,将所有变更、新增文件下载到该目录中 - 新版本对应的
chcp.json
和chcp.manifest
文件也会置于update
目录中
[图片上传失败...(image-b49207-1521624148035)]
2、安装更新内容
- 将当前版本对应目录下的
www
目录拷贝到新版本对应的目录下 - 在新版本对应目录下,将
update
目录中变更、新增文件拷贝到www
目录中,同时根据chcp.manifest
移除被删除文件 - 移除
update
目录 - 应用重定向到新版本目录下加载网页内容
插件 JS 接口
默认情况下,Cordova Hot Code Push (CHCP) 插件不需要额外的代码,就可以自动执行 checking->downloading->installation
这个更新循环。当然也可以通过其提供的接口来控制这更新流程,这时,我们需要在项目 config.xml
文件中配置 auto-download
和 auto-install
为 false
<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.json
中 min_native_interface
选项是网页内容执行时要求 native side 最低版本号。每一次热更新过程中,都会去检查这个逻辑,判断当前 native side 的版本是否符合要求。如果当前 APP 中的 native side 版本号低于 chcp.json
中 min_native_interface
的选项值,那么执行热更新就会提示错误:chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
,这个时候,我们应当提示用户前往应用商店对 APP 进行升级。
恰当的处理方式是,在出现 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
错误时,弹框提示用户前往应用商店进行升级,弹框有两个按键:一个点击后跳转到应用商店该 APP 对应下载页面;另一个点击后关闭弹框。插件也提供了 API 处理这个过程,我们只需:
- 在
chcp.json
配置文件中设置android_identifier
和ios_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.json
和chcp.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 代码热更新 - 简书的主要内容,如果未能解决你的问题,请参考以下文章
Cordova - XCode10编译热更新插件错误解决方法!