react-native-code-push進階篇

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

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

部署與配置

部署(deployment)Test,Staging和Production

在真正的項目中,咱們通常會分爲開發版(Test),灰度版(Staging)和發佈版(Production),在Test中我通常是用來跟蹤code-push的執行,在Staging中實際上是和Production是一樣的代碼,可是當要熱修復線上版本時,先會發布熱更新到Staging版,在Staging測事後再經過promoting推到Production中去。android

大體步驟:ios

  • 經過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來查看能夠執行的操做git

最後結果以下圖所示:
github

image.png
image.png

image.png
image.png

在原生項目中動態部署

在上面有提過須要把部署的key添加到原生項目中,這樣在不一樣的運行環境下動態的使用對應的部署key,例如在Staging下使用Stagingkey,在Relase下使用Productionkey,在Debug下不使用熱更新(如需在debug環境下測試code-push,能夠在codePush.sync裏的option參數中動態修改部署key)。npm

在Android中動態部署key,而且在同一設備同時安裝不一樣部署的Android包
有兩種方式:react-native

  • 官方配置入口: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…api

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

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

參考項目:github.com/lyxia/CodeP…

應用中常常遇到的技巧

一、分清楚 Target binary version 和 Label

image.png
image.png

label表明發佈的更新版本, Target binary version表明app的版本號。

二、使用patch打補丁,修改元數據屬性。
使用場景:例如當你已經發布了一個更新,可是到有些狀況下,好比--des須要修改,--targetBinaryVersion寫錯了,好比個人8.6.0寫成了8.6,而後在我發佈8.6.1新版的時候就會拉取8.6的版本更新,這個時候就能夠code-push patch MyAppAndroid Production --label v4 --targetBinaryVersion 8.6.1

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

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

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

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

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

六、使用deployment h查看更新狀態
使用場景:在發佈更新後,須要查看安裝狀況,能夠經過code-push deployment h MyAppAndroid Production來查看每一次更新的安裝指標。

七、較難理解的發佈參數

  • 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

八、理解安裝指標(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,讓B check update,發現下圖中 activeof右邊的數增長了 1,表明多了一個用戶 receivedv1,可是 of左邊的數字爲 0,表明v1沒有活躍用戶, total的改變是多了 (1 pending),表明有一個用戶 receivedv1,可是尚未 install(也就是 notifyApplicationReady沒被調用)
image.png
image.png

第七步:讓A check update,發現 Active沒有任何改變,由於B之前就接收過v1。 totalpending數爲 2了,表明有兩個用戶 receivedv1。
image.png
image.png

第八步:讓B installv1, active變爲 50%,能夠看出 installed/received爲50%。 total增長了 1,表明v1多了一次 installed,一共經歷了 2installed(1 pending)表明還有一個 received
image.png
image.png

第九步:讓A installv1, 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直接從v1 total中讀下來,也就是說全部的v1用戶都會 receivedv2, pending1表明A receviedv2,但沒有 installed
image.png
image.png

這時,咱們讓A installedv2,發現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
相關文章
相關標籤/搜索