1. 功能
1.1 三種更新策略選擇:
- 靜默模式(slient mode): 無提示的直接安裝新的更新,可以更新起效的時(shí)間點(diǎn):
- IMMEDIATE 立即生效,activity重啟;
- ON_NEXT_RESTART 下一次應(yīng)用restart
- ON_NEXT_RESUME 下一次應(yīng)用resume或者restart時(shí)
- 選擇模式(active mode): 彈出一個(gè)對(duì)話框,提示用戶升級(jí),由用戶決定;
- 自定義模式(custom mode): CodePush 提供了onSyncStatusChange 和 onDownloadProgress的回調(diào)函數(shù)的使用方法,可以通過自定回調(diào)函數(shù)以達(dá)到自定義的更新流程的功能
1.2 注冊(cè)使用
略
2. 基本架構(gòu)
涉及到Javascript部分和Java部分,值得注意的是兩部分均有網(wǎng)絡(luò)訪問服務(wù)器的功能,Javascript部分的網(wǎng)絡(luò)訪問請(qǐng)求主要是檢查是否有更新,而Java部分的網(wǎng)絡(luò)訪問請(qǐng)求則是下載更新包,猜測(cè)CodePush將檢查是否有更新放到Javascript里可能是考慮到該接口可能會(huì)有更改,比如報(bào)文字段,而下載更新包的接口實(shí)質(zhì)是給定資源地址,不存在請(qǐng)求參數(shù),所以變動(dòng)不大。

2.1 JavaScript部分
CodePush.js
熱更新js端入口文件,其實(shí)質(zhì)是一個(gè)對(duì)應(yīng)用的根組件的進(jìn)行裝飾的裝飾類.
// CodePush.js
var decorator = (RootComponent) => {
return class CodePushComponent extends React.Component {
componentDidMount() {
// ...
CodePush.sync(options,...);
}
render() {
return <RootComponent {...this.props} ref={"rootComponent"} />;
}
}
};
if (typeof options === "function") {
// Infer that the root component was directly passed to us.
return decorator(options);
} else {
return decorator;
}
// 不使用CodePush的寫法
class myApp extends Component {
//…
};
AppRegistry.registerCompent("myApp", () => myApp);
/************************************************/
// 使用CodePush的寫法
class myApp extends Component{
//…
};
let CodePushOptions = { //設(shè)置一些CodePush相關(guān)屬性
//…
};
let CodePushApp = CodePush(CodePushOptions)(myApp); //裝飾myApp
AppRegistry.registerCompent("myApp", () => CodePushApp);
AcquistionManager
與后臺(tái)服務(wù)器通信的SDK文件,負(fù)責(zé)檢查更新請(qǐng)求的發(fā)送等內(nèi)容,注意下載更新包的請(qǐng)求并不式j(luò)s端完成,而是在Java端完成;
-
queryUpdateWithCurrentPackage()根據(jù)當(dāng)前的安裝包信息查詢更新情況 -
reportStatusDeploy()向服務(wù)器上傳信息 -
reportStatusDownload()向服務(wù)器上傳信息
RestartManager
維持一個(gè)_restartQueue數(shù)組,提供以下四個(gè)函數(shù)
-
allow()設(shè)置_allowed變量為true,如果隊(duì)列中不為空,則執(zhí)行restartApp(_restartQueue.shit(1)) -
clearPendingRestart()清空隊(duì)列,即_restartQueue = [] -
disallow()設(shè)置_allowed變量為false -
restartApp()進(jìn)一步調(diào)用NativeCodePush.restartApp()
NativeCodePush
Java端CodePushNativeModule在JS端的調(diào)用對(duì)象,用于調(diào)用Native的方法.
let NativeCodePush = require("react-native").NativeModules.CodePush;
2.2 Java部分
CodePush.java
一個(gè)ReactPackage的實(shí)現(xiàn),管理CodePushNativeModule
public class CodePush implements ReactPackage {
// ...
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext);
List<NativeModule> nativeModules = new ArrayList<>();
nativeModules.add(codePushModule);
nativeModules.add(dialogModule);
return nativeModules;
}
}
CodePushNativeModule
定義對(duì)JS層暴露的方法:
getConfiguration()獲取配置參數(shù),如appVersion、serverUrl等getUpdateMetadata()獲取當(dāng)前package的app.json的內(nèi)容getNewStatusReport()downloadUpdate()下載更新,實(shí)質(zhì)執(zhí)行的CodePushUpdateManager.downloadUpdate()installUpdate()安裝更新,實(shí)質(zhì)執(zhí)行的是CodePushUpdateManager.installUpdate()notifyApplicationReady()recordStatusReported()-
restartApp()調(diào)用loadBundle()(利用反射):private void loadBundle() { mCodePush.clearDebugCacheIfNeeded(); try { // #1) Get the ReactInstanceManager instance, which is what includes the // logic to reload the current React context. final ReactInstanceManager instanceManager = resolveInstanceManager(); if (instanceManager == null) { return; } String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); // #2) Update the locally stored JS bundle file path setJSBundle(instanceManager, latestJSBundleFile); // #3) Get the context creation method and fire it on the UI thread (which RN enforces) final Method recreateMethod = instanceManager.getClass().getMethod("recreateReactContextInBackground"); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { try { recreateMethod.invoke(instanceManager); mCodePush.initializeUpdateAfterRestart(); } catch (Exception e) { // The recreation method threw an unknown exception // so just simply fallback to restarting the Activity (if it exists) loadBundleLegacy(); } } }); } catch (Exception e) { // Our reflection logic failed somewhere // so fall back to restarting the Activity (if it exists) loadBundleLegacy(); } } saveStatusReportForRetry()
CodePushUpdateManager
管理更新包的下載、刪除等
-
downloadPackage( updateJsonObj )根據(jù)傳入的updateJsonObj對(duì)象構(gòu)造下載請(qǐng)求,并存儲(chǔ)結(jié)構(gòu)存儲(chǔ)下載的更新包 -
installPackage( updateJsonObj )根據(jù)傳入的updateJsonObj對(duì)象,更新codepush.json文件,codepush.json文件用于記錄當(dāng)前使用的和上一次使用的package的信息 -
rollbackPackage()版本回滾,實(shí)質(zhì)是更新codepush.json文件
CodePushTelemetryManager
管理SharedPreference中的RETRY_DELOYMENT_KEY和LAST_DELOPYMENT_KEY兩個(gè)值,提供的相應(yīng)的增刪改查操作;
RETRY_DELOYMENT_KEYLAST_DELOPYMENT_KEY
SettingsManager
管理SharedPreference中的FAILED_UPDATES_KEY和PENDING_UPDATE_KEY兩個(gè)值,提供的相應(yīng)的增刪改查操作;
-
FAILED_UPDATES_KEY存儲(chǔ)安裝失敗的pacakage的信息(信息格式為json字符串) -
PENDING_UPDATE_KEY存儲(chǔ)等待安裝的pacakage的信息(信息格式為json字符串)
3. 數(shù)據(jù)存儲(chǔ)
3.1 存儲(chǔ)
-
SharedPerefence
FAILED_UPDATES_KEYPENDING_UPDATE_KEYRETRY_DELOYMENT_KEYLAST_DELOPYMENT_KEY
內(nèi)部存儲(chǔ)
根目錄/data/data/com.xxx.xxx/files,即Context.getFilesDir().getAbsolutePath(),屬于應(yīng)用的私文件
- CodePush/
- codepush.json
- {hashcode}/
- xxxx.bundle
- app.json
- {hashcode}/
- xxxx.bundle
- app.json
- unzipped/ (臨時(shí),若下載的更新包是zip文件)
3.2 json結(jié)構(gòu)
// codepush.json 中的參數(shù)
{
// ...
currentPackage: "當(dāng)前package的hashCode值",
priviousPackage: "上一個(gè)版本的package的hashcode值"
}
// app.json 中的參數(shù)
{
// ...
packageHash: "該package對(duì)應(yīng)的hashcode值",
bundlePath: "JS bundle的相對(duì)位置"
}