在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.102 和 127.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 测试插件,这个只作为测试使用,实际上没有什么价值。
在项目根目录下执行$ 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