原文地址:http://blog.csdn.net/lovelyelfpop/article/details/50848524
插件地址:https://github.com/nordnet/cordova-hot-code-push
下面是我对GitHub项目readme的翻译
——————————————————————————————————————————————
Cordova Hot Code Push Plugin
此插件提供了可以使cordova app自动更新web内容的功能。基本上, 你App中所有位于 www
目录内的文件都可以被自动更新.
当你重新发布新的app时-重新打包了web内容: html 文件, JavaScript 代码, 图片等等. 一般有两种方式进行升级:
在appstore中上架新的app. 但是耗时比较长.
牺牲所有原生功能,每次打开都从远端网站加载. 但是如果没有网络,app就没法使用.
此插件就为了解决这个问题而生. 当用户初次打开app - 它会将所有web内容复制一份到外部存储. 此后从外部存储加载web内容,而并不加载打包在app内部的web内容. app每次启动都会连接服务器检查更新并下载新的web内容. 如果下载了更新 - 此次更新内容将会在下次app启动时生效.
这样, 你的app就得到了实时更新, 并且也能在离线的时候使用. 还有,插件允许你对web内容设置最小支持的app外壳版本, 以保证新的web内容可以在旧的app外壳上运行.
App Store可以上架这种app吗? 可以... 只要你更新后的web内容符合app一开始的功能. 如果本来是个计算器, 更新后变成了一个音乐播放器 - 这是会被禁止的.
Android 4.0.0 或以上.
iOS 7.0 或以上.
安装
从低版本迁移
Cordova 项目快速向导
Ionic 项目 快速向导
更新机制的流程图
web内容是如何存储和更新的
Cordova Hot Code Push 命令行客户端
本地开发扩展
Cordova 配置项
配置文件
Application config app配置
Content manifest 内容清单
Build options build设置
JavaScript 模块
监听更新的事件
请求更新
安装更新
运行时改变插件设置
请求从store更新app(外壳)
错误码
需要cordova 5.0+
cordova plugin add cordova-hot-code-push-plugin
也可直接从 仓库url 安装(不稳定)
cordova plugin add https://github.com/nordnet/cordova-hot-code-push.git
插件安装完后,会推荐你安装Cordova Hot Code Push 命令行客户端. 此客户的可以帮助你:
方便生成必须的app配置文件;
启动本地服务器,监听开发模式下的web内容变更,并直接部署新版本.
当然,你也可以不用这个命令行客户端, 只是用了它会更加方便.
在版本 1.0.x 的时候,本地开发模式集成到了此插件里面. 从 v1.1.x 开始这部分功能作为了此插件的一个扩展,移到了这里. 因为 v1.0 版本为了支持ios的Swift做了一些优化 - 升级到 v1.1.x 你需要禁用它.
重新安装 iOS platform的办法:
cordova platform remove ios cordova platform add ios
当 platform 被添加之后 - 所有插件会自动安装.
进阶 - 手动移除 Swift 支持. 你需要用Xcode打开 iOS 项目, 然后:
在 Build Settings
,设置 Embedded Content Contains Swift Code
为 NO
.
打开
, 移除 #import
. 比如:
#ifdef __OBJC__ #import "TestProject-Swift.h" #endif
重新build, 检查是否正常.
此向导展示了在开发中如何快速使用这个插件. 我们需要添加 开发扩展 ,需要 Xcode 7, 尽管hot code push plugin插件自身可以支持低版本xcode.
创建新的Cordova项目,并添加android和iOS platform:
cordova create TestProject com.example.testproject TestProjectcd ./TestProject cordova platform add android cordova platform add ios
或者可以用一个已有的项目.
添加插件:
cordova plugin add cordova-hot-code-push-plugin
添加开发扩展:
cordova plugin add cordova-hot-code-push-local-dev-addon
安装 Cordova Hot Code Push 命令行客户端:
npm install -g cordova-hot-code-push-cli
启动本地服务器:
cordova-hcp server
你会看到下面的命令行输出:
Running server Checking: /Cordova/TestProject/www local_url http://localhost:31284 Warning: .chcpignore does not exist. Build 2015.09.02-10.17.48 created in /Cordova/TestProject/www cordova-hcp local server available at: http://localhost:31284 cordova-hcp public server available at: https://5027caf9.ngrok.com
打开新的控制台, 进入到项目根目录,运行app:
cordova run
稍等,app会安装到手机或者模拟器.
现在打开 TestProject/www/index.html
, 做一些改动然后保存. 几秒种后你可以在手机或模拟器上看到更新后的页面.
到此,你可以本地开发,新的web内容会自动在设备上更新,而无需重新启动app查看效果.
此向导展示了在开发中如何快速使用这个插件. 我们需要添加 开发扩展 ,需要 Xcode 7, 尽管hot code push plugin插件自身可以支持低版本xcode.
创建新的Ionic项目,并添加android和iOS platform::
ionic start TestProject blankcd ./TestProject ionic platform add android ionic platform add ios
Or use the existing one.
添加插件:
ionic plugin add cordova-hot-code-push-plugin
添加开发扩展:
ionic plugin add cordova-hot-code-push-local-dev-addon
安装 Cordova Hot Code Push 命令行客户端:
npm install -g cordova-hot-code-push-cli
启动本地服务器:
cordova-hcp server
你会看到下面的命令行输出:
Running server Checking: /Cordova/TestProject/www local_url http://localhost:31284 Warning: .chcpignore does not exist. Build 2015.09.02-10.17.48 created in /Cordova/TestProject/www cordova-hcp local server available at: http://localhost:31284 cordova-hcp public server available at: https://5027caf9.ngrok.com
打开新的控制台, 进入到项目根目录,运行app:
ionic run
稍等,app会安装到手机或者模拟器.
现在打开 TestProject/www/index.html
, 做一些改动然后保存. 几秒种后你可以在手机或模拟器上看到更新后的页面.
到此,你可以本地开发,新的web内容会自动在设备上更新,而无需重新启动app查看效果.
先防止所有的配置相关的内容弄得你稀里糊涂 - 先来看看此插件的实现更新功能的流程图. 应该没有技术细节.
用户打开你的app.
插件初始化,在后台进程启动 升级加载器(update loader).
Update loader 从 config.xml
取 config-file
配置(一个url),并从此url加载一段 JSON 配置. 然后它把这段JSON配置中的 release
版本号 和当前app 已经安装的进行比较. 如果不同 - 进入下一步.
Update loader 使用app配置(application config)中的 content_url
,去加载清单文件(manifest). 它会找出自上次升级以来,哪些文件需要更新.
Update loader 从 content_url
下载更新文件.
如果一切顺利 - 发出一个"升级文件已经准备好,可以安装了"的通知.
升级文件已安装, app重新进入更新过的页面.
当然, 还有其他的细节, 不过你已经有了大致的思路.
每一个Cordova 项目都有一个 www
目录, 这里存放所有的web内容. 当cordova build
执行后 - www
里的内容会拷贝到对应platform的 www
目录下:
安卓: platforms/android/assets/www
.
iOS: platforms/ios/www
.
于是这些文件被打包进了app. 我们不能更新安装包里的这些文件, 因为它们是只读的. 正因为如此,所以我们要在app第一次启动的时候,将内置的web内容(www目录)复制到外部存储. 我们不想在拷贝过程中阻塞ui - 我们还是会先加载app内置的index.html. 但是下一次启动或更新 - 我们就从外部存储加载index.html.
但是如果你的app外壳需要增加新的cordova插件或者原生功能 - 你必须要重新上架外壳app到store商店. 还有 - 增加外壳 app 的build版本号 (App Store 或 Google Play强制的).下次启动,插件检查外壳app版本号是否变化, 如果变了 - 会重新拷贝内置web内容(www目录)到外部存储.
开发app的时候 - 你可能会困惑: 改了一些文件, 重新启动了app - 但却看到的是旧的页面. 现在你知道原因了: 插件用的是旧版本的web内容(外部存储中). 若要清除缓存,你需要:
卸载app, 执行 cordova run
.
增加外壳app版本号,强制插件重新安装 www
目录. 更改外壳app版本号请设置 config.xml文件的
android-versionCode
和 ios-CFBundleVersion
.
安装 本地开发扩展 ,让它帮你处理版本号问题. 每次build他会自动帮你app的build版本号加1,不需要你手动更改
上面就是简要介绍, 以便你理解大致的思路. 现在我们继续深入.
之后你会阅读到 配置文件 这一节- 这有app配置 (application config), 名字是chcp.json
. 里面有个 release
设置, 这个指明了web内容的版本. 这个配置必须而且每次发布的release版本必须不一样. 它由 命令行客户端 自动生成,格式是: yyyy.MM.dd-HH.mm.ss
(比如 2015.09.01-13.30.35
).
每次发布,插件在外部存储自动生成一个以这个 release版本 为名字的目录, 然后把web内容全部放到这里面. release版本号成为了 url的一部分. 这个手段可以解决一些问题:
网页内容缓存问题. 比如, iOS 上,css 文件会被 UIWebView缓存起来, 即使我们重新载入了index.html - 新的样式还是不会被应用. 你需要用任务管理器杀死app, 或者改变css的路径.
基本不会发生更新后损坏已有web内容的现象, 因为我们每次更新都在不同的目录下.
即使更新导致了web内容损坏 - 我们可以回滚到上一个版本的release.
比如, 我们当前运行的release版本是 2015.12.01-12.01.33
. 这意味着:
所有web内容存储在 /sdcard/some_path/2015.12.01-12.01.33/www/
. 包含了Cordova的资源.
Index 页面, 用户看到的是 /sdcard/some_path/2015.12.01-12.01.33/www/index.html
.
某个时候我们发布了一个新的release: 2016.01.03-10.45.01
. 第一步,插件需要下载新的web文件, 发生情况如下:
在外部存储创建了一个以新的 release 版本号为名字的目录: /sdcard/some_path/2016.01.03-10.45.01/
.
目录里面 - 又创建了一个 update
目录 : /sdcard/some_path/2016.01.03-10.45.01/update/
.
所有根据 chcp.manifest
更新的文件 都被下载到了这个 update
目录内.
新的 chcp.manifest
和 chcp.json
也被放到了 update
目录内.
新的web内容已准备安装.
安装更新的时候:
插件从当前正在使用的release版本 目录内拷贝 www
下所有内容到 新的 release 版本目录下. 用我们的例子就是:从 /sdcard/some_path/2015.12.01-12.01.33/www/
拷贝所有文件到 /sdcard/some_path/2016.01.03-10.45.01/www/
.
从update
目录下拷贝新的web内容和配置文件,到 www
目录下: /sdcard/some_path/2016.01.03-10.45.01/update/
-> /sdcard/some_path/2016.01.03-10.45.01/www/
.
移除 /sdcard/some_path/2016.01.03-10.45.01/update/
目录,因为我们不再使用了.
加载新的release版本index.html: /sdcard/some_path/2016.01.03-10.45.01/www/index.html
.
至此,插件会从新的release加载页面, 而旧的release则会作为一个备份留下来,以防万一.
Cordova Hot Code Push 命令行客户端 是一个命令行工具,以便你web内容的开发.
它可以:
生成 chcp.json
和 chcp.manifest
文件, 这样你就不用手动去创建;
运行本地服务,开发时可以检测更新,并发布新的release版本,使得可以再设备上实时更新web内容;
部署你的web内容到外部服务器上.
当然, 你可以不使用这个命令行工具. 只是用了它会更方便一些.
当你本地开发app时 - 一般做法类似:
web项目做一些更改.
执行 cordova run
启动app.
稍等一会查看运行结果.
即使很小的变更也需要打包重装app. 耗时比较久,比较麻烦.
为了提升速度 - 你可以使用本地开发扩展 Hot Code Push Local Development Add-on. 安装很简答:
添加此cordova插件.
启动本地服务 cordova-hcp server
.
在你的项目的config.xml
文件中
块下添加
.
启动app.
这样, 所有web内容的变更都会被插件检测到, 并直接更新显示到app上,而不需要重启app.
只有在添加了新的cordova插件时你才会重启app.
重要: 你应该只在开发状态下使用此扩展. 发布外壳app的时候,应该移除此扩展: cordova plugin remove cordova-hot-code-push-local-dev-addon
.
你应该知道, Cordova 使用 config.xml
文件配置不同项目: app名字, 描述, 起始页面,等等. 使用config.xml文件,你也可以为此插件配置.
这些配置位于
块. 比如:
定义了一个 URL,指定了需要从哪里加载app配置(application config,就是chcp.json). URL 在 url
属性中声明. 此项必须.
以防万一,开发的时候, 如果 config-file
没有定义 - 会自动设为本地服务上 chcp.json 的路径.
自动下载web内容更新. 默认是自动, 如果你想手动下载web内容更新,你可以使用 JavaScript 模块(下面有).
禁用自动下载可以设置 config.xml
:
默认是 true
.
自动安装. web内容更新. 默认是自动, 如果你想手动安装web内容更新,你可以使用 JavaScript 模块(下面有).
禁用自动安装可以设置 config.xml
:
默认是 true
.
此插件用到2个配置文件:
app配置 Application config - 包含最新的release信息: release 版本号, 最低需要的外壳app版本号,等等. 文件名 chcp.json
Web内容清单 Content manifest - 包含所有web内容文件的名字和MD5值. 文件名 chcp.manifest
这两个文件必须. 他们描述了是否有新的release版本,以及文件更新时的比较.
还有一个build 可选参数 (build options) 文件, 可以再执行cordova build
命令时指定插件的配置.
包含最新版本的release信息.
简单的例子:
{ "content_url": "https://5027caf9.ngrok.com", "release": "2015.09.01-13.30.35"}
这个文件应该放在 www
目录下,文件名是 chcp.json
. 这个文件也被打包到了外壳app内.
你可以手动创建它, 或者用 cordova-hcp
命令(Cordova Hot Code Push 命令行)自动生成. 只要在cordova项目根目录下运行 cordova-hcp init
, 以后要发布新的release只要执行 cordova-hcp build
. 更多内容请阅读 命令行客户端的文档.
服务端URL, 也就是你所有web内容文件的位置. 插件会把它作为下载新的清单文件、新的web内容文件的 base url. 此项必须.
任何字符串. 每次release应该唯一. 插件基于这个才知道有没有新版本release. 此项必须.
重要: 插件只比较release字符串是否相等, 如果不等,就认为服务端有新版本.(不会比较大小)
所需最小的外壳app版本. 这是app的build版本号,是个整型数字, 不是应用商店中看到的形如"1.0.0"字符串.
在 config.xml
中,这样指定build版本号:
version
- app字符串版本号, 也就是用户在商店中看到的版本.
android-versionCode
- 安卓的build版本号. 这个应该用于 min_native_interface
.
ios-CFBundleVersion
- iOS的build版本号.这个应该用于 min_native_interface
.
Preference creates dependency between the web and the native versions of the application.
重要: 因为cordova的一个奇葩现象, 生成的 .apk
的build版本号会被加 10, 导致了变成了形如 70, 72, or 74, 根据不同平台 (arm/x86/etc),后面的0、2、4不一样. 为了绕过这个, 我们建议也给 iOS build版本号手动加10, 这样 min_native_interface
(比如 70
) 就可以对安卓和iOS都有效, 大致是这样:
举个例子, 假设你的外壳app加了个新的插件 - 你应该会更新外壳app. 为了防止用户下载了不适合他现有外壳app的web内容 - 你应该设置 min_native_interface
这个值.
比如, 我们app里的chcp.json是这样的:
{ "content_url": "https://5027caf9.ngrok.com", "release": "2015.09.01-13.30.35", "min_native_interface": 10}
外壳app的build版本是 13
.
某个时候,web内容有了新的release发布:
{ "content_url": "https://5027caf9.ngrok.com", "release": "2015.09.05-12.20.15", "min_native_interface": 15}
插件加载到这段json的时候, 发现 min_native_interface
比当前外壳app的build号要大 - 它就不会下载web内容. 而是触发一个 chcp_updateLoadFailed
错误通知, 告诉用户需要升级外壳app了. 更多内容请看 从应用商店请求app升级 小节.
备注: 目前你还不能为不同平台指定不同的 min_native_interface
. 如果需要以后可以支持.
指定了什么时候安装web内容更新. 支持的值有:
start
- app启动时安装更新. 默认值.
resume
- app从后台切换过来的时候安装更新.
now
- web内容下载完毕即安装更新.
你可以用JavaScript禁止自动安装. 请看 JavaScript module 小节.
apk包名. 如果指定了 - 引导用户到 Google Play Store 的app页面.
ios应用标识号, 比如: id345038631
. 如果指定了 - 引导用户到 App Store 的app页面.
内容清单描述了web项目所有文件的状态.
[ { "file": "index.html", "hash": "5540bd44cbcb967efef932bc8381f886" }, { "file": "css/index.css", "hash": "e46d9a1c456a9c913ca10f3c16d50000" }, { "file": "img/logo.png", "hash": "7e34c95ac701f8cd9f793586b9df2156" }, { "file": "js/index.js", "hash": "0ba83df8459288fd1fa1576465163ff5" } ]
根据它,插件才知道什么文件被移除了, 什么文件更新或新增了. 于是:
更新阶段,从服务端下载所有web内容文件;
安装阶段,删除服务端不存在(已移除)的文件.
这个文件应该放在 www
目录下,文件名是 chcp.manifest
.这个文件也被打包到了外壳app内.
同样的, 清单文件要放到 content_url
(app配置 Application config中指定的)指定的目录下. 比如, 如果你的 content_url
是 https://somedomain.com/www
, 这个清单文件的url就必须是 https://somedomain.com/www/chcp.manifest
.
生成 chcp.manifest
文件可以执行命令行客户端的 build
命令 (在cordova项目根目录下执行):
cordova-hcp build
相对于 www
的路径(就是你存放web内容的地方).
比如, 你的web内容位于: /Workspace/Cordova/TestProject/www.
你的 file
值应该是相对于这个路径.
文件的 MD5 值. 用于检测自上次release以来。这个文件是否变更过. 还有用于检测app端下载的文件是否出错.
建议: 每次变更web内容后都应该更新 chcp.manifest
文件. 否则插件不会检测到任何更新.
就像在 Cordova 配置项 一节中说的 - 你可以在config.xml
文件里改变插件配置.
但是如果你想在使用build命令行的时候改变插件配置呢? 为了达到这个目的,你需要使用chcpbuild.options
文件.
文件必须位于 Cordova 项目根目录. 在这个文件里面,你指定(JSON格式) 所有你想改变 config.xml
文件的配置. 源文件 config.xml
(Cordova项目根目录) 不会发生变动, 我们改变的是 特定平台下的 config.xml
(在cordova build过程的 after_prepare
阶段).
比如, 你的Cordova项目是 /Cordova/TestProject
目录.config.xml
文件 (/Cordova/TestProject/config.xml
) 有下面的配置:
这时我们在 /Cordova/Testproject/
下创建 chcpbuild.options
文件,文件内容如下:
{ "dev": { "config-file": "https://dev.company_server.com/mobile/www/chcp.json" }, "production": { "config-file": "https://company_server.com/mobile/www/chcp.json" }, "QA": { "config-file": "https://test.company_server.com/mobile/www/chcp.json" } }
build app的时候, 转为开发要用的服务器, 可执行:
cordova build -- chcp-dev
结果就是, 特定拍下的 config.xml
文件(比如, /Cordova/TestProject/platforms/android/res/xml/config.xml
) 变成了这样:
你可能注意到了 - 我们用的命令有个 chcp-
. 这个必须, 这样插件才知道, 这个参数是为它设置的. 而且, 不会和其它插件的命令参数冲突.
如果你的app可以测试了 - 你可以用下面的命令build, 就指定了测试服务器:
cordova build -- chcp-QA
特定平台下的 config.xml
就会变成:
当我们需要上架app的时候 (Google Play, App Store) - 我们正常build:
cordova build --release
这样 config.xml
是不会改变的.
如果没有使用 chcpbuild.options
- 插件会使用 config.xml
里面默认的值.
默认情况下, 所有的 检查更新->下载->安装 过程都是插件在原生端自动进行的. 不需要其它js端代码. 然而, 这些过程也可以用js控制.
你可以:
监听更新相关的事件;
从服务端检查和下载新的web内容;
安装已下载的web内容;
更改插件配置;
让用户到应用商店下载新的外壳app.
比如, web内容已经下载并可以安装了,会有事件通知, 或者出错了导致安装新的web内容失败了.
监听事件像这样:
document.addEventListener(eventName, eventCallback, false); function eventCallback(eventData) { // do something }
错误事件有详细错误信息. 像这样:
function eventCallback(eventData) { var error = eventData.details.error; if (error) { console.log('Error with code: ' + error.code); console.log('Description: ' + error.description); } }
可用的事件如下:
chcp_updateIsReadyToInstall
- web内容已经下载并可以安装时触发.
chcp_updateLoadFailed
- 插件无法下载web更新时触发. 详细错误信息在事件参数里.
chcp_nothingToUpdate
- 无可用更新下载时触发.
chcp_updateInstalled
- web内容安装成功时触发.
chcp_updateInstallFailed
- web内容安装失败时触发. 详细错误信息在事件参数里.
chcp_nothingToInstall
-无可用更新安装时触发.
chcp_assetsInstalledOnExternalStorage
- 插件成功把app内置的web内容拷贝到外置存储中时触发. 你可能需要开发调试时用到这个事件,也许不会.
chcp_assetsInstallationError
-插件无法拷贝app内置的web内容到外置存储中时触发. 如果此事件发生了 - 插件不再工作. 也许是设备没有足够的存储空间导致. 详细错误信息在事件参数里.
该举一些简单的例子了. 假设我们有个 index.js
文件, 它被 index.html
引用.
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); }, // deviceready Event Handler onDeviceReady: function() { console.log('Device is ready for work'); } }; app.initialize();
这个和cordova默认创建的 index.js
文件很像. 监听 chcp_updateIsReadyToInstall
事件如下:
bindEvents: function() { // ...some other events subscription code... document.addEventListener('chcp_updateIsReadyToInstall', this.onUpdateReady, false); },
编写事件处理函数:
// chcp_updateIsReadyToInstall Event Handler onUpdateReady: function() { console.log('Update is ready for installation'); }
index.js
结果如下:
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();
这样我们就知道了web内容什么时候下载完毕并可以安装了. 通过 JavaScript 模块我们可以让插件即时安装web更新, 否则将在下次启动app时安装.
使用js代码,让插件检查更新:
chcp.fetchUpdate(updateCallback); function updateCallback(error, data) { // do some work }
回调有2个参数:
error
- 如果检查失败,有error参数; null
表示一切正常;
data
- 额外的 数据, 原生端提供. 暂时可以忽略.
我们假设 index.html
有一些按钮, 按下它可以检查更新. 我们需要这样写代码:
监听button的 click
事件.
当点击button时调用chcp.fetchUpdate()
.
处理更新事件的结果.
我们来改 index.js
代码:
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); }, // deviceready Event Handler onDeviceReady: function() { // Add click event listener for our update button. // We do this here, because at this point Cordova modules are initialized. // Before that chcp is undefined. document.getElementById('myFetchBtn').addEventListener('click', app.checkForUpdate); }, checkForUpdate: function() { chcp.fetchUpdate(this.fetchUpdateCallback); }, fetchUpdateCallback: function(error, data) { if (error) { console.log('Failed to load the update with error code: ' + error.code); console.log(error.description); } else { console.log('Update is loaded'); } } }; app.initialize();
注意: 即使你在fetchUpdate
回调里处理了,相关的更新事件还是会触发并广播的.
调用:
chcp.installUpdate(installationCallback); function installationCallback(error) { // do some work }
如果安装失败 - error
参数会有错误详细信息. 否则- 为 null
.
现在让我们来继续上面的代码,处理web内容下载完后的安装.
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); }, // deviceready Event Handler onDeviceReady: function() { // Add click event listener for our update button. // We do this here, because at this point Cordova modules are initialized. // Before that chcp is undefined. document.getElementById('myFetchBtn').addEventListener('click', app.checkForUpdate); }, checkForUpdate: function() { chcp.fetchUpdate(this.fetchUpdateCallback); }, fetchUpdateCallback: function(error, data) { if (error) { console.log('Failed to load the update with error code: ' + error.code); console.log(error.description); return; } console.log('Update is loaded, running the installation'); chcp.installUpdate(this.installationCallback); }, installationCallback: function(error) { if (error) { console.log('Failed to install the update with error code: ' + error.code); console.log(error.description); } else { console.log('Update installed!'); } } }; app.initialize();
注意: 即使你在
installUpdate
回调里处理了,相关的更新事件还是会触发并广播的
正常情况下,所有的插件配置都在 config.xml
. 但是你可以用js动态改变.
通过下面的代码实现:
chcp.configure(options, callback); function callback(error) { // do some work }
支持的有:
config-file
- application config(chcp.json) 的url. 如果设置了 - 这个url将会被用于检查更新,而不是config.xml
中的值.
auto-download
- 设为 false
你可以禁止插件自动检测web内容更新并下载.
auto-install
- 设为 false
你可以禁止插件自动安装web更新.
这些需要在 deviceready
事件中设置. 你应该在每个页面加载的时候处理,
假如你一开就打算手动更新和下载安装 - 你应该在config.xml中设置
而不是js端动态设置.
比如, 我们在config.xml禁用了 auto-download
and auto-install
. 然后某个时间点 config-file
改变了, 但是我们不想从原有的url检测和下载web更新. 此时, 我们应该这样:
发布新版本的web内容, 它们可以用于最初的 config-file
url.
在新的版本 index.js
文件中,内容像这样:
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); }, // deviceready Event Handler onDeviceReady: function() { // change plugin options app.configurePlugin(); }, configurePlugin: function() { var options = { 'config-file': 'https://mynewdomain.com/some/path/mobile/chcp.json' }; chcp.configure(options, configureCallback); }, configureCallback: function(error) { if (error) { console.log('Error during the configuration process'); console.log(error.description); } else { console.log('Plugin configured successfully'); app.checkForUpdate(); } }, checkForUpdate: function() { chcp.fetchUpdate(this.fetchUpdateCallback); }, fetchUpdateCallback: function(error, data) { if (error) { console.log('Failed to load the update with error code: ' + error.code); console.log(error.description); return; } console.log('Update is loaded, running the installation'); chcp.installUpdate(this.installationCallback); }, installationCallback: function(error) { if (error) { console.log('Failed to install the update with error code: ' + error.code); console.log(error.description); } else { console.log('Update installed!'); } } }; app.initialize();
从 Application config app配置 小节我们知道,可以给web更新设置最小支持的外壳app版本 (min_native_interface
). 如果插件检查发现用户安装的外壳app版本比服务端新的web内容要求的版本要低 - 就会触发错误事件,错误码chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW
. 通过这个错误码我们可以引导用户去应用商店更新外壳app (Google Play /App Store).
这里你想怎么做就怎么做. 常用方法是显示一个对话框,问用户是否需要转到应用商店. 插件也提供了这个.
你需要做的是:
在 application config(chcp.json) 中设置t android_identifier
和 ios_identifier
.
js端监听相应事件,并在出现错误的时候调用 chcp.requestApplicationUpdate
方法.
举个例子. 简单起见我们监听 chcp_updateLoadFailed
事件.
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();
下载安装web更新的过程中可能会发生一些错误. 你可以从回调或者事件中匹配错误码( chcp.error
对象中有各种错误码).
v1.2.0版本之前 你需要用特定的错误码数字值. 此时开始, 请使用静态常量名,这样可以使代码可读,也可以减少对错误码具体数字的依赖. 比如, 不应该用 if (error.code == -2)
而用 if (error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW)
.
错误列表:
NOTHING_TO_INSTALL
- 请求插件安装更新,却没有更新需要安装. 值为 1
.
NOTHING_TO_UPDATE
- 没有可用web更新需要下载.值为 2
.
FAILED_TO_DOWNLOAD_APPLICATION_CONFIG
- 下载新的application config 文件(chcp.json)失败. 要么文件不存在或者网络问题.值为 -1
.
APPLICATION_BUILD_VERSION_TOO_LOW
- 外壳app的build版本号太低. 新的web内容需要新的外壳app. 用户需要更新外壳app.值为 -2
.
FAILED_TO_DOWNLOAD_CONTENT_MANIFEST
- 下载内容清单文件(chcp.manifest)失败. 文件chcp.manifest
必须位于 content_url
对应目录下, 和chcp.json一起.值为 -3
.
FAILED_TO_DOWNLOAD_UPDATE_FILES
- 下载web内容失败. 清单 chcp.manifest
中列出文件的必须都要位于 content_url
对应目录下. 还有, 检查各个文件的MD5是否正确. 值为 -4
.
FAILED_TO_MOVE_LOADED_FILES_TO_INSTALLATION_FOLDER
- 移动已下载的文件到安装目录时失败. 可能存储空间不足.值为 -5
.
UPDATE_IS_INVALID
- web内容已损坏. 安装之前,插件会检查已下载文件的MD5和 chcp.manifest
中的比较看是否一致. 如果不一致或者文件缺失 - 会发生此错误. 值为 -6
.
FAILED_TO_COPY_FILES_FROM_PREVIOUS_RELEASE
- 从上一版本拷贝www下文件到新版本www目录出错.可能存储空间不足.值为 -7
.
FAILED_TO_COPY_NEW_CONTENT_FILES
- 拷贝新文件到内容目录下失败.可能存储空间不足.值为 -8
.
LOCAL_VERSION_OF_APPLICATION_CONFIG_NOT_FOUND
- 加载本地chcp.json失败. 可能是用户手动删除了外部存储的web内容相关文件. 如果发生,会回滚至上移release版本的web内容.值为 -9
.
LOCAL_VERSION_OF_MANIFEST_NOT_FOUND
-加载本地chcp.manifest失败.可能是用户手动删除了外部存储的web内容相关文件. 如果发生,会回滚至上移release版本的web内容. 值为 -10
.
LOADED_VERSION_OF_APPLICATION_CONFIG_NOT_FOUND
-加载本地已下载的新版本的chcp.json失败.可能是用户手动删除了外部存储的web内容相关文件.如果发生 - app下次启动时会恢复. 值为 -11
.
LOADED_VERSION_OF_MANIFEST_NOT_FOUND
-加载本地已下载的新版本的chcp.manifest失败.可能是用户手动删除了外部存储的web内容相关文件.如果发生 - app下次启动时会恢复.值为 -12
.
FAILED_TO_INSTALL_ASSETS_ON_EXTERNAL_STORAGE
- 拷贝app内置web内容到外部存储时失败.可能存储空间不足. app初次启动时会执行此操作. 如果失败,插件就不再有用了. 值为 -13
.
CANT_INSTALL_WHILE_DOWNLOAD_IN_PROGRESS
- 调用 chcp.installUpdate
而 插件正在下载更新时触发. 你必须等待下载完毕. 值为 -14
.
CANT_DOWNLOAD_UPDATE_WHILE_INSTALLATION_IN_PROGRESS
- 调用 chcp.fetchUpdate
而安装过程在再执行. 你必须等待安装完毕. 值为 -15
.
INSTALLATION_ALREADY_IN_PROGRESS
- 调用 chcp.installUpdate
,而安装过程在再执行.值为 -16
.
DOWNLOAD_ALREADY_IN_PROGRESS
- 调用 chcp.fetchUpdate
,而 插件正在下载更新时触发. 值为 -17
.
ASSETS_FOLDER_IN_NOT_YET_INSTALLED
- 调用 chcp
方法, 而插件正在拷贝app内置web内容到外部存储时触发. 只可能在app初次启动时发生. 最后这个错误会被移除.值为 -18
.
好多同学都测试不成功,大家不要想太复杂了,我再简要概括一下:
chcp.json文件中的content_url为服务器项目的地址加端口号
config.xml为服务器项目地址加端口号再加上/chcp.json
每次修改完文件后,必须将【修改的文件】和【chcp.manifest文件】一并复制到服务器项目中进行覆盖。
将服务器中的chcp.json文件中的【"release": "2016.08.04-18.04.06"】时间改为当前时间。
3 和 4是最重要的,不然热更新就不起作用。
最后你们不要在纠结cordova-hcp server,这个东西就是在开发的时候启动用来监听文件的修改,如果有文件修改,就对应在chcp.manifest中修改该文件的hash值。
还没完,为了更清楚的了解热更新是怎么回事,这里我画了一张图。
[热更新的流程解析]
app启动
从服务器请求chcp.json文件(会覆盖本地chcp.json文件)。
服务器返回chcp.json文件与app里的chcp.json文件做对比,判断两个文件中的release时间。
如果服务器chcp.json文件的release时间大于app里chcp.json的release时间(说明新的资源)
如果有新的资源,再次发送一个请求,请求服务器的chcp.manifest文件(会覆盖本地chcp.json文件)。
服务器返回chcp.manifest文件与app里的chcp.manifest文件内容做对比。
如果有不一样的hash值。
对服务器请求新的资源。
请求成功的资源将覆盖本地资源。
这里通过对app进行抓包,来分析热更新是怎样进行应用内更新的。注意看1~8,我是没有对服务器资源进行更新的。直到第9个请求的时候:9,10,11连续发送了3个请求。
[热更新的抓包图]
第9个请求将服务器的chcp.json文件请求回来后判断时间是大于app的chcp.json时间的
然后发送了第10个请求,chcp.manifest,与本地chcp.manifest文件做对比
其中我只改了一个login.html,所以这里对login.html重新加载覆盖。
一直有人问我,用不了,不会用之类的。我在这说一下
用 cordova-hcp server 命令启动 hcp 服务器的时候,会看到如上图的local server 和public server,这两个地址是不能自己定义的
类似“https://f5f6894c.ngrok.io” 这个地址貌似并没有用,也许只是国内没法用吧
所以,我建议不要用 cordova-hcp server,你需要自己部署一个服务器,托管 www 下的 web 内容
Local Development Add-on 这个扩展也可以不要,记得自己生成新的apk/ipa之前要改config.xml的version(android-versionCode/ios-CFBundleVersion)就行了
其实 cordova-hcp server 启动的那个服务器,就是多了2个功能:
1、检测到 www 变更后,自动生成清单文件 chcp.manifest
2、自动实时推送变更到app端
用了你自己的服务器之后,这2个功能都没了。所以
1、每次更改 www 下的 web 内容之后,一定要手动用 cordova-hcp build(在corodva项目根目录下执行), 生成清单文件 chcp.manifest
2、app 只能在每次启动的时候,才能检查有无内容更新。有更新就会在后台下载,等到下次启动 app 才应用更新。(也就是要重启app 2次才能看到效果)
chcp.json 这个文件的内容也要改下,把 update 改为 "start",比如
{
"update": "start",
"content_url": "http://10.0.0.100/HCP/",
"release": "2016.04.28-10.14.32"
}
chcp.json 的功能,不懂的看上面翻译
{
"update": "start",
"content_url": "http://10.0.0.100/HCP/"
}
常见问题解决办法:
1、如果用安卓模拟器测试的,请确保手机模拟器的时区、时间和服务器时区、时间一致。否则,版本号对不上,就检测不到更新了
欢迎加入Sencha Touch + Phonegap交流群
1群:194182999 (满)
2群:419834979
共同学习交流(博主QQ:479858761)