1. 功能
1.1 三种更新策略选择:
- 静默模式(slient mode): 无提示的直接安装新的更新,可以更新起效的时间点:
- IMMEDIATE 立即生效,activity重启;
- ON_NEXT_RESTART 下一次应用restart
- ON_NEXT_RESUME 下一次应用resume或者restart时
- 选择模式(active mode): 弹出一个对话框,提示用户升级,由用户决定;
- 自定义模式(custom mode): CodePush 提供了onSyncStatusChange 和 onDownloadProgress的回调函数的使用方法,可以通过自定回调函数以达到自定义的更新流程的功能
1.2 注册使用
略
2. 基本架构
涉及到Javascript部分和Java部分,值得注意的是两部分均有网络访问服务器的功能,Javascript部分的网络访问请求主要是检查是否有更新,而Java部分的网络访问请求则是下载更新包,猜测CodePush将检查是否有更新放到Javascript里可能是考虑到该接口可能会有更改,比如报文字段,而下载更新包的接口实质是给定资源地址,不存在请求参数,所以变动不大。

2.1 JavaScript部分
CodePush.js
热更新js端入口文件,其实质是一个对应用的根组件的进行装饰的装饰类.
// 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 = { //设置一些CodePush相关属性
//…
};
let CodePushApp = CodePush(CodePushOptions)(myApp); //装饰myApp
AppRegistry.registerCompent("myApp", () => CodePushApp);
AcquistionManager
与后台服务器通信的SDK文件,负责检查更新请求的发送等内容,注意下载更新包的请求并不式js端完成,而是在Java端完成;
-
queryUpdateWithCurrentPackage()根据当前的安装包信息查询更新情况 -
reportStatusDeploy()向服务器上传信息 -
reportStatusDownload()向服务器上传信息
RestartManager
维持一个_restartQueue数组,提供以下四个函数
-
allow()设置_allowed变量为true,如果队列中不为空,则执行restartApp(_restartQueue.shit(1)) -
clearPendingRestart()清空队列,即_restartQueue = [] -
disallow()设置_allowed变量为false -
restartApp()进一步调用NativeCodePush.restartApp()
NativeCodePush
Java端CodePushNativeModule在JS端的调用对象,用于调用Native的方法.
let NativeCodePush = require("react-native").NativeModules.CodePush;
2.2 Java部分
CodePush.java
一个ReactPackage的实现,管理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
定义对JS层暴露的方法:
getConfiguration()获取配置参数,如appVersion、serverUrl等getUpdateMetadata()获取当前package的app.json的内容getNewStatusReport()downloadUpdate()下载更新,实质执行的CodePushUpdateManager.downloadUpdate()installUpdate()安装更新,实质执行的是CodePushUpdateManager.installUpdate()notifyApplicationReady()recordStatusReported()-
restartApp()调用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 )根据传入的updateJsonObj对象构造下载请求,并存储结构存储下载的更新包 -
installPackage( updateJsonObj )根据传入的updateJsonObj对象,更新codepush.json文件,codepush.json文件用于记录当前使用的和上一次使用的package的信息 -
rollbackPackage()版本回滚,实质是更新codepush.json文件
CodePushTelemetryManager
管理SharedPreference中的RETRY_DELOYMENT_KEY和LAST_DELOPYMENT_KEY两个值,提供的相应的增删改查操作;
RETRY_DELOYMENT_KEYLAST_DELOPYMENT_KEY
SettingsManager
管理SharedPreference中的FAILED_UPDATES_KEY和PENDING_UPDATE_KEY两个值,提供的相应的增删改查操作;
-
FAILED_UPDATES_KEY存储安装失败的pacakage的信息(信息格式为json字符串) -
PENDING_UPDATE_KEY存储等待安装的pacakage的信息(信息格式为json字符串)
3. 数据存储
3.1 存储
-
SharedPerefence
FAILED_UPDATES_KEYPENDING_UPDATE_KEYRETRY_DELOYMENT_KEYLAST_DELOPYMENT_KEY
内部存储
根目录/data/data/com.xxx.xxx/files,即Context.getFilesDir().getAbsolutePath(),属于应用的私文件
- CodePush/
- codepush.json
- {hashcode}/
- xxxx.bundle
- app.json
- {hashcode}/
- xxxx.bundle
- app.json
- unzipped/ (临时,若下载的更新包是zip文件)
3.2 json结构
// codepush.json 中的参数
{
// ...
currentPackage: "当前package的hashCode值",
priviousPackage: "上一个版本的package的hashcode值"
}
// app.json 中的参数
{
// ...
packageHash: "该package对应的hashcode值",
bundlePath: "JS bundle的相对位置"
}