前言:前面写了如何搭建热更新的本地环境,今天梳理一下热更新的流程,在看代码之前,我们先想一下,实现更新应该需要以下几个步骤:
1、向服务端提交检查请求
2、服务端告知本地有新版本,通知本地去更新
3、本地下载新版本
4、安装新版本
5、加载新版本内容
以上应该是检查更新的基本步骤,我们再来看一下对应的代码:
demo中的检查更新是在App.js文件中执行的,我们可以看到执行更新主要有以下两个方法:
/** Update is downloaded silently, and applied on restart (recommended) */
sync() {
CodePush.sync(
{},
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this)
);
}
/** Update pops a confirmation dialog, and then immediately reboots the app */
syncImmediate() {
CodePush.sync(
{ installMode: CodePush.InstallMode.IMMEDIATE, updateDialog: true },
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this)
);
}
方法中都调用了CodePush.sync
这个方法,我们看一下这个方法做了什么:
/**
* Allows checking for an update, downloading it and installing it, all with a single call.
检查更新、下载、安装都在这个方法里啦啦啦啦啦。
*
* @param options Options used to configure the end-user update experience (e.g. show an prompt?, install the update immediately?).
更新的参数
* @param syncStatusChangedCallback An optional callback that allows tracking the status of the sync operation, as opposed to simply checking the resolved state via the returned Promise.
更新状态改变的回调
* @param downloadProgressCallback An optional callback that allows tracking the progress of an update while it is being downloaded.
下载进度的回调
* @param handleBinaryVersionMismatchCallback An optional callback for handling target binary version mismatch
版本不一致的回调(可选)
*/
function sync(options?: SyncOptions,
syncStatusChangedCallback?: SyncStatusChangedCallback,
downloadProgressCallback?: DowloadProgressCallback,
handleBinaryVersionMismatchCallback?:
HandleBinaryVersionMismatchCallback): Promise<SyncStatus>;
检查更新的参数:
export interface SyncOptions {
deploymentKey?: string;
检查更新的部署密钥,就是我们在code-push服务器上创建app时生成的key,服务器根据此来判断是检查哪一个应用。
installMode?: CodePush.InstallMode;//安装模式,默认是下一次restart时安装,包含:
IMMEDIATE(安装更新并立刻重启应用),
ON_NEXT_RESTART(安装更新,但不立马重启,直到下一次重新进入),
ON_NEXT_RESUME(安装更新,但是不立马重新启动,直到下一次从后台恢复到前台)
ON_NEXT_SUSPEND(下一次处于后台时)
mandatoryInstallMode?: CodePush.InstallMode;
指定如果更新的版本是标识了强制更新怎么更新。具体参数和上面一样。
minimumBackgroundDuration?: number;
指定应用程序需要在后台重新启动应用程序之前所需的最小秒数,默认是0。
updateDialog?: UpdateDialog;
标记检查到更新是要弹出对话框
}
让我们再看看CodePush中sync方法具体做了什么处理(代码在node_modules/react-native-code-push/CodePush.js
,本地的module代码对应的是react-native-code-push对应的lib库中的com/microsoft/codepush/react/CodePushNativeModule.java
):
const sync = (() => {
let syncInProgress = false;
const setSyncCompleted = () => { syncInProgress = false; };
//上面是互斥处理,保证多次调用sync只有一个在检查更新,其他直接返回正在检查中的状态
return (options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) => {
let syncStatusCallbackWithTryCatch, downloadProgressCallbackkWithTryCatch;
//将syncStatusChangeCallback和downloadProgressCallback放入try catch中。
if (typeof syncStatusChangeCallback === "function") {
syncStatusCallbackWithTryCatch = (...args) => {
try {
syncStatusChangeCallback(...args);
} catch (error) {
log(`An error has occurred : ${error.stack}`);
}
}
}
//将syncStatusChangeCallback和downloadProgressCallback放入try catch中。
if (typeof downloadProgressCallback === "function") {
downloadProgressCallbackkWithTryCatch = (...args) => {
try {
downloadProgressCallback(...args);
} catch (error) {
log(`An error has occurred: ${error.stack}`);
}
}
}
//检查更新的互斥逻辑处理
if (syncInProgress) {
typeof syncStatusCallbackWithTryCatch === "function"
? syncStatusCallbackWithTryCatch(CodePush.SyncStatus.SYNC_IN_PROGRESS)
: log("Sync already in progress.");
return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS);
}
syncInProgress = true;
//调用了syncInternal方法
const syncPromise = syncInternal(options, syncStatusCallbackWithTryCatch, downloadProgressCallbackkWithTryCatch, handleBinaryVersionMismatchCallback);
syncPromise
.then(setSyncCompleted)
.catch(setSyncCompleted);
return syncPromise;
};
})();
我们再看一下syncInternal方法做了什么,这个里面就包含了所有的流程:
/*
该方法提供了一个简单的在线合并检查、下载、安装更新
* The syncInternal method provides a simple, one-line experience for
* incorporating the check, download and installation of an update.
*
它将这一些方法组合到一起,并支持强制更新、忽略失败更新、更新确认dialog等
* It simply composes the existing API methods together and adds additional
* support for respecting mandatory updates, ignoring previously failed
* releases, and displaying a standard confirmation UI to the end-user
* when an update is available.
*/
async function syncInternal(options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) {
let resolvedInstallMode;
//准备检查更新的参数
const syncOptions = {
deploymentKey: null,//这个地方为null,是因为从原生代码中获取的
ignoreFailedUpdates: true,//是否忽略失败的更新版本
installMode: CodePush.InstallMode.ON_NEXT_RESTART,//安装模式
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,//强制更新时的安装模式
minimumBackgroundDuration: 0,//重启最小等待时间
updateDialog: null,//是否要对话框
...options//其他传递过来的参数
};
//更新状态同步的回调
syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
? syncStatusChangeCallback
: (syncStatus) => {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
break;
case CodePush.SyncStatus.UP_TO_DATE:
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESTART) {
} else if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESUME) {
}
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
break;
}
};
try {
//通知App已经ready。
await CodePush.notifyApplicationReady();
//发出第一个同步状态。
syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
//检查更新
const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);
//定义下载的方法
const doDownloadAndInstall = async () => {
...
};
const updateShouldBeIgnored = remotePackage && (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates);
if (!remotePackage || updateShouldBeIgnored) {
//如果没有更新或者当前更新应该被忽略的
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 if (syncOptions.updateDialog) {
//如果需要对话框,则需要用户手动点击下载安装
...
return await new Promise((resolve, reject) => {
let message = null;
const dialogButtons = [{
text: null,
onPress:() => {
doDownloadAndInstall()
.then(resolve, reject);
}
}];
} else {
//如果不需要对话框,直接下载并安装
return await doDownloadAndInstall();
}
} catch (error) {
syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR);
log(error.message);
throw error;
}
};
notifyApplicationReady
上面的notifyApplicationReady
方法是调用了notifyApplicationReadyInternal
方法,而notifyApplicationReadyInternal
方法如下:
async function notifyApplicationReadyInternal() {
await NativeCodePush.notifyApplicationReady();//原生Module的方法
//获取本地的状态,并上传到服务器
const statusReport = await NativeCodePush.getNewStatusReport();
statusReport && tryReportStatus(statusReport); // Don't wait for this to complete.
return statusReport;
}
NativeCodePush.notifyApplicationReady()
原生代码操作如下:
@ReactMethod
public void notifyApplicationReady(Promise promise) {
mSettingsManager.removePendingUpdate();//清除未更新的hash之
promise.resolve("");
}
NativeCodePush.getNewStatusReport
的方法就是根据上一次检查的状态返回不同的值,此处不做细述。
tryReportStatus
方法如下:
async function tryReportStatus(statusReport, resumeListener) {
log(`statusReport = (${statusReport})`);
const config = await getConfiguration();//获取配置信息
const previousLabelOrAppVersion = statusReport.previousLabelOrAppVersion;//当前版本的标签或者app的版本
const previousDeploymentKey = statusReport.previousDeploymentKey || config.deploymentKey;//部署的key
try {
if (statusReport.appVersion) {
const sdk = getPromisifiedSdk(requestFetchAdapter, config);//得到操作的sdk,具体方法在前一个目录下code-push/script/acquisition-sdk.js
await sdk.reportStatusDeploy(/* deployedPackage */ null, /* status */ null, previousLabelOrAppVersion, previousDeploymentKey);
} else {
const label = statusReport.package.label;
if (statusReport.status === "DeploymentSucceeded") {
log(`Reporting CodePush update success (${label})`);
} else {
log(`Reporting CodePush update rollback (${label})`);
}
config.deploymentKey = statusReport.package.deploymentKey;
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
await sdk.reportStatusDeploy(statusReport.package, statusReport.status, previousLabelOrAppVersion, previousDeploymentKey);
}
NativeCodePush.recordStatusReported(statusReport);//本地保存状态报告
resumeListener && AppState.removeEventListener("change", resumeListener);//移除对于change时间的监听
} catch (e) {
log(`Report status failed: ${JSON.stringify(statusReport)}`);
NativeCodePush.saveStatusReportForRetry(statusReport);//本地保存状态报告并在resume时重新调用tryReportStatus方法
// Try again when the app resumes
if (!resumeListener) {
resumeListener = async (newState) => {
if (newState !== "active") return;
const refreshedStatusReport = await NativeCodePush.getNewStatusReport();
if (refreshedStatusReport) {
tryReportStatus(refreshedStatusReport, resumeListener);
} else {
AppState.removeEventListener("change", resumeListener);
}
};
AppState.addEventListener("change", resumeListener);
}
}
}
notifyApplicationReady方法讲解完成
checkForUpdate
状态什么的处理完后就开始执行checkForUpdate
检查更新了。
async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchCallback = null) {
/*
//获取本地配置信息,最终调用NativeCodePush.getConfiguration方法
*/
const nativeConfig = await getConfiguration();
const config = deploymentKey ? { ...nativeConfig, ...{ deploymentKey } } : nativeConfig;
const sdk = getPromisifiedSdk(requestFetchAdapter, config);//和服务器交互的sdk
const localPackage = await module.exports.getCurrentPackage();//本地的package
let queryPackage;
if (localPackage) {
queryPackage = localPackage;
} else {
queryPackage = { appVersion: config.appVersion };
if (Platform.OS === "ios" && config.packageHash) {
queryPackage.packageHash = config.packageHash;
}
}
//从服务器检查更新
const update = await sdk.queryUpdateWithCurrentPackage(queryPackage);
/*
* 有四种结果:
* ----------------------------------------------------------------
* 1) 没有更新.
* 2) 服务器又更新,但是当前原生版本不支持此版本
* 3) 服务器告诉又更新,但是hsah值和本地是一样的(理论上不会出现)
* 4) 服务器有更新,但是hash值和当前运行的二进制(差异化的二进制hash)是一样的,这只会发生在Android手机,我理解的是iOS没有返回差异化的二进制。
*/
if (!update || update.updateAppVersion ||
localPackage && (update.packageHash === localPackage.packageHash) ||
(!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) {
if (update && update.updateAppVersion) {
//有更新,但是当前原生的版本不兼容
if (handleBinaryVersionMismatchCallback && typeof handleBinaryVersionMismatchCallback === "function") {
handleBinaryVersionMismatchCallback(update)
}
}
return null;
} else {
//和本地对比,返回此更新的信息
const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);//根据此hash判断是否是失败的更新(本地会存储之前更新的状态)
remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;
return remotePackage;
}
}
需要有两点需要注意:
1、remotePackage
不仅仅有此版本的信息,同时也将PackageMixins
的属性包含进来了,而PackageMixins
对应的是package-mixins.js
的代码,里面只有两个属性download
下载更新和install
安装更新。里面的逻辑在讲到下载时再细说。
2、上面提到的和服务器交互的sdkcode-push/script/acquisition-sdk.js
里面有queryUpdateWithCurrentPackage
查询更新,reportStatusDeploy
记录状态报告,reportStatusDownload
下载状态报告,这几个方法,具体细节可以看源代码,不复杂。
到目前为止,我们已经得到了更新的版本信息。往下执行会根据此版本信息来判断是否是忽略版本,需要下载。如果需要下载,则会执行前面提到的doDownloadAndInstall
方法。
doDownloadAndInstall
doDownloadAndInstall
里面代码逻辑很少,如下:
const doDownloadAndInstall = async () => {
syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);//同步状态
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;
};
我们在来分布解析下载和安装的逻辑,
download
download
的逻辑比较简单:
async download(downloadProgressCallback) {
if (!this.downloadUrl) {//下载地址为空,抛出异常
throw new Error("Cannot download an update without a download url");
}
let downloadProgressSubscription;//下载进度回调
if (downloadProgressCallback) {
const codePushEventEmitter = new NativeEventEmitter(NativeCodePush);
// CodePushDownloadProgress该事件会有原生下载时发出
downloadProgressSubscription = codePushEventEmitter.addListener(
"CodePushDownloadProgress",
downloadProgressCallback
);
}
// Use the downloaded package info. Native code will save the package info
// so that the client knows what the current package version is.
try {
const updatePackageCopy = Object.assign({}, this);
Object.keys(updatePackageCopy).forEach((key) => (typeof updatePackageCopy[key] === 'function') && delete updatePackageCopy[key]);
//调用本地下载
const downloadedPackage = await NativeCodePush.downloadUpdate(updatePackageCopy, !!downloadProgressCallback);
//记录下载状态
reportStatusDownload && reportStatusDownload(this);
//返回下载package信息
return { ...downloadedPackage, ...local };
} finally {
downloadProgressSubscription && downloadProgressSubscription.remove();
}
},
isPending: false // A remote package could never be in a pending state
};
上面NativeCodePush.downloadUpdate
是通过本地下载,本地的源码如下:
@ReactMethod
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());
//通过 mUpdateManager下载并监听进度回调,具mUpdateManager的方法和普通下载一样,不多细述。
mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
private boolean hasScheduledNextFrame = false;
private DownloadProgress latestDownloadProgress = null;
@Override
public void call(DownloadProgress downloadProgress) {
if (!notifyProgress) {
return;
}
latestDownloadProgress = downloadProgress;
// If the download is completed, synchronously send the last event.
if (latestDownloadProgress.isCompleted()) {
dispatchDownloadProgressEvent();
return;
}
if (hasScheduledNextFrame) {
return;
}
hasScheduledNextFrame = true;
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
@Override
public void run() {
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new ChoreographerCompat.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
if (!latestDownloadProgress.isCompleted()) {
dispatchDownloadProgressEvent();
}
hasScheduledNextFrame = false;
}
});
}
});
}
//重点是该方法,通过时间不断地发出下载的进度状态
public void dispatchDownloadProgressEvent() {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
}
}, mCodePush.getPublicKey());
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);
}
通过上面源码分析一下就可以知道下载流程,还是比较简单的,重点说一下通知下载进度给ReactNative的Component是通过getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
方法,而时间的event就是CodePushDownloadProgress
,这个上面已经提到过了。
下载完成之后就执行到
// 安装模式
resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;
syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
//准备安装
await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
});
方法了,我们再来看一下具体的安装方法源码:
const local = {
//传入参数
installMode安装模式,默认NativeCodePush.codePushInstallModeOnNextRestart,
minimumBackgroundDuration上面提到的,如果是onresume的安装模式,应用从上一次暂停到前台的时间间隔开始安装,默认为0。
updateInstalledCallback安装回调。
async install(installMode = NativeCodePush.codePushInstallModeOnNextRestart, minimumBackgroundDuration = 0, updateInstalledCallback) {
const localPackage = this;
const localPackageCopy = Object.assign({}, localPackage); // In dev mode, React Native deep freezes any object queued over the bridge
//调用本地的installUpdate方法,就是讲bundle文件准备好,以便重新加载,后面细述。
await NativeCodePush.installUpdate(localPackageCopy, installMode, minimumBackgroundDuration);
updateInstalledCallback && updateInstalledCallback();
if (installMode == NativeCodePush.codePushInstallModeImmediate) {
//如果安装模式是立即安装的话,直接重启
RestartManager.restartApp(false);
} else {
//如果不是,清除已经准备好的重启任务
RestartManager.clearPendingRestart();
localPackage.isPending = true; // Mark the package as pending since it hasn't been applied yet
}
},
isPending: false // A local package wouldn't be pending until it was installed
};
上面是CodePush中的安装方法,里面有两个点需要细述:
一、本地的NativeCodePush.installUpdate
方法:
@ReactMethod
public void installUpdate(final ReadableMap updatePackage, final int installMode, final int minimumBackgroundDuration, final Promise promise) {
AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
mUpdateManager.installPackage(CodePushUtils.convertReadableToJsonObject(updatePackage), mSettingsManager.isPendingUpdate(null));
String pendingHash = CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY);
if (pendingHash == null) {
throw new CodePushUnknownException("Update package to be installed has no hash.");
} else {
mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false);
}
if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() ||
// We also add the resume listener if the installMode is IMMEDIATE, because
// if the current activity is backgrounded, we want to reload the bundle when
// it comes back into the foreground.
installMode == CodePushInstallMode.IMMEDIATE.getValue() ||
installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
// Store the minimum duration on the native module as an instance
// variable instead of relying on a closure below, so that any
// subsequent resume-based installs could override it.
CodePushNativeModule.this.mMinimumBackgroundDuration = minimumBackgroundDuration;
if (mLifecycleEventListener == null) {
// Ensure we do not add the listener twice.
mLifecycleEventListener = new LifecycleEventListener() {
private Date lastPausedDate = null;
private Handler appSuspendHandler = new Handler(Looper.getMainLooper());
private Runnable loadBundleRunnable = new Runnable() {
@Override
public void run() {
CodePushUtils.log("Loading bundle on suspend");
loadBundle();
}
};
@Override
public void onHostResume() {
appSuspendHandler.removeCallbacks(loadBundleRunnable);
// As of RN 36, the resume handler fires immediately if the app is in
// the foreground, so explicitly wait for it to be backgrounded first
if (lastPausedDate != null) {
long durationInBackground = (new Date().getTime() - lastPausedDate.getTime()) / 1000;
if (installMode == CodePushInstallMode.IMMEDIATE.getValue()
|| durationInBackground >= CodePushNativeModule.this.mMinimumBackgroundDuration) {
CodePushUtils.log("Loading bundle on resume");
loadBundle();
}
}
}
@Override
public void onHostPause() {
// Save the current time so that when the app is later
// resumed, we can detect how long it was in the background.
lastPausedDate = new Date();
if (installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue() && mSettingsManager.isPendingUpdate(null)) {
appSuspendHandler.postDelayed(loadBundleRunnable, minimumBackgroundDuration * 1000);
}
}
@Override
public void onHostDestroy() {
}
};
getReactApplicationContext().addLifecycleEventListener(mLifecycleEventListener);
}
}
promise.resolve("");
return null;
}
};
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
这个里面有两个知识点:
1、是调用mUpdateManager.installPackage
来处理已经下载好的bundle文件,里面原理比较简单,可以自行看一下。每一个版本的文件命名都是以其hash值命名的。
2、建立一个监听,根据安装模式来判断是在onHostResume
中调用loadBundle
还是在onHostPause
中调用loadBundle
。
二、RestartManager
这个类:
RestartManager
这个类的源码在RestartManager.js
中,里面有两个要素,一个是重启任务的数组,这个不用细说;一个是重启的方法,也是调用NativeCodePush.restartApp(onlyIfUpdateIsPending)
原生的方法,源码如下:
@ReactMethod
public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) {
// If this is an unconditional restart request, or there
// is current pending update, then reload the app.
if (!onlyIfUpdateIsPending || mSettingsManager.isPendingUpdate(null)) {
loadBundle();
promise.resolve(true);
return;
}
promise.resolve(false);
}
我们看到了里面关键的方法是loadBundle
,这个方法获取了一个ReactInstanceManager
对象和最后更新下来的bundle文件路径latestJSBundleFile
,然后调用setJSBundle(instanceManager, latestJSBundleFile)
方法完成重启,这个方法源码如下:
private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException {
try {
JSBundleLoader latestJSBundleLoader;
//准备JSBundleLoader对象
if (latestJSBundleFile.toLowerCase().startsWith("assets://")) {
latestJSBundleLoader = JSBundleLoader.createAssetLoader(getReactApplicationContext(), latestJSBundleFile, false);
} else {
latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
}
//通过反射,得到 instanceManager中的mBundleLoader对象
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
bundleLoaderField.setAccessible(true);
//给instanceManager中重新设置mBundleLoader对象
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
} catch (Exception e) {
CodePushUtils.log("Unable to set JSBundle - CodePush may not support this version of React Native");
throw new IllegalAccessException("Could not setJSBundle");
}
}
上面提到的通过ReactInstanceManager
中的JSBundleLoader
完成bundle文件的重加载,具体的源码就不多分析,后续有时间可以梳理一下ReactNative如何加载bundle文件的文章。重新加载完成之后,通过调用instanceManager.recreateReactContextInBackground();
重新创建了ReactContext。
至此,一次完整的检查--下载--安装--更新流程完成了。
需要注意以下几个知识点:
1、ReactNative项目在进入后台是会发出background
事件通知,在进入前台时会发出active
事件通知。
2、热更新检查可以通过手动调用sync方法也可以在主界面的componentDidMount
中通过事件触发调用。