Ionic2跨平台项目(五)热修复(正儿八经Android)

时间:2023-02-04 08:08:39

原文地址:http://blog.csdn.net/lovelyelfpop/article/details/50848524#local-development-add-on



插件地址:https://github.com/nordnet/cordova-hot-code-push


下面是我对GitHub项目readme的翻译

——————————————————————————————————————————————


Cordova Hot Code Push Plugin

此插件提供了可以使cordova app自动更新web内容的功能。​基本上, 你App中所有位于 www 目录内的文件都可以被自动更新.

当你重新发布新的app时-重新打包了web内容: html 文件, JavaScript 代码, 图片等等. 一般有两种方式进行升级:

  1. 在appstore中上架新的app. 但是耗时比较长. 

  2. 牺牲所有原生功能,每次打开都从远端网站加载. 但是如果没有网络,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 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内容变更,并直接部署新版本. 

当然,你也可以不用这个命令行客户端, 只是用了它会更加方便.

从低版本迁移

从 v1.0.x 到 v1.1.x

在版本 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 项目, 然后:

  1. 在 Build Settings,设置 Embedded Content Contains Swift Code 为 NO.

  2. 打开 <YOUR_PROJECT_NAME>-Prefix.pch , 移除 #import <YOUR_PROJECT_NAME>-Swift.h. 比如: 

    #ifdef __OBJC__
    #import "TestProject-Swift.h"
    #endif
  3. 重新build, 检查是否正常. 

Cordova 项目快速向导

此向导展示了在开发中如何快速使用这个插件. 我们需要添加 开发扩展 ,需要 Xcode 7, 尽管hot code push plugin插件自身可以支持低版本xcode.

  1. 创建新的Cordova项目,并添加android和iOS platform:

    cordova create TestProject com.example.testproject TestProjectcd ./TestProject
    cordova platform add android
    cordova platform add ios

    或者可以用一个已有的项目. 

  2. 添加插件: 

    cordova plugin add cordova-hot-code-push-plugin
  3. 添加开发扩展: 

    cordova plugin add cordova-hot-code-push-local-dev-addon
  4. 安装 Cordova Hot Code Push 命令行客户端: 

    npm install -g cordova-hot-code-push-cli
  5. 启动本地服务器: 

    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
  6. 打开新的控制台, 进入到项目根目录,运行app: 

    cordova run

    稍等,app会安装到手机或者模拟器.

  7. 现在打开 TestProject/www/index.html , 做一些改动然后保存. 几秒种后你可以在手机或模拟器上看到更新后的页面. 

到此,你可以本地开发,新的web内容会自动在设备上更新,而无需重新启动app查看效果.

Ionic 项目快速向导

此向导展示了在开发中如何快速使用这个插件. 我们需要添加 开发扩展 ,需要 Xcode 7, 尽管hot code push plugin插件自身可以支持低版本xcode.

  1. 创建新的Ionic项目,并添加android和iOS platform:

    ionic start TestProject blankcd ./TestProject
    ionic platform add android
    ionic platform add ios

    Or use the existing one. 

  2. 添加插件: 

    ionic plugin add cordova-hot-code-push-plugin
  3. 添加开发扩展:

    ionic plugin add cordova-hot-code-push-local-dev-addon
  4. 安装 Cordova Hot Code Push 命令行客户端

    npm install -g cordova-hot-code-push-cli
  5. 启动本地服务器:

    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
  6. 打开新的控制台, 进入到项目根目录,运行app

    ionic run

    稍等,app会安装到手机或者模拟器.

  7. 现在打开 TestProject/www/index.html , 做一些改动然后保存. 几秒种后你可以在手机或模拟器上看到更新后的页面.

到此,你可以本地开发,新的web内容会自动在设备上更新,而无需重新启动app查看效果.

更新机制的流程图

先防止所有的配置相关的内容弄得你稀里糊涂 - 先来看看此插件的实现更新功能的流程图. 应该没有技术细节.

Ionic2跨平台项目(五)热修复(正儿八经Android)

  1. 用户打开你的app. 

  2. 插件初始化,在后台进程启动 升级加载器(update loader).

  3. Update loader  从 config.xml 取 config-file 配置(一个url),并从此url加载一段 JSON 配置.  然后它把这段JSON配置中的 release 版本号 和当前app 已经安装的进行比较. 如果不同 - 进入下一步.

  4. Update loader 使用app配置(application config)中的 content_url ,去加载清单文件(manifest). 它会找出自上次升级以来,哪些文件需要更新. 

  5. Update loader 从 content_url下载更新文件.

  6. 如果一切顺利 - 发出一个"升级文件已经准备好,可以安装了"的通知. 

  7. 升级文件已安装, app重新进入更新过的页面. 

当然, 还有其他的细节, 不过你已经有了大致的思路.

web内容是如何存储和更新的

每一个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文件, 发生情况如下:

  1. 在外部存储创建了一个以新的 release 版本号为名字的目录/sdcard/some_path/2016.01.03-10.45.01/.

  2. 目录里面 - 又创建了一个 update 目录 : /sdcard/some_path/2016.01.03-10.45.01/update/.

  3. 所有根据 chcp.manifest 更新的文件 都被下载到了这个 update 目录内.

  4. 新的 chcp.manifest 和 chcp.json 也被放到了 update 目录内.

  5. 新的web内容已准备安装. 

安装更新的时候:

  1. 插件从当前正在使用的release版本 目录内拷贝 www 下所有内容到 新的 release 版本目录下. 用我们的例子就是:从 /sdcard/some_path/2015.12.01-12.01.33/www/ 拷贝所有文件到 /sdcard/some_path/2016.01.03-10.45.01/www/.

  2. update 目录下拷贝新的web内容和配置文件,到 www 目录下: /sdcard/some_path/2016.01.03-10.45.01/update/ -> /sdcard/some_path/2016.01.03-10.45.01/www/.

  3. 移除 /sdcard/some_path/2016.01.03-10.45.01/update/ 目录,因为我们不再使用了.

  4. 加载新的release版本index.html: /sdcard/some_path/2016.01.03-10.45.01/www/index.html.

至此,插件会从新的release加载页面, 而旧的release则会作为一个备份留下来,以防万一.

Cordova Hot Code Push 命令行客户端

Cordova Hot Code Push 命令行客户端 是一个命令行工具,以便你web内容的开发.

它可以:

  • 生成 chcp.json 和 chcp.manifest 文件, 这样你就不用手动去创建; 

  • 运行本地服务,开发时可以检测更新,并发布新的release版本,使得可以再设备上实时更新web内容; 

  • 部署你的web内容到外部服务器上. 

当然, 你可以不使用这个命令行工具. 只是用了它会更方便一些.

本地开发扩展

当你本地开发app时 - 一般做法类似:

  1. web项目做一些更改. 

  2. 执行 cordova run 启动app.

  3. 稍等一会查看运行结果. 

即使很小的变更也需要打包重装app. 耗时比较久,比较麻烦.

为了提升速度 - 你可以使用本地开发扩展 Hot Code Push Local Development Add-on. 安装很简答:

  1. 添加此cordova插件. 

  2. 启动本地服务 cordova-hcp server.

  3. 在你的项目的config.xml 文件中 <chcp /> 块下添加 <local-development enabled="true" />

  4. 启动app. 

这样, 所有web内容的变更都会被插件检测到, 并直接更新显示到app上,而不需要重启app.

只有在添加了新的cordova插件时你才会重启app.

重要: 你应该只在开发状态下使用此扩展. 发布外壳app的时候,应该移除此扩展: cordova plugin remove cordova-hot-code-push-local-dev-addon.

Cordova 配置项

你应该知道, Cordova 使用 config.xml 文件配置不同项目: app名字, 描述, 起始页面,等等. 使用config.xml文件,你也可以为此插件配置.

这些配置位于 <chcp> 块. 比如:

<chcp>
<config-file url="https://5027caf9.ngrok.com/chcp.json"/>
</chcp>
config-file

定义了一个 URL,指定了需要从哪里加载app配置(application config,就是chcp.json). URL 在 url 属性中声明. 此项必须.

以防万一,开发的时候, 如果 config-file 没有定义 - 会自动设为本地服务上 chcp.json 的路径.

auto-download

自动下载web内容更新. 默认是自动, 如果你想手动下载web内容更新,你可以使用 JavaScript 模块(下面有).

禁用自动下载可以设置 config.xml:

<chcp>
<auto-download enabled="false" />
</chcp>

默认是 true.

auto-install

自动安装. web内容更新. 默认是自动, 如果你想手动安装web内容更新,你可以使用 JavaScript 模块(下面有).

禁用自动安装可以设置 config.xml:

<chcp>
<auto-install enabled="false" />
</chcp>

默认是 true.

配置文件

此插件用到2个配置文件:

  • app配置 Application config - 包含最新的release信息: release 版本号, 最低需要的外壳app版本号,等等. 文件名 chcp.json

  • Web内容清单 Content manifest - 包含所有web内容文件的名字和MD5值. 文件名 chcp.manifest

这两个文件必须. 他们描述了是否有新的release版本,以及文件更新时的比较.

还有一个build 可选参数 (build options) 文件, 可以再执行cordova
build
 命令时指定插件的配置.

Application config app配置

包含最新版本的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. 更多内容请阅读 命令行客户端的文档.

content_url

服务端URL, 也就是你所有web内容文件的位置. 插件会把它作为下载新的清单文件、新的web内容文件的 base url. 此项必须.

release

任何字符串. 每次release应该唯一. 插件基于这个才知道有没有新版本release. 此项必须.

重要: 插件只比较release字符串是否相等, 如果不等,就认为服务端有新版本.(不会比较大小)

min_native_interface

所需最小的外壳app版本. 这是app的build版本号,是个整型数字, 不是应用商店中看到的形如"1.0.0"字符串.

在 config.xml中,这样指定build版本号:

<widget id="io.cordova.hellocordova"
version="1.0.1"
android-versionCode="7"
ios-CFBundleVersion="3">
  • 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都有效, 大致是这样:

<widget id="io.cordova.hellocordova"
version="1.0.1"
android-versionCode="7"
ios-CFBundleVersion="70">

举个例子, 假设你的外壳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 . 如果需要以后可以支持.

update

指定了什么时候安装web内容更新. 支持的值有:

  • start - app启动时安装更新. 默认值.

  • resume - app从后台切换过来的时候安装更新.

  • now - web内容下载完毕即安装更新.

你可以用JavaScript禁止自动安装. 请看 JavaScript module 小节.

android_identifier

apk包名. 如果指定了 - 引导用户到 Google Play Store 的app页面.

ios_identifier

ios应用标识号, 比如: id345038631. 如果指定了 - 引导用户到 App Store 的app页面.

Content manifest内容清单

内容清单描述了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
file

相对于 www 的路径(就是你存放web内容的地方).

比如, 你的web内容位于:  /Workspace/Cordova/TestProject/www.  你的 file 值应该是相对于这个路径.

hash

文件的 MD5 值. 用于检测自上次release以来。这个文件是否变更过. 还有用于检测app端下载的文件是否出错.

建议: 每次变更web内容后都应该更新 chcp.manifest 文件. 否则插件不会检测到任何更新.

Build options build设置

就像在 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) 有下面的配置:

<chcp>
<config-file url="https://company_server.com/mobile/www/chcp.json" />
</chcp>

这时我们在 /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>
<config-file url="https://dev.company_server.com/mobile/www/chcp.json"/>
</chcp>

你可能注意到了 - 我们用的命令有个 chcp-. 这个必须, 这样插件才知道, 这个参数是为它设置的. 而且, 不会和其它插件的命令参数冲突.

如果你的app可以测试了 - 你可以用下面的命令build, 就指定了测试服务器:

cordova build -- chcp-QA

特定平台下的 config.xml 就会变成:

<chcp>
<config-file url="https://test.company_server.com/mobile/www/chcp.json"/>
</chcp>

当我们需要上架app的时候 (Google Play, App Store) - 我们正常build:

cordova build --release

这样 config.xml 是不会改变的.

如果没有使用 chcpbuild.options  - 插件会使用 config.xml 面默认的值.

JavaScript 模块

默认情况下, 所有的 检查更新->下载->安装 过程都是插件在原生端自动进行的. 不需要其它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 有一些按钮, 按下它可以检查更新. 我们需要这样写代码:

  1. 监听button的 click 事件.

  2. 当点击button时调用chcp.fetchUpdate() .

  3. 处理更新事件的结果. 

我们来改 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 回调里处理了,相关的更新事件还是会触发并广播的.

安装web更新

调用:

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中设置

<chcp>
<auto-download enabled="false" />
<auto-install enabled="false" />
</chcp>

而不是js端动态设置.

比如, 我们在config.xml禁用了 auto-download and auto-install  . 然后某个时间点 config-file 改变了, 但是我们不想从原有的url检测和下载web更新. 此时, 我们应该这样:

  1. 发布新版本的web内容, 它们可以用于最初的 config-file url.

  2. 在新的版本 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();


引导用户去应用商店更新外壳app

从 Application config app配置 小节我们知道,可以给web更新设置最小支持的外壳app版本 (min_native_interface ). 如果插件检查发现用户安装的外壳app版本比服务端新的web内容要求的版本要低 - 就会触发错误事件,错误码chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW. 通过这个错误码我们可以引导用户去应用商店更新外壳app (Google Play /App Store).

这里你想怎么做就怎么做. 常用方法是显示一个对话框,问用户是否需要转到应用商店. 插件也提供了这个.

你需要做的是:

  1. 在 application config(chcp.json) 中设置t android_identifier 和  ios_identifier.

  2. 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.




关于热更新的流程解析

好多同学都测试不成功,大家不要想太复杂了,我再简要概括一下:

  1. chcp.json文件中的content_url为服务器项目的地址加端口号

  2. config.xml为服务器项目地址加端口号再加上/chcp.json

  3. 每次修改完文件后,必须将【修改的文件】和【chcp.manifest文件】一并复制到服务器项目中进行覆盖。

  4. 将服务器中的chcp.json文件中的【"release": "2016.08.04-18.04.06"】时间改为当前时间。

3 和 4是最重要的,不然热更新就不起作用。

最后你们不要在纠结cordova-hcp server,这个东西就是在开发的时候启动用来监听文件的修改,如果有文件修改,就对应在chcp.manifest中修改该文件的hash值。

还没完,为了更清楚的了解热更新是怎么回事,这里我画了一张图。

[热更新的流程解析]

Ionic2跨平台项目(五)热修复(正儿八经Android)

  1. app启动

  2. 从服务器请求chcp.json文件(会覆盖本地chcp.json文件)。

  3. 服务器返回chcp.json文件与app里的chcp.json文件做对比,判断两个文件中的release时间。

  4. 如果服务器chcp.json文件的release时间大于app里chcp.json的release时间(说明新的资源)

  5. 如果有新的资源,再次发送一个请求,请求服务器的chcp.manifest文件(会覆盖本地chcp.json文件)。

  6. 服务器返回chcp.manifest文件与app里的chcp.manifest文件内容做对比。

  7. 如果有不一样的hash值。

  8. 对服务器请求新的资源。

  9. 请求成功的资源将覆盖本地资源。


案例

这里通过对app进行抓包,来分析热更新是怎样进行应用内更新的。注意看1~8,我是没有对服务器资源进行更新的。直到第9个请求的时候:9,10,11连续发送了3个请求。

[热更新的抓包图]

Ionic2跨平台项目(五)热修复(正儿八经Android)

  • 第9个请求将服务器的chcp.json文件请求回来后判断时间是大于app的chcp.json时间的

  • 然后发送了第10个请求,chcp.manifest,与本地chcp.manifest文件做对比

  • 其中我只改了一个login.html,所以这里对login.html重新加载覆盖。



一直有人问我,用不了,不会用之类的。我在这说一下

Ionic2跨平台项目(五)热修复(正儿八经Android)

用 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",比如

[javascript] view plain copy
  1. {  
  2.   "update""start",  
  3.   "content_url""http://10.0.0.100/HCP/",  
  4.   "release""2016.04.28-10.14.32"  
  5. }  

chcp.json 的功能,不懂的看上面翻译


可以在 cordova 项目根目录下放一个 cordova-hcp.json,这是个模板文件
这样每次执行 cordova-hcp build, 就会利用这个模板生成新的 chcp.json,而不用手动更改 www/chcp.json了。
cordova-hcp.json内容如下:
[javascript] view plain copy
  1. {  
  2.   "update""start",  
  3.   "content_url""http://10.0.0.100/HCP/"  
  4. }  




常见问题解决办法:

1、如果用安卓模拟器测试的,请确保手机模拟器的时区、时间和服务器时区、时间一致。否则,版本号对不上,就检测不到更新了