之前寫了一篇關於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來檢視可以執行的操作
最後結果如下圖所示:
在原生專案中動態部署
在上面有提過需要把部署的key
新增到原生專案中,這樣在不同的執行環境下動態的使用對應的部署key
,例如在Staging
下使用Staging
的key
,在Relase
下使用Production
的key
,在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/values
和debug/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)有不同的圖示和標題
應用中經常遇到的技巧
1、分清楚 Target binary version 和 Labellabel
代表釋出的更新版本,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
時,updateDialog
為true
的情況下,如果-mandatory
為false
,則更新提示框會彈出兩個按鈕,一個是【確認更新】,一個是【取消更新】,但是在-mandatory
為true
的情況下就只有一個按鈕【確認更新】使用者沒法拒絕安裝這個更新。在updateDialog
為false
的情況下,-mandatory
就不起作用了,因為都會靜默更新。注意:mandatory是伺服器傳給客戶端的,它是一個“動態”屬性,意思就是當你正在使用版本
v1
的更新,然後現在伺服器上有v2
和v3
的更新可用,v2
的mandatory
為true
,v3
的mandatory
為false
,此時去check update
,伺服器會返回v3
的更新屬性給客戶端,這時服務返回的v3
的mandatory
為true
,因為v3
在v2
之後釋出的更新,它會被認為是包含v2
的所有更新資訊的,竟然v2
有強制更新的需求,那跳過v2
直接更新到v3
的情況下,v3
也被要求強制更新。但是如果你當前是在使用v2
的更新包,check update
時伺服器返回v3
的更新包屬性,此時v3
的mandatory
為false
,因為對於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
值被patch
為100
。 - 當
rollback
時,rollout
值會被置空(為100)。 - 當
promote
去其他部署時,rollout
會被置空(為100),可以重新指定--rollout
。
- 不能釋出新的更新包,除非最後一個更新包的
8、理解安裝指標(Install Metrics)資料
先來看下試用過程,現在有兩個機子,分別為A和B
第一步:發了一個更新包,Install Metrics
中提示No install recorded
表示沒有安裝記錄
第二步:A安裝了這個更新包,並且現在正在使用這個更新包
第三步:給v1
打了個patch
,把App Version
改為1.0.0
,並且把元屬性Disabled
改為true
第四步:A卸掉App,發現Install Metrics
中的Activite
為0%
了(0 of 1),證明在of
左邊的數是會增降的,of右邊的數是隻會增不會降的,of
左邊的數代表當前install
或者receive
的總人數,當有使用者解除安裝App,或者使用了更新的更新包時,這個數就會降低。因此它很好的解釋了當前更新包有多少活躍使用者,多少使用者接收過這個安裝包。Install Metrics
中的total
並沒有改變,還是為1
,代表有多少個使用者install
過這個更新包,這個數字只增不降,注意total
與active
的區別。
第五步:分別在A、B上安裝這個App。發現圖中資料和上圖沒有任何區別,那是因為disabled
為true
,因此不會接收這個更新包。
第六步:給v1打了個patch,把元屬性Disabled
改為true
,讓Bcheck update
,發現下圖中active
中of
右邊的數增加了1
,代表多了一個使用者received
v1,但是of
左邊的數字為0
,代表v1沒有活躍使用者,total
的改變是多了(1 pending)
,代表有一個使用者received
v1,但是還沒有install
(也就是notifyApplicationReady
沒被呼叫)
第七步:讓Acheck update
,發現Active
沒有任何改變,因為B以前就接收過v1。total
中pending
數為2
了,代表有兩個使用者received
v1。
第八步:讓Binstall
v1,active
變為50%
,可以看出installed/received
為50%。total
增加了1
,代表v1多了一次installed
,一共經歷了2
次installed
,(1 pending)
代表還有一個received
。
第九步:讓Ainstall
v1,active
變為100%
。total
增加了1
,代表v1多了一次installed
,一共經歷了3
次installed
,沒有pending
代表沒有received
。
第十步:發一個可以觸發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
為:
這時我們讓A去check update
,並且把code-push debug ios
開啟(注意debug必須使用模擬器)。發現v2的total
直接從v1total
中讀下來,也就是說所有的v1使用者都會received
v2,pending
為1
代表Arecevied
v2,但沒有installed
。
這時,我們讓Ainstalled
v2,發現A會閃退,然後再次進入App,發現pending
沒有了,但是total
並沒有增加,active
也沒有改變,pending
的加到rollbacks
去了。
此時code-push debug ios
會列印Update did not finish loading the last time, rolling back to a previous version.
第十一步:釋出個修訂版,修復v2產生的bug。然後讓B安裝。
哈哈,這個圖看懂了嗎,看懂了就代表瞭解它的意思了O(∩_∩)O哈哈~
第十二步:釋出一個強制更新的更新包。
經過上面的測試,大致瞭解了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將會把它回滾到之前的一個更新包中。
原始碼解讀
檢查、下載、使用以及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 = currentPackageHash
,currentPackageHash = 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不存在,不做任何處理