react-native-code-push進階篇

lyxia_iOS發表於2017-08-20

之前寫了一篇關於react-native-code-push的入門使用篇:微軟的React Native熱更新 - 使用篇,真的是很簡單的使用,能熱更新成功就行了。這一篇通過在專案中實戰所遇到的問題,根據原始碼分析它的原理,來更深入的理解code-push。

這篇文章是在已經搭建好code-push環境(執行過npm install --save react-native-code-push@latest
react-native link react-native-code-push ,並安裝了code-push cli且成功登陸)為基礎下寫的,沒有使用CRNA來建立App。

部署與配置

部署(deployment)Test,Staging和Production

在真正的專案中,我們一般會分為開發版(Test),灰度版(Staging)和釋出版(Production),在Test中我一般是用來跟蹤code-push的執行,在Staging中其實是和Production是同樣的程式碼,但是當要熱修復線上版本時,先會發布熱更新到Staging版,在Staging測過後再通過promoting推到Production中去。

大致步驟:

  • 通過code-push app add MyAppIOS ios react-native來建立iOS端的App,或者通過code-push app add MyAppAndroid android react-native建立Android端的App。
  • 使用code-push app ls檢視是否新增成功,預設會建立兩個部署(deployment)環境:Staging和Production,可以通過code-push deployment ls MyAppIOS -k來檢視當前App所有的部署,-k是用來檢視部署的key,這個key是要方法原生專案中去的。
  • 新增一個Test部署環境:code-push deployment add MyAppIOS Test,新增成功後,就可以通過code-push deployment ls MyAppIOS -k來檢視Test部署環境下的key了。

經常使用code-push --h來檢視可以執行的操作

最後結果如下圖所示:

image.png
image.png

image.png
image.png

在原生專案中動態部署

在上面有提過需要把部署的key新增到原生專案中,這樣在不同的執行環境下動態的使用對應的部署key,例如在Staging下使用Stagingkey,在Relase下使用Productionkey,在Debug下不使用熱更新(如需在debug環境下測試code-push,可以在codePush.sync裡的option引數中動態修改部署key)。

在Android中動態部署key,並且在同一裝置同時安裝不同部署的Android包
有兩種方式:

  • 官方配置入口:github.com/Microsoft/r… you want to be able to install both debug and release builds simultaneously on the same device`中有提到在同一裝置同時安裝不同部署的Android包。
  • 第二種方式是通過資原始檔R.string來實現同樣的效果,在app/src中分別新增staging/res/valuesdebug/res/values兩個資料夾,然後複製app/src/main/res/value/strings.xml貼上到剛新建的兩個values目錄下,最後在程式碼中獲取key的方式為R.string.reactNativeCodePush_androidDeploymentKey

配置好後可以使用./gradlew assembleStaging來打包Staging下的apk,輸出目錄在./android/app/build/outputs/apk下,沒有在gradle中配置簽名安裝(adb install app-staging.apk)會出現如下錯誤:Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES] React native,關於gradle的buildType的使用:tools.android.com/tech-docs/n…

在iOS中動態部署key
官方配置入口:github.com/Microsoft/r…

在iPhone上同時安裝相同App的不同部署包
讓你的iOS應用在不同狀態(debug, release)有不同的圖示和標題

參考專案:github.com/lyxia/CodeP…

應用中經常遇到的技巧

1、分清楚 Target binary version 和 Label

image.png
image.png

label代表釋出的更新版本,Target binary version代表app的版本號。

2、使用patch打補丁,修改後設資料屬性。
使用場景:例如當你已經發布了一個更新,但是到有些情況下,比如--des需要修改,--targetBinaryVersion寫錯了,比如我的8.6.0寫成了8.6,然後在我釋出8.6.1新版的時候就會拉取8.6的版本更新,這個時候就可以code-push patch MyAppAndroid Production --label v4 --targetBinaryVersion 8.6.1

3、使用promote將Staging推到Production
使用場景:當你在指定的部署環境下測試更新時,例如Staging,測試通過後,想把這個更新發布到正式生產環境Production中,則可以使用code-push promote MyAppAndroid Staging Production,這時可以修改一些後設資料,例如--description--targetBinaryVersion--rollout等。

4、使用rollback回滾
使用場景:當你釋出的更新測試沒通過時,可以回滾到之前的某個版本。code-push rollback MyAppAndroid Production,當執行這個命令時它會在MyAppAndroid上的Production部署上再次釋出一個release,這個release的程式碼和元屬性與Production上倒數第二個版本一致。也可以通過可選引數--targetRelease來指定rollback到的版本,例如code-push rollback MyAppAndroid Production --targetRelase v2,則會新建一個release,這個release的程式碼和元屬性與v2相同。

注意:這個回滾是主動回滾,與自動回滾不一樣

5、使用debug檢視是否使用了熱更新版本
使用場景:當你想知道code-push的狀態時,比如正在檢查是否有更新包,正在下載,正在安裝,當前載入的
bundle路徑等,對於android可以使用code-push debug android,對於iOS可以使用code-push debug ios

注意:debug ios必須在模擬器下才可以使用

6、使用deployment h檢視更新狀態
使用場景:在釋出更新後,需要檢視安裝情況,可以通過code-push deployment h MyAppAndroid Production來檢視每一次更新的安裝指標。

7、較難理解的釋出引數

  • Mandatory 代表是否強制性更新,這個屬性只是簡單的傳遞給客戶端,具體要對這個屬性如何處理是由客戶端決定的,也就是說,如果在客戶端使用codePush.sync時,updateDialogtrue的情況下,如果-mandatoryfalse,則更新提示框會彈出兩個按鈕,一個是【確認更新】,一個是【取消更新】,但是在-mandatorytrue的情況下就只有一個按鈕【確認更新】使用者沒法拒絕安裝這個更新。在updateDialogfalse的情況下,-mandatory 就不起作用了,因為都會靜默更新。

    注意:mandatory是伺服器傳給客戶端的,它是一個“動態”屬性,意思就是當你正在使用版本v1的更新,然後現在伺服器上有v2v3的更新可用,v2mandatorytrue,v3mandatoryfalse,此時去check update,伺服器會返回v3的更新屬性給客戶端,這時服務返回的v3mandatorytrue,因為v3v2之後釋出的更新,它會被認為是包含v2的所有更新資訊的,竟然v2有強制更新的需求,那跳過v2直接更新到v3的情況下,v3也被要求強制更新。但是如果你當前是在使用v2的更新包,check update時伺服器返回v3的更新包屬性,此時v3mandatoryfalse,因為對於v2而言v3不是強制要更新的。

  • Disabled 預設是為false,顧名思義,這個引數的意思就是這個更新包是否讓使用者使用,如果為true,則不會讓使用者下載這個更新包,使用場景:
    • 當你想釋出一個更新,但是卻不想讓這個更新立馬生效,比如想對外公佈一些資訊後才讓這個更新生效,這時候就可以使用code-push promote MyAppAndroid Staging Production --disabled false來發布更新到正式環境,在對外公佈資訊後,使用code-push patch MyAppAndroid Production --disabled true來讓使用者可以使用這個更新。
  • Rollout 用來指定可以接收到這個更新的使用者的百分比,取值範圍為0-100,不指定時預設為100。如果你希望部分使用者體驗這個新的更新,然後在觀察它的崩潰率和反饋後,在將這個更新發布給所有使用者時,這個屬性就非常有用。當部署中的最後一個更新包的rollout值小於100,有三點要注意:
    • 不能釋出新的更新包,除非最後一個更新包的rollout值被patch100
    • rollback時,rollout值會被置空(為100)。
    • promote去其他部署時,rollout會被置空(為100),可以重新指定--rollout

8、理解安裝指標(Install Metrics)資料
先來看下試用過程,現在有兩個機子,分別為A和B
第一步:發了一個更新包,Install Metrics中提示No install recorded表示沒有安裝記錄

image.png
image.png

第二步:A安裝了這個更新包,並且現在正在使用這個更新包
image.png
image.png

第三步:給v1打了個patch,把App Version改為1.0.0,並且把元屬性Disabled改為true
image.png
image.png

第四步:A卸掉App,發現Install Metrics中的Activite0%了(0 of 1),證明在of左邊的數是會增降的,of右邊的數是隻會增不會降的,of左邊的數代表當前install或者receive的總人數,當有使用者解除安裝App,或者使用了更新的更新包時,這個數就會降低。因此它很好的解釋了當前更新包有多少活躍使用者,多少使用者接收過這個安裝包。Install Metrics中的total並沒有改變,還是為1,代表有多少個使用者install過這個更新包,這個數字只增不降,注意totalactive的區別。
image.png
image.png

第五步:分別在A、B上安裝這個App。發現圖中資料和上圖沒有任何區別,那是因為disabledtrue,因此不會接收這個更新包。
image.png
image.png

第六步:給v1打了個patch,把元屬性Disabled改為true,讓Bcheck update,發現下圖中activeof右邊的數增加了1,代表多了一個使用者receivedv1,但是of左邊的數字為0,代表v1沒有活躍使用者,total的改變是多了(1 pending),代表有一個使用者receivedv1,但是還沒有install(也就是notifyApplicationReady沒被呼叫)
image.png
image.png

第七步:讓Acheck update,發現Active沒有任何改變,因為B以前就接收過v1。totalpending數為2了,代表有兩個使用者receivedv1。
image.png
image.png

第八步:讓Binstallv1,active變為50%,可以看出installed/received為50%。total增加了1,代表v1多了一次installed,一共經歷了2installed(1 pending)代表還有一個received
image.png
image.png

第九步:讓Ainstallv1,active變為100%total增加了1,代表v1多了一次installed,一共經歷了3installed,沒有pending代表沒有received
image.png
image.png

第十步:發一個可以觸發rollback的更新。
App.js的建構函式中新增如下程式碼:

constructor() {
        super(...arguments)
        throw new Error('roll back')
    }複製程式碼

然後發個更新出去:code-push release-react MyAppIOS ios -d Staging --dev false --des rollBackTest
此時code-push deployment h MyAppIOS Staging為:

image.png
image.png

這時我們讓A去check update,並且把code-push debug ios開啟(注意debug必須使用模擬器)。發現v2的total直接從v1total中讀下來,也就是說所有的v1使用者都會receivedv2,pending1代表Areceviedv2,但沒有installed
image.png
image.png

這時,我們讓Ainstalledv2,發現A會閃退,然後再次進入App,發現pending沒有了,但是total並沒有增加,active也沒有改變,pending的加到rollbacks去了。
image.png
image.png

此時code-push debug ios會列印Update did not finish loading the last time, rolling back to a previous version.
第十一步:釋出個修訂版,修復v2產生的bug。然後讓B安裝。

image.png
image.png

哈哈,這個圖看懂了嗎,看懂了就代表瞭解它的意思了O(∩_∩)O哈哈~
第十二步:釋出一個強制更新的更新包。

image.png
image.png

經過上面的測試,大致瞭解了Install metrics中各個引數的意思,這裡大概總結一下:

  • Active 成功安裝並執行當前release的使用者的數量(當使用者開啟你的App就會執行這個release),這個數字會根據使用者成功installed這個release或者離開這個release(installed了別的更新包,或者解除安裝了App),總之有它就知道當前release的活躍使用者量
  • Total 成功installed這個release的使用者的數量,這個數量只會增不會減。
  • Pending 當前這個release被下載的數量,但是還沒有被installed,因此這一個數值會在release被下載時增長,在installed時降低。這個指標主要是適配於沒有為更新配置立馬安裝(mandatory)。如果你為更新配置了立馬安裝但是還是有pending,很有可能是你的App啟動時沒有呼叫notifyApplicationReady
  • Rollbacks 這個數字代表在客戶端自動回滾的數量,理想狀態下,它應該為0,如果你釋出了一個更新包,在installing中發生crash,code-push將會把它回滾到之前的一個更新包中。

    可以在github.com/lyxia/CodeP…

原始碼解讀

檢查、下載、使用以及rollback更新包
js模組:
code-push中Javascript API並不多,可以在JavaScript API查閱。
而快速接入的方法也就兩種,一種是sync,一種是root-level HOC。現在來看HOC的原始碼:

//CodePush.js 456行
componentDidMount() {
  if (options.checkFrequency === CodePush.CheckFrequency.MANUAL) {
    //如果是手動檢查更新,直接installed
    CodePush.notifyAppReady();
  } else {
    ...
     //如果不是手動更新,則每次start app都會去sync
    CodePush.sync(options, syncStatusCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback);
    if (options.checkFrequency === CodePush.CheckFrequency.ON_APP_RESUME) {
      //每次從後臺恢復時sync
      ReactNative.AppState.addEventListener("change", (newState) => {
        newState === "active" && CodePush.sync(options, syncStatusCallback, downloadProgressCallback);
      });
    }
  }
}複製程式碼

可以看出更新的程式碼是sync

//CodePush.js 344行
syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);複製程式碼

在checkForUpdate中會去拿App的版本號,部署key和當前更新包的hash值,確保伺服器傳過來對應的更新包,有幾種情況拿不到更新包,第一種是服務端沒有更新包,第二種是服務端的更新包要求的版本號與當前App版本不符,第三種是服務端的更新包和App當前正在使用的更新包Hash值相同。

//CodePush.js 85行
//PackageMixins.remote(...)執行後返回一個物件包含兩屬性,分別是download和isPending。
//download是一個非同步方法用來下載更新包,isPending初始值為false,表示沒有installed。
const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
//會去判斷這個包是否是已經安裝失敗的包(rollback過)
remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);
remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;複製程式碼

拿到remotePackage後判斷這個更新包是否能使用,能使用就去下載:

    //CodePush.js 362行
    //如果有拿個更新包,但是這個更新包是安裝失敗的包,並且設定中配置忽略安裝失敗的包,則這個更新包會被忽略
    const updateShouldBeIgnored = remotePackage && (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates);
    if (!remotePackage || updateShouldBeIgnored) {
      if (updateShouldBeIgnored) {
          log("An update is available, but it is being ignored due to having been previously rolled back.");
      }

      //會去原生端拿當前下載的更新包,如果這個更新包沒有installed,又更新包可以安裝,如果已經installed就會提示已經是最新版本。
      const currentPackage = await CodePush.getCurrentPackage();
      if (currentPackage && currentPackage.isPending) {
        syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
        return CodePush.SyncStatus.UPDATE_INSTALLED;
      } else {
        syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE);
        return CodePush.SyncStatus.UP_TO_DATE;
      }
    } else{
      //如果設定中配置彈提示框,則根據mandatory彈出不同的提示框,根據使用者的選擇決定是否下載更新包。
      //如果沒有配置彈提示框,則直接下載更新包
      ...
    }複製程式碼

下載的程式碼:

    const doDownloadAndInstall = async () => {
      syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);
      //使用之前提到的download方法來下載更新包。
      const localPackage = await remotePackage.download(downloadProgressCallback);

      //檢查安裝方式
      resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;

      syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
      //安裝更新
      await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
        syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
      });

      return CodePush.SyncStatus.UPDATE_INSTALLED;
    };複製程式碼

原生模組(以Android端為例):
首先尋找jsbundle路徑,getJSBundleFile中返回了CodePush.getJSBundleFile(),在這裡面會判斷是否有新下載的更新包,如果比本地新則載入這個更新包,否則載入本地包,

    //CodePush.java 143行
    public String getJSBundleFileInternal(String assetsBundleFileName) {
        this.mAssetsBundleFileName = assetsBundleFileName;
        String binaryJsBundleUrl = CodePushConstants.ASSETS_BUNDLE_PREFIX + assetsBundleFileName;

        //獲取當前可以使用的更新包的路徑
        String packageFilePath = mUpdateManager.getCurrentPackageBundlePath(this.mAssetsBundleFileName);
        if (packageFilePath == null) {
            // 當前沒有任何更新包可以使用
            CodePushUtils.logBundleUrl(binaryJsBundleUrl);
            sIsRunningBinaryVersion = true;
            return binaryJsBundleUrl;
        }

        //獲取當前可以使用的更新包的配置檔案
        JSONObject packageMetadata = this.mUpdateManager.getCurrentPackage();
        if (isPackageBundleLatest(packageMetadata)) {
            //如果當前更新包是最新可用的(版本號相符),使用當前更新包
            CodePushUtils.logBundleUrl(packageFilePath);
            sIsRunningBinaryVersion = false;
            return packageFilePath;
        } else {
            // 當前App的版本是新的(比如更新包是8.6.0的,現在App是8.6.1)
            this.mDidUpdate = false;
            if (!this.mIsDebugMode || hasBinaryVersionChanged(packageMetadata)) {
                //當App版本號有改變的時候清除所有更新包
                this.clearUpdates();
            }
            //使用本地bundle
            CodePushUtils.logBundleUrl(binaryJsBundleUrl);
            sIsRunningBinaryVersion = true;
            return binaryJsBundleUrl;
        }
    }複製程式碼

在js端的remotePackage.download中會呼叫原生的downloadUpdate方法

   //CodePushNativeModule.java 203行
   public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) {
        //後臺下載任務
        AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    JSONObject mutableUpdatePackage = CodePushUtils.convertReadableToJsonObject(updatePackage);
                    CodePushUtils.setJSONValueForKey(mutableUpdatePackage, CodePushConstants.BINARY_MODIFIED_TIME_KEY, "" + mCodePush.getBinaryResourcesModifiedTime());
                    //開始下載remotePackage
                    mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
                          //下載進度回撥
                          ...
                    });
                    //獲取remotePackage的資訊並返回給js
                    JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
                    promise.resolve(CodePushUtils.convertJsonObjectToWritable(newPackage));
                } catch (IOException e) {
                    e.printStackTrace();
                    promise.reject(e);
                } catch (CodePushInvalidUpdateException e) {
                    e.printStackTrace();
                    mSettingsManager.saveFailedUpdate(CodePushUtils.convertReadableToJsonObject(updatePackage));
                    promise.reject(e);
                }

                return null;
            }
        };

        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }複製程式碼

在js端呼叫installUpdate,一共會出現三個hash值,分別是剛下載的更新包的hash值(packageHash),當前使用的hash值(currentPackageHash),以前使用的hash值(previousPackageHash),現在要把prevousPackageHash = currentPackageHashcurrentPackageHash = packageHash

    //CodePushUpdateManager.java
    public void installPackage(JSONObject updatePackage, boolean removePendingUpdate) {
        //獲取更新包的hash值
        String packageHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
        JSONObject info = getCurrentPackageInfo();
        //獲取當前使用的更新包的hash值
        String currentPackageHash = info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null);
        if (packageHash != null && packageHash.equals(currentPackageHash)) {
            // 如果下載的更新包和當前使用的是同一個更新包,不做處理
            return;
        }

        if (removePendingUpdate) {
            //如果當前使用的更新包是下載好但沒有installed的更新包,則把這個更新包移除
            String currentPackageFolderPath = getCurrentPackageFolderPath();
            if (currentPackageFolderPath != null) {
                FileUtils.deleteDirectoryAtPath(currentPackageFolderPath);
            }
        } else {
             //獲取之前的更新包,並移除
            String previousPackageHash = getPreviousPackageHash();
            if (previousPackageHash != null && !previousPackageHash.equals(packageHash)) {
                FileUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
            }
            //將上一個更新包指向當前更新包
            CodePushUtils.setJSONValueForKey(info, CodePushConstants.PREVIOUS_PACKAGE_KEY, info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null));
        }

        //設定當前可使用的更新包為update package
        CodePushUtils.setJSONValueForKey(info, CodePushConstants.CURRENT_PACKAGE_KEY, packageHash);
        updateCurrentPackageInfo(info);
    }複製程式碼

將剛下載的更新包標記為pending package,isloading為false:

//CodePushNativeModule.java 411行,
//標記為pending,並且isLoading為false
mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false);複製程式碼

App第一次進入和重新載入bundle時會呼叫initializeUpdateAfterRestart,用來判斷是否有pending package,如果有並且isloading為true(被init過),代表這個pending package在notifyApplicationReady前崩潰了,因此需要rollback,如果isloading為false則代表是第一次載入更新包,會將isloading(init)置為true,用來判斷下次進入時需不需要rollback:

//CodePush.js 177行
void initializeUpdateAfterRestart() {
        ...
        JSONObject pendingUpdate = mSettingsManager.getPendingUpdate();
        if (pendingUpdate != null) {
            //有新的更新包可用
            JSONObject packageMetadata = this.mUpdateManager.getCurrentPackage();
            if (!isPackageBundleLatest(packageMetadata) && hasBinaryVersionChanged(packageMetadata)) {
                //版本不符
                CodePushUtils.log("Skipping initializeUpdateAfterRestart(), binary version is newer");
                return;
            }

            try {
                boolean updateIsLoading = pendingUpdate.getBoolean(CodePushConstants.PENDING_UPDATE_IS_LOADING_KEY);
                if (updateIsLoading) {
                    // Pending package已經被init過, 但是 notifyApplicationReady 沒有被呼叫.
                    // 因此認為這是個無效的更新並且rollback.
                    CodePushUtils.log("Update did not finish loading the last time, rolling back to a previous version.");
                    sNeedToReportRollback = true;
                    rollbackPackage();
                } else {
                    // 現在有個新的更新包可以執行,開始init這個更新包
  //如果它崩潰了,需要在下一次啟動時rollback
mSettingsManager.savePendingUpdate(pendingUpdate.getString(CodePushConstants.PENDING_UPDATE_HASH_KEY),
                            /* isLoading */true);
                }
            } catch (JSONException e) {
                // Should not happen.
                throw new CodePushUnknownException("Unable to read pending update metadata stored in SharedPreferences", e);
            }
        }
    }複製程式碼

rollback的程式碼:

    //CodePush.java 257行
    private void rollbackPackage() {
        //將當前使用的更新包標記為失敗的包
        JSONObject failedPackage = mUpdateManager.getCurrentPackage();
        mSettingsManager.saveFailedUpdate(failedPackage);
       //用之前使用的更新包替換當前使用的更新包
        mUpdateManager.rollbackPackage();
       //移除pending package
        mSettingsManager.removePendingUpdate();
    }複製程式碼

notifyApplicationReady的程式碼:

   //CodePushNativeModule.java 498行
   public void notifyApplicationReady(Promise promise) {
        //移除pending package
        mSettingsManager.removePendingUpdate();
        promise.resolve("");
    }複製程式碼

總結:
js端使用checkupdate用App當前的版本號,當時使用的更新包資訊以及部署key傳遞給原生,原生呼叫codu-push伺服器查詢是否有更新包可以使用,如果不存在更新包,或者更新包與當前使用的更新包一致,或者版本號不符都不會產生remotePackage。拿到remotePackage後會去原生的本地儲存查詢這個remotePackage的hash是否為failedPackage,如果是failedPackage則會選擇忽略這個更新包,否則就download這個更新包。
下載好更新包後,將這個更新包標誌位pending package,並且isloading為false,將previousPacakge置為currentPackage,currentPackage置為下載的更新包。
在載入更新包時會判斷這個更新包是否是pending package,如果是則判斷isloading是否為false,如果為false則代表這個pending package是第一次載入,如果為true則代表這個pending被載入後呼叫notifyApplicationReady前發生崩潰,需要回滾。
如果發生回滾會將pending package置空,將previouPackage賦值給currentPackage。
在正確載入更新包後,應該手動觸發notifyApplicationReady將pending package置空,代表這個更新包被正確installed。

示例:
hash包的管理:
failed package:崩潰的package
pending package:下載好的沒有被installed的package
previous package: 之前使用的package
current package:當前正在使用package
第一步:下載更新包A

pending pacakge = A 
isloding = false
previous package = current package
current package = pending package複製程式碼

第二步:第一次使用A

pending isloading = true複製程式碼

如果在notifyApplicationReady之前發生崩潰走第三步,否則走第四步。
第三步:再次載入bundle,發現pending package還存在,並且isloading為true,回滾
第四步:pending package不存在,不做任何處理

Demo

地址:github.com/lyxia/CodeP…

image.png
image.png

image.png
image.png

image.png
image.png

image.png
image.png

相關文章