我是靠谱客的博主 爱听歌老鼠,这篇文章主要介绍热更新插件踩坑及优化 iOS热更新插件踩坑及优化 iOS,现在分享给大家,希望可以做个参考。

热更新插件踩坑及优化 iOS

1. 前言

本文所指的热更新插件为 cordova-hot-code-push-plugin

注意:此插件已于 2018-09-30 废弃,作者已停止维护

可以换用 微软维护的 cordova-plugin-code-push 或者 腾讯的 Bugly 应用升级 等其他热更新技术,这两个笔者也没有深入了解,读者有兴趣的话可以自行查阅相关资料。

Github:https://github.com/nordnet/cordova-hot-code-push

可以使用npm或者拉取Github上的源代码或者别人 fork 的分支使用(使用 http://gitpop2.herokuapp.com/ 查看Git库的所有fork)

本文基于 npm 上的 cordova-hot-code-push-plugin 使用,使用iOS项目测试,不过Android端应该也相差无几。

2. 简单了解

下面了解下插件的一些相关内容和原理。

  • chcp.manifest 存储 www 下文件信息(每个源码文件的哈希值,判断是否更新)

  • chcp.json 存储发布信息(发布时间、版本号)

  • APP启动时,启动线程连接热更新服务器,加载服务器上的热更新配置文件

  • 将本地和服务器的热更新配置文件对比。

    • 若版本号相同,发布时间不同,则通过 chcp.manifest 文件查找有更新的文件,下载安装;

    • 若版本号不同,提示用户下载安装新的App

  • 下载更新时间点: App 启动时下载或者由js控制

  • 安装更新时间点: 根据开发者选择的更新策略(Update method)不同,在对应的时间点进行安装,或者由js控制。

3. 使用

3.1 准备

首先准备一个Cordova项目,添加平台

复制代码
1
2
3
4
cordova create testHotCodePush com.examplde.testhotcodepush testHotCodePush cd testHotCodePush cordova platform add ios

然后 全局安装热更新客户端,用于生成热更新配置文件

复制代码
1
2
npm install cordova-hot-code-push-cli -g

3.2 添加插件,生成配置文件

添加插件:

复制代码
1
2
cordova plugin add cordova-hot-code-push-plugin

初始化:

在项目根目录下执行以下语句进行初始化操作,生成 cordova-hcp.json

复制代码
1
2
cordova-hcp init

执行命令后需输入一些参数:

  • Project name: 不能含有中文

  • 有关 Amazon 的可直接回车跳过

  • IOS app identifier:APP 在 App Store上的 ID。用于有新版本更新时,打开苹果应用商店中的网页。可不填

  • Android app identifier:Android的 App ID。用于有新版本更新时,打开Google应用商店中的网页。可不填

  • Update method: 更新策略,默认为 resume。可选值有:

    • now 下载完成后就安装更新
    • start 应用启动时安装更新。即,有更新时,用户启动App,开始下载更新,下次再启动App时安装更新。(实际测试似乎 start 与 resume 表现相同)
    • resume 应用启动或从后台唤醒时安装更新。
  • content_url: 热更新服务地址,写到 chcp.json 的上一级文件夹。

需要注意的是,我们要打开 cordova-hcp.json,添加配置 “min_native_interface”:1,这项配置代表web内容适应的最小外壳版本,是正整数,对应 android-versionCode 或者 ios-CFBundleVersion。

修改 config.xml

在Cordova 工程的 配置文件 config.xml 中添加热更新配置:

复制代码
1
2
3
4
5
6
7
<chcp> <auto-download enabled="true" /> <auto-install enabled="true" /> <native-interface version="1" /> <config-file url="这里写服务器chcp.json的地址" /> </chcp>

四个配置代表:

  • auto-download 是否允许插件自动下载更新,默认为true,可省略,可以设置为 false,通过js控制何时下载更新。
  • auto-install 是否允许插件自动安装更新,默认为true,可省略,可以设置为 false,通过js控制何时安装更新。
  • native-interface 表示当前的App 版本号,默认设置为1. 和 chcp.json 文件中的 min_native_interface 配合使用,该字段是 App 升级提示的依据。若安装包中的 native-interface 小于热更新服务器上的 min_native_interface ,App需要提示用户重新下载安装进行版本升级,此时插件不会自动下载www目录下的更新文件。对Cordova插件或原生代码有修改,重新打包发布时,需要更新 min_native_interface 。 热更新插件不支持为不同的移动端系统指定不同的min_native_interface 。
  • config-file 必填,由于指定热更新的 chcp.json 文件,需根据实际情况填写。

(注意: 改完要在项目根目录下执行 cordova build ios 命令,将修改同步到 platforms/ios 下的项目中)

拷贝代码到 www 文件夹下

使用以下命令,在 www 目录下生成热更新配置文件,即 chcp.json chcp.manifest

复制代码
1
2
cordova-hcp build

3.3 本地测试

本地可启动 http 服务,测试热更新功能

全局安装 serve 工具:

复制代码
1
2
npm install serve -g

在Cordova 根目录下执行命令,并指定端口号(可选,默认端口 5000)

复制代码
1
2
serve -p 8080

修改 www 下文件,比如在 html 中新增一行文本。

执行命令,更新热更新配置文件

复制代码
1
2
cordova-hcp build

在设备上,重启APP(可能需要重启两次),看是否修改成功。

3.4 js 调用

对于 Cordova 插件,为了了解插件暴露给 js 的方法,我们可以查看插件文件夹下www/**.js的内容。

此插件可以查看 www/chcp.js,可以看到,热更新插件暴露的接口有以下几个:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pluginNativeMethod = { // 初始化 INITIALIZE: 'jsInitPlugin', // 下载更新 FETCH_UPDATE: 'jsFetchUpdate', // 安装更新 INSTALL_UPDATE: 'jsInstallUpdate', // 修改热更新配置 CONFIGURE: 'jsConfigure', // 请求用户更新 REQUEST_APP_UPDATE: 'jsRequestAppUpdate', // 是否有更新需安装 IS_UPDATE_AVAILABLE_FOR_INSTALLATION: 'jsIsUpdateAvailableForInstallation', // 获取版本信息(APP版本、build版本、当前版本、上一个版本、准备安装的版本等) GET_INFO: 'jsGetVersionInfo' };

可以在 config.xml 中关闭自动下载和自动安装,使用 插件原生接口 在代码中灵活控制。

同时可以添加对应的监听,监听到事件后执行后续操作,如下:

复制代码
1
2
3
4
5
6
7
8
9
10
document.addEventListener(chcp.event.UPDATE_LOAD_FAILED, this.onUpdateLoadFailed, false) onUpdateLoadFailed(eventData){ let error = eventData.detail.error if(error && error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) { // 需要重新下载应用安装,更新原生代码 console.log("应用版本过低,需升级") } }

热更新插件的错误码事件有以下几种,起名见名知意,就不多做赘述:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
error: { NOTHING_TO_INSTALL: 1, NOTHING_TO_UPDATE: 2, FAILED_TO_DOWNLOAD_APPLICATION_CONFIG: -1, APPLICATION_BUILD_VERSION_TOO_LOW: -2, FAILED_TO_DOWNLOAD_CONTENT_MANIFEST: -3, FAILED_TO_DOWNLOAD_UPDATE_FILES: -4, FAILED_TO_MOVE_LOADED_FILES_TO_INSTALLATION_FOLDER: -5, UPDATE_IS_INVALID: -6, FAILED_TO_COPY_FILES_FROM_PREVIOUS_RELEASE: -7, FAILED_TO_COPY_NEW_CONTENT_FILES: -8, LOCAL_VERSION_OF_APPLICATION_CONFIG_NOT_FOUND: -9, LOCAL_VERSION_OF_MANIFEST_NOT_FOUND: -10, LOADED_VERSION_OF_APPLICATION_CONFIG_NOT_FOUND: -11, LOADED_VERSION_OF_MANIFEST_NOT_FOUND: -12, FAILED_TO_INSTALL_ASSETS_ON_EXTERNAL_STORAGE: -13, CANT_INSTALL_WHILE_DOWNLOAD_IN_PROGRESS: -14, CANT_DOWNLOAD_UPDATE_WHILE_INSTALLATION_IN_PROGRESS: -15, INSTALLATION_ALREADY_IN_PROGRESS: -16, DOWNLOAD_ALREADY_IN_PROGRESS: -17, ASSETS_FOLDER_IN_NOT_YET_INSTALLED: -18, NEW_APPLICATION_CONFIG_IS_INVALID: -19 }, // Plugin events event: { BEFORE_ASSETS_INSTALLATION: 'chcp_beforeAssetsInstalledOnExternalStorage', ASSETS_INSTALLATION_FAILED: 'chcp_assetsInstallationError', ASSETS_INSTALLED: 'chcp_assetsInstalledOnExternalStorage', NOTHING_TO_UPDATE: 'chcp_nothingToUpdate', UPDATE_LOAD_FAILED: 'chcp_updateLoadFailed', UPDATE_IS_READY_TO_INSTALL: 'chcp_updateIsReadyToInstall', BEFORE_UPDATE_INSTALLATION: 'chcp_beforeInstall', UPDATE_INSTALLATION_FAILED: 'chcp_updateInstallFailed', UPDATE_INSTALLED: 'chcp_updateInstalled', NOTHING_TO_INSTALL: 'chcp_nothingToInstall' },

4. 流程原理探究

这里,就对热更新的流程和原理进行简单探索。配置为自动下载和安装依赖。

为方便理解各文件所在路径,将项目文件夹命名为 projectDir。

cordova在安装后,都会在 projectDir/platforms/对应平台/www/plugins 下添加以插件ID命名的文件夹,里面的内容与 插件文件夹中www下的js文件( projectDir/plugins/插件文件夹/www/**.js)基本一致,只是最外层多了一层 cordova.define…。

所幸,热更新插件的js文件只有一个,很容易就可以找到,下面就以 chcp.js 为入口,根据一些主要的代码梳理一下插件初始化流程。

4.1 初始化流程

在 chcp.js 大概 22 行,可以看到以下代码:

复制代码
1
2
3
4
5
6
7
8
// chcp.js channel.onCordovaReady.subscribe(function() { // 初始化 js Event ensureCustomEventExists(); // 执行Native端 jsInitPlugin 接口 exec(nativeCallback, null, PLUGIN_NAME, pluginNativeMethod.INITIALIZE, []); });

subscribe 是响应式编程的写法,相当于Promise中的then,这里代表在 onCordovaReady 事件后,调用 subscribe 里的函数。

nativeCallback 则是原生接口执行成功的回调。

INITIALIZE 就是执行插件的 jsInitPlugin 方法。
但是,注意,在调用 jsInitPlugin 之前,会先调用插件的初始化方法: pluginInitialize
pluginInitialize 中主要是变量和监听的初始化。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// HCPPlugin.m -(void)pluginInitialize { // 插件的变量的初始化,如xml配置、文件结构等 [self doLocalInit]; // NSNotification 方式添加应用回到前台的监听看是否需要安装更新、以及下载和安装事件的监听 [self subscribeToEvents]; // install www folder if it is needed if ([self isWWwFolderNeedsToBeInstalled]) { [self installWwwFolder]; return; } // cleanup file system: remove older releases, except current and the previous one [self cleanupFileSystemFromOldReleases]; _isPluginReadyForWork = YES; [self resetIndexPageToExternalStorage]; [self loadApplicationConfig]; // install update if any exists if (_pluginXmlConfig.isUpdatesAutoInstallationAllowed && _pluginInternalPrefs.readyForInstallationReleaseVersionName.length > 0) { [self _installUpdate:nil]; } }

接下来,再看 jsInitPlugin 的主要操作,是执行了 _fetchUpdate 方法。

复制代码
1
2
3
4
5
6
7
// HCPPlugin.m - (void)jsInitPlugin:(CDVInvokedUrlCommand *)command { ... [self _fetchUpdate:nil withOptions:nil]; ... }

_fetchUpdate 方法中则 主要执行了 下载更新 操作,根据是否下载成功返回 YES/NO。

复制代码
1
2
3
4
5
6
- (BOOL)_fetchUpdate:(NSString *)callbackId withOptions:(HCPFetchUpdateOptions *)options { ... [[HCPUpdateLoader sharedInstance] executeDownloadRequest:request error:&error]; ... }

下载是否成功,给 js 的回调是在 HCPUpdateLoaderWorker.m 中,之前也提到在 插件的初始化方法 中初始化了对下载和安装的监听。

下面的代码只列出了下载成功的回调以作参考。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// HCPUpdateLoaderWorker.m - (void)runWithComplitionBlock:(void (^)(void))updateLoaderComplitionBlock { ... [self notifyUpdateDownloadSuccess:newAppConfig]; ... } /** * 发送通知,下载已完成,可以进行安装操作 * * @param config application config that was used for download */ - (void)notifyUpdateDownloadSuccess:(HCPApplicationConfig *)config { if (_complitionBlock) { _complitionBlock(); } NSNotification *notification = [HCPEvents notificationWithName:kHCPUpdateIsReadyForInstallationEvent applicationConfig:config taskId:self.workerId]; [[NSNotificationCenter defaultCenter] postNotification:notification]; }

下载成功后回调到 HCPPlugin.m 中

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// HCPPlugin.m /** * Method is called when update is loaded and ready for installation. * * @param notification captured notification with event details */ - (void)onUpdateIsReadyForInstallation:(NSNotification *)notification { ... // send notification to the associated callback CDVPluginResult *pluginResult = [CDVPluginResult pluginResultForNotification:notification]; if (_downloadCallback) { [self.commandDelegate sendPluginResult:pluginResult callbackId:_downloadCallback]; _downloadCallback = nil; } // send notification to the default callback [self invokeDefaultCallbackWithMessage:pluginResult]; // 如果更新策略为 now,立即更新 if (_pluginXmlConfig.isUpdatesAutoInstallationAllowed && newConfig.contentConfig.updateTime == HCPUpdateNow && ![HCPUpdateLoader sharedInstance].isDownloadInProgress && ![HCPUpdateInstaller sharedInstance].isInstallationInProgress) { [self _installUpdate:nil]; } } /** * 通知 JS 模块发生的事件. * For that we will use callback, received on plugin initialization stage. * * @param result message to send to web side * @return YES - 返回web页面; NO - otherwise */ - (BOOL)invokeDefaultCallbackWithMessage:(CDVPluginResult *)result { ... [self.commandDelegate sendPluginResult:result callbackId:_defaultCallbackID]; return YES; }

回调到 chcp.js 中的 nativeCallback 中,就会触发相对应的事件,js端可以添加对应的事件监听来进行处理。

4.2 HCPFileStructure

这是热更新插件执行热更新所需要的文件结构,还是很重要的。

主要有以下几个文件夹:

  • rootFolder: Library/Application Support/APP对应的bundle id/cordova-hot-code-push-plugin

  • contentFolder: rootFolder/对应的releaseVersion

  • wwwFolder: contentFolder/www 放置最终版本资源包

  • downloadFolder: contentFolder/update 放置下载文件的临时文件夹

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// HCPFilesStructure.h #import <Foundation/Foundation.h> /** * Model for plugins file structure. * Each release has it's own folder, so we need to initialize this object with release version. * 每一个版本都有自己的文件夹,我们需要为新的版本初始化这个对象。 * * @see HCPFileStructure */ @interface HCPFilesStructure : NSObject /** * Constructor. * * @param releaseVersion for what version this file structure * * @return object instance */ - (instancetype)initWithReleaseVersion:(NSString *)releaseVersion; /** * Absolute path to plugins working directory. */ @property (nonatomic, strong, readonly) NSURL *contentFolder; /** * Absolute path to the folder on the external storage where all web content is placed. * From this folder we will show web pages. * Think of this as an bundle folder on the external storage. */ @property (nonatomic, strong, readonly) NSURL *wwwFolder; /** * Absolute path to the temporary folder where we will put files during the update download. * 放置下载文件的临时文件夹 */ @property (nonatomic, strong, readonly) NSURL *downloadFolder; /** * Getter for the name of the application config file. */ @property (nonatomic, strong, readonly) NSString *configFileName; /** * Getter for the name of the manifest file. */ @property (nonatomic, strong, readonly) NSString *manifestFileName; /** * Get root folder for this plugin. * In it all releases are located. * 对应文件夹为: * Library/Application Support/APP对应的bundle id/cordova-hot-code-push-plugin * * @return url on the external storage to the plugins root folder. */ + (NSURL *)pluginRootFolder; /** * Default application config file name. * Should be equal to the config name, that is bundled with the app. * * @return default application config file name. */ + (NSString *)defaultConfigFileName; /** * Default name of the manifest file. * Should be equal to the manifest name, that is bundled with the app. * * @return default manifest file name. */ + (NSString *)defaultManifestFileName; @end

4.3 下载

下面,再来看一下下载操作。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// HCPUpdateLoaderWorker.m - (void)runWithComplitionBlock:(void (^)(void))updateLoaderComplitionBlock { _complitionBlock = updateLoaderComplitionBlock; // initialize before the run NSError *error = nil; // 加载本地配置文件 if (![self loadLocalConfigs:&error]) { [self notifyWithError:error applicationConfig:nil]; return; } HCPDataDownloader *configDownloader = [[HCPDataDownloader alloc] init]; // 读取服务器上的chcp.json ,内部实现是用的 NSURLSession [configDownloader downloadDataFromUrl:_configURL requestHeaders:_requestHeaders completionBlock:^(NSData *data, NSError *error) { HCPApplicationConfig *newAppConfig = [self getApplicationConfigFromData:data error:&error]; if (newAppConfig == nil) { // 获取新的配置为空,说明下载失败,返回通知给HCPPlugin,HCPPlugin会发送对应事件给js端 [self notifyWithError:[NSError errorWithCode:kHCPFailedToDownloadApplicationConfigErrorCode descriptionFromError:error] applicationConfig:nil]; return; } // 检查是否有更新 if ([newAppConfig.contentConfig.releaseVersion isEqualToString:_oldAppConfig.contentConfig.releaseVersion]) { [self notifyNothingToUpdate:newAppConfig]; return; } // 检查当前原生代码版本是否支持新的资源内容 if (newAppConfig.contentConfig.minimumNativeVersion > _nativeInterfaceVersion) { [self notifyWithError:[NSError errorWithCode:kHCPApplicationBuildVersionTooLowErrorCode description:@"Application build version is too low for this update"] applicationConfig:newAppConfig]; return; } // 读取服务器上的chcp.manifest NSURL *manifestFileURL = [newAppConfig.contentConfig.contentURL URLByAppendingPathComponent:_pluginFiles.manifestFileName]; [configDownloader downloadDataFromUrl:manifestFileURL requestHeaders:_requestHeaders completionBlock:^(NSData *data, NSError *error) { HCPContentManifest *newManifest = [self getManifestConfigFromData:data error:&error]; if (newManifest == nil) { // 获取chcp.manifest为空,说明下载失败,返回通知给HCPPlugin,HCPPlugin会发送对应事件给js端 [self notifyWithError:[NSError errorWithCode:kHCPFailedToDownloadContentManifestErrorCode descriptionFromError:error] applicationConfig:newAppConfig]; return; } // 对比服务器下载的和本地的manifest,看是否有文件修改, // manifestDiff 有四个变量 updateFileList(包含新增和修改的文件)、deletedFiles(新版本删除的文件)、addedFiles(新增)、changedFiles(修改); // 其中 addedFiles、changedFiles 只是赋值,并未使用 HCPManifestDiff *manifestDiff = [_oldManifest calculateDifference:newManifest]; if (manifestDiff.isEmpty) { // 如果没有文件修改,将新的chcp.json 和 chcp.manifest 存到本地,发送无更新通知给HCPPlugin,HCPPlugin会发送对应事件给js端 [_manifestStorage store:newManifest inFolder:_pluginFiles.wwwFolder]; [_appConfigStorage store:newAppConfig inFolder:_pluginFiles.wwwFolder]; [self notifyNothingToUpdate:newAppConfig]; return; } // switch file structure to new release _pluginFiles = [[HCPFilesStructure alloc] initWithReleaseVersion:newAppConfig.contentConfig.releaseVersion]; // 创建 downloadFolder [self createNewReleaseDownloadFolder:_pluginFiles.downloadFolder]; // 如果有文件新增或更新,就下载对应的文件到 downloadFolder, // 并将服务器的chcp.json 和 chcp.manifest 存到downloadFolder,发送下载成功通知给HCPPlugin,HCPPlugin会发送对应事件给js端 NSArray *updatedFiles = manifestDiff.updateFileList; if (updatedFiles.count > 0) { // 具体的下载可参考 HCPFileDownloader.m,是调用 NSSession 的 downloadTaskWithURL [self downloadUpdatedFiles:updatedFiles appConfig:newAppConfig manifest:newManifest]; return; } // 否则 - 只有需要删除的文件; // 将服务器的chcp.json 和 chcp.manifest 存到downloadFolder,发送下载成功通知给HCPPlugin,HCPPlugin会发送对应事件给js端 [_manifestStorage store:newManifest inFolder:_pluginFiles.downloadFolder]; [_appConfigStorage store:newAppConfig inFolder:_pluginFiles.downloadFolder]; [self notifyUpdateDownloadSuccess:newAppConfig]; }]; }]; }

下载成功:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// HCPPlugin.m /** * Method is called when update is loaded and ready for installation. * * @param notification captured notification with event details */ - (void)onUpdateIsReadyForInstallation:(NSNotification *)notification { // new application config from server HCPApplicationConfig *newConfig = notification.userInfo[kHCPEventUserInfoApplicationConfigKey]; NSLog(@"Update is ready for installation: %@", newConfig.contentConfig.releaseVersion); // 将版本信息保存到 NSUserDefault _pluginInternalPrefs.readyForInstallationReleaseVersionName = newConfig.contentConfig.releaseVersion; [_pluginInternalPrefs saveToUserDefaults]; // send notification to the associated callback CDVPluginResult *pluginResult = [CDVPluginResult pluginResultForNotification:notification]; if (_downloadCallback) { [self.commandDelegate sendPluginResult:pluginResult callbackId:_downloadCallback]; _downloadCallback = nil; } // 发送回调给js [self invokeDefaultCallbackWithMessage:pluginResult]; // 如果自动更新为 YES 并且 update method设置为 now,直接更新 if (_pluginXmlConfig.isUpdatesAutoInstallationAllowed && newConfig.contentConfig.updateTime == HCPUpdateNow && ![HCPUpdateLoader sharedInstance].isDownloadInProgress && ![HCPUpdateInstaller sharedInstance].isInstallationInProgress) { [self _installUpdate:nil]; } }

4.4 更新

下面,再来看一下更新操作。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// HCPInstallationWorker.m ... // 下面的注释以新版本和旧版本代指 HCPFilesStructure *_newReleaseFS; // 服务器上新版本的文件结构 HCPFilesStructure *_currentReleaseFS; // 当前设备上正在使用的版本的文件结构 ... - (void)runWithComplitionBlock:(void (^)(void))updateInstallationComplitionBlock { // 安装前,返回到js event [self dispatchBeforeInstallEvent]; NSError *error = nil; // 操作主要在 if 条件中 // initBeforeRun 查看新旧配置文件是否都在 // isUpdateValid 确认更新文件是否存在、更新文件的hash是否与chcp.manifest中的一致 // copyFilesFromCurrentReleaseToNewRelease 将旧版本 wwwFolder的内容 复制到 新版本 的wwwFolder // deleteUnusedFiles 删除新版本没有的文件 // moveDownloadedFilesToWwwFolder 将需要新增或更新的文件从 新版本的 downloadFolder 复制到 新版本的 wwwFolder if (![self initBeforeRun:&error] || ![self isUpdateValid:&error] || ![self copyFilesFromCurrentReleaseToNewRelease:&error] || ![self deleteUnusedFiles:&error] || ![self moveDownloadedFilesToWwwFolder:&error]) { NSLog(@"%@. Error code %ld", [error underlyingErrorLocalizedDesription], (long)error.code); // 删除 新版本的 contentFolder 内容 [self cleanUpOnFailure]; // 安装失败,发送安装失败通知给HCPPlugin,HCPPlugin会发送对应事件给js端 [self dispatchEventWithError:error]; return; } // 删除 新版本的 download文件夹的内容 [self cleanUpOnSucess]; // 保存新版本的 chcp.json chcp.manifest 到新版本的 wwwFolder [self saveNewConfigsToWwwFolder]; // 安装成功,发送安装成功通知给HCPPlugin,HCPPlugin会发送对应事件给js端 [self dispatchSuccessEvent]; }

更新成功:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// HCPPlugin.m /** * Method is called when update has been installed. * * @param notification captured notification with the event details */ - (void)onUpdateInstalledEvent:(NSNotification *)notification { // 更新appconfig _appConfig = notification.userInfo[kHCPEventUserInfoApplicationConfigKey]; // 将新版本置为当前版本,旧版本置为上一个版本 _pluginInternalPrefs.readyForInstallationReleaseVersionName = @""; _pluginInternalPrefs.previousReleaseVersionName = _pluginInternalPrefs.currentReleaseVersionName; _pluginInternalPrefs.currentReleaseVersionName = _appConfig.contentConfig.releaseVersion; [_pluginInternalPrefs saveToUserDefaults]; _filesStructure = [[HCPFilesStructure alloc] initWithReleaseVersion:_pluginInternalPrefs.currentReleaseVersionName]; CDVPluginResult *pluginResult = [CDVPluginResult pluginResultForNotification:notification]; // send notification to the caller from the JavaScript side of there was any if (_installationCallback) { [self.commandDelegate sendPluginResult:pluginResult callbackId:_installationCallback]; _installationCallback = nil; } // 发送回调给js [self invokeDefaultCallbackWithMessage:pluginResult]; // 重新加载html [self loadURL:[self indexPageFromConfigXml]]; // 删除旧版本文件 [self cleanupFileSystemFromOldReleases]; }

5. 问题

5.1 kCFErrorDomainCFNetwork error -1002

问题:

报错截图如下:
报错截图

-1002 对应的报错类型可以在 Xcode 中 使用快捷键 shift+command+o ,搜索 CFNetworkErrors,可以看到对应的报错为 kCFURLErrorUnsupportedURL

排查为 HCPUpdateRequest 的 configURL 为 nil,入口是 HCPPlugin.m 下的 jsInitPlugin,调用了 _fetchUpdate 方法,_fetchUpdate 取 options(js参数)、defaultFetchUpdateOptions(原生端配置)、_pluginXmlConfig(xml配置) 下不为空的 configUrl(优先级:options > defaultFetchUpdateOptions > _pluginXmlConfig ),但三个变量下的 configUrl 均为 nil,最终导致了此问题。
截图四

解决:其实是在修改cordova项目下的 config.xml 时,没有 执行 cordova build ios 导致的,执行完再运行到手机就可以了。

5.2

6. 优化点

6.1 应用首次安装

应用首次安装会提示用户授予网络权限,在这个过程中,由于无网络,热更新报错。

6.2 代码升级

代码中存在一些已经废弃的类和方法,可以进行升级

6.3

最后

以上就是爱听歌老鼠最近收集整理的关于热更新插件踩坑及优化 iOS热更新插件踩坑及优化 iOS的全部内容,更多相关热更新插件踩坑及优化内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部