CodePush 流程 偏iOS

作者: 一本大书 | 来源:发表于2019-04-11 18:34 被阅读7次

    前言

    本文从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];
    

    相关文章

      网友评论

        本文标题:CodePush 流程 偏iOS

        本文链接:https://www.haomeiwen.com/subject/pstxwqtx.html