前言
本文从React再到iOS源码对CodePush业务逻辑进行梳理。
在完成订制 CodePush 版本的过程中,笔者会对本文持续更新,补充各模块细节。如果读者同样对CodePush兴趣,欢迎讨论。
React
启动应用,调用CodePush.sync()进行更新请求,我们直接看sync()这个方法做了些什么事情
const sync = (() => {
let syncInProgress = false;
const setSyncCompleted = () => { syncInProgress = false; };
return (options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) => {
// 1. 对传入的参数进行校验
let syncStatusCallbackWithTryCatch, downloadProgressCallbackWithTryCatch;
if (typeof syncStatusChangeCallback === "function") {
syncStatusCallbackWithTryCatch = (...args) => {
try {
syncStatusChangeCallback(...args);
} catch (error) {
log(`An error has occurred : ${error.stack}`);
}
}
}
if (typeof downloadProgressCallback === "function") {
downloadProgressCallbackWithTryCatch = (...args) => {
try {
downloadProgressCallback(...args);
} catch (error) {
log(`An error has occurred: ${error.stack}`);
}
}
}
if (syncInProgress) {
typeof syncStatusCallbackWithTryCatch === "function"
? syncStatusCallbackWithTryCatch(CodePush.SyncStatus.SYNC_IN_PROGRESS)
: log("Sync already in progress.");
return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS);
}
syncInProgress = true;
// 2. 检查更新、下载、安装
const syncPromise = syncInternal(options, syncStatusCallbackWithTryCatch, downloadProgressCallbackWithTryCatch, handleBinaryVersionMismatchCallback);
syncPromise
.then(setSyncCompleted)
.catch(setSyncCompleted);
return syncPromise;
};
})();
syncInternal() 主要流程如下:
await CodePush.notifyApplicationReady();
// 1. 根据deploymentKey获取当前是否有新包
const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);
// 2. 下载中
const localPackage = await remotePackage.download(downloadProgressCallback);
// 3. 根据是否是“强制更新” 确定包的安装方式
resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;
// 4. 安装
await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
// 5. 安装完成 回调
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
});
iOS
1. 检查更新
checkForUpdate() 内调用原生方法 getConfiguration() 获取当前的版本号,并且判断当前是否有新版本 && 新版本是否兼容原生版本 等判断。
2. 下载
download() 内调用原生方法 downloadUpdate() 下载并解压压缩包。
下载的核心方法在
// CodePushPackage.m
+ (void)downloadPackage:(NSDictionary *)updatePackage
expectedBundleFileName:(NSString *)expectedBundleFileName
publicKey:(NSString *)publicKey
operationQueue:(dispatch_queue_t)operationQueue
progressCallback:(void (^)(long long, long long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback;
此方法代码量庞大,我对其逻辑做了梳理,大致如下。
- 删除之前的下载记录
- 下载更新包
- 解压更新包
- 复制解压后的 bundle 到 newUpdateFolderPath 目录下
3. 安装
install() 内调用原生方法 installUpdate()
安装实际调用的是下面这2个方法
// CodePushPackage.m
// 对解压好的包进行hash校验,确认无误后调用updateCurrentPackageInfo:
+ (BOOL)installPackage:(NSDictionary *)updatePackage
removePendingUpdate:(BOOL)removePendingUpdate
error:(NSError **)error;
// 安装的核心方法:实际就是将packageInfo写入到缓存,写入成功即表示安装成功。之后codepush去读取本地代码都是从这个文件路径去获取package的配置信息。
+ (BOOL)updateCurrentPackageInfo:(NSDictionary *)packageInfo
error:(NSError **)error;
4. Restart
在安装成功回调给 react 后,react 调用 CodePush.restartApp() 方法,去调用原生的方法:
// CodePush.m
RCT_EXPORT_METHOD(restartApp:(BOOL)onlyIfUpdateIsPending
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
重启:为bridge设置新的bundleURL
[super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"];
[super.bridge reload];
网友评论