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_KEY
LAST_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_KEY
PENDING_UPDATE_KEY
RETRY_DELOYMENT_KEY
LAST_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的相对位置"
}