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)不大胜宇。
![CodePush for RN](https://raw.githubusercontent.com/MarcusMa/ReactNativeResearchReport/master/1.png)
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_KEY
LAST_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_KEY
PENDING_UPDATE_KEY
RETRY_DELOYMENT_KEY
LAST_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ì)位置"
}