热更新插件踩坑及优化 iOS

news/2024/7/20 21:02:24 标签: objective-c, 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项目,添加平台

cordova create testHotCodePush com.examplde.testhotcodepush testHotCodePush
cd testHotCodePush
cordova platform add ios

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

npm install cordova-hot-code-push-cli -g

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

添加插件:

cordova plugin add cordova-hot-code-push-plugin

初始化:

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

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 中添加热更新配置:

<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

cordova-hcp build

3.3 本地测试

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

全局安装 serve 工具:

npm install serve -g

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

serve -p 8080

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

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

cordova-hcp build

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

3.4 js 调用

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

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

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 中关闭自动下载和自动安装,使用 插件原生接口 在代码中灵活控制。

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

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("应用版本过低,需升级")
    }
}

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

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 行,可以看到以下代码:

// 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 中主要是变量和监听的初始化。

objective-c">// 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 方法。

objective-c">// HCPPlugin.m
- (void)jsInitPlugin:(CDVInvokedUrlCommand *)command {
	...
	[self _fetchUpdate:nil withOptions:nil];
	...
}

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

objective-c">- (BOOL)_fetchUpdate:(NSString *)callbackId withOptions:(HCPFetchUpdateOptions *)options {
	...
	[[HCPUpdateLoader sharedInstance] executeDownloadRequest:request error:&error];
	...
}

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

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

objective-c">// 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 中

objective-c">// 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 放置下载文件的临时文件夹

objective-c">//  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 下载

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

objective-c">// 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];
        }];
    }];
}

下载成功:

objective-c">// 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 更新

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

objective-c">// 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];
}

更新成功:

objective-c">// 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


http://www.niftyadmin.cn/n/741507.html

相关文章

【无标题】2022年施工员-设备方向-通用基础(施工员)考试模拟100题及模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2022施工员-设备方向-通用基础(施工员)考试题库是施工员-设备方向-通用基础(施工员)考试真题新版习题库&#xff01;2022年施工员-设备方向-通用基础(施工员)考试模拟100题及模拟考试依据施工员-设备方向-通用基础(施…

sql server 2008 java 连接字符串函数_java连接sql server 2008数据库代码

在实际项目(多数据源)中发现sqlserver连接中断后&#xff0c;无法支持自动重连&#xff0c;拿不到connect&#xff1a;1.首先通过我的监控页面发现 sqlserver配置的连接池 连接有效性检查类名 为 null。2.跟踪代码 com.alibaba.druid.pool.DruidDataSource.java initValidConne…

仿 微信/QQ 实现小程序功能 -IOS

仿 微信/QQ 实现小程序功能 -IOS 1. 需求 首先&#xff0c;来大致看一下 微信/QQ 小程序的功能。 Android端&#xff1a; 点击图标进入小程序&#xff0c;会新开一个任务栈&#xff0c;每个小程序一个任务栈&#xff08;有的机型所有小程序都在一个任务栈&#xff09;&…

2022年R2移动式压力容器充装(山东省)考试题模拟考试题库及在线模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2022R2移动式压力容器充装&#xff08;山东省&#xff09;操作考试题系R2移动式压力容器充装&#xff08;山东省&#xff09;练习题考前押题&#xff01;2022年R2移动式压力容器充装&#xff08;山东省&#xff09;考…

构建Docker Android开发基础镜像

构建Docker Android开发基础镜像 一、需求 项目有功能需要调用 npm命令。 后端代码中使用的是 **java.lang.Runtime **类调用命令&#xff0c;但是默认Docker基础镜像是 openjdk:8-jre-slim&#xff0c;并不包含nodejs环境。 经过数次试错&#xff0c;最终决定更换基础镜像…

【无标题】2022美容师(技师)操作证考试题库模拟考试平台操作

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2022美容师&#xff08;技师&#xff09;模拟考试题库为美容师&#xff08;技师&#xff09;模拟考试试题考前押题密卷&#xff01;2022美容师&#xff08;技师&#xff09;操作证考试题库模拟考试平台操作根据美容师…

1146 mysql_MySQL--ERROR 1146 (42S02):table doesn’t exist

ERROR 1146 (42S02): Table ‘xxx’ doesn’t exist 可能是很多人都遇到的问题&#xff0c;尤其在数据库迁移或备份的时候mysql数据目录结构mysql数据目录下有如下几个重要文件&#xff1a;ibdata1ib_logfile0ib_logfile1数据库xx 以及该目录下的一系列 .frm 文件其中ib_logfil…

创建 RTSP RTMP 视频源(Windows/Linux)

1. 前言 由于项目需要实现 视频识别图像分类&#xff0c;网上能用的视频流又很少&#xff0c;所以需要自己搭建视频源进行本地测试。 本文记录在 Windows 以及 Linux 系统分别创建 RTSP、RTMP 视频源 进行测试。 2. 准备 2.1 视频 我们要准备好一份 mp4 格式的视频&#xff…