-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Labels
Description
我们知道react native app可以动态下发js bundle实现热更新,但是一般需要用户重启app才能应用更新。
React Native: 0.59.10
reload
RN内置的开发菜单有一个reload功能:
实际上,React Native是允许我们在不重启App的情况下刷新应用。
IOS
在IOS中,Reload功能最终是通过RCTBridge的reload方法来刷新应用:
- (void)reload
{
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
[self setUp];
});
}而在setUp中,RCTBridge可以通过一个delegate对象来更新bundleURL:
// Only update bundleURL from delegate if delegate bundleURL has changed
NSURL *previousDelegateURL = _delegateBundleURL;
_delegateBundleURL = [self.delegate sourceURLForBridge:self];
if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
_bundleURL = _delegateBundleURL;
}所以,我们所要做的就是改变一下RCTBridge的创建方式:
_bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];我们把AppDelegate作为RCTBridge代理对象,然后实现一个sourceURLForBridge方法:
#pragma mark - sourceURL
-(NSURL*)sourceURLForBridge:(RCTBridge*)bridge{
NSURL *jsCodeLocation = nil;
#ifdef DEBUG
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else
HCWebAppInfo *appInfo = [HCWebAppInfo appInfoWithId:@"gfclickeggs-ios"];
[appInfo selfExaminationVersionAndStartPage];
if ([appInfo isCurrentLocalContentValid]) {
jsCodeLocation = appInfo.startPageURL;
}
#endif
return jsCodeLocation;
}这样bridge就可以在reload时更新bundleURL,实现应用刷新了。
Android
在Android中,Reload功能最终是通过ReactInstanceManager的recreateReactContextInBackground方法来刷新应用:
@ThreadConfined(UI)
private void recreateReactContextInBackground(
JavaScriptExecutor.Factory jsExecutorFactory,
JSBundleLoader jsBundleLoader) {
Log.d(ReactConstants.TAG, "ReactInstanceManager.recreateReactContextInBackground()");
UiThreadUtil.assertOnUiThread();
final ReactContextInitParams initParams = new ReactContextInitParams(
jsExecutorFactory,
jsBundleLoader);
if (mCreateReactContextThread == null) {
runCreateReactContextOnNewThread(initParams);
} else {
mPendingReactContextInitParams = initParams;
}
}但是这个方法是private的,不对外开放。
ReactInstanceManager同时提供了一个不带参数的recreateReactContextInBackground实现:
/**
* Recreate the react application and context. This should be called if configuration has
* changed or the developer has requested the app to be reloaded. It should only be called after
* an initial call to createReactContextInBackground.
*
* Called from UI thread.
*/
@ThreadConfined(UI)
public void recreateReactContextInBackground() {
Assertions.assertCondition(
mHasStartedCreatingInitialContext,
"recreateReactContextInBackground should only be called after the initial " +
"createReactContextInBackground call.");
recreateReactContextInBackgroundInner();
}这个方法是public的,可以被外部调用。
不过在调用之前,我们需要先通过代码反射技术更新一下mBundleLoader:
// update js bundle
private void updateJSBundle(ReactInstanceManager reactInstanceManager, String latestJSBundleFile) throws NoSuchFieldException, IllegalAccessException {
try {
ArmsLog.i(ArmsConstant.TAG, "update js bundle");
JSBundleLoader latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
Field bundleLoaderField = reactInstanceManager.getClass().getDeclaredField("mBundleLoader");
bundleLoaderField.setAccessible(true);
bundleLoaderField.set(reactInstanceManager, latestJSBundleLoader);
} catch (Exception e) {
throw new IllegalAccessException("Could not updateJSBundle");
}
}然后就可以在主线程中调用recreateReactContextInBackground:
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
ArmsLog.i(ArmsConstant.TAG, "reloaded");
reactInstanceManager.recreateReactContextInBackground();
} catch (Exception e) {
ArmsLog.e(ArmsConstant.TAG, e.toString());;
}
}
});这样就可以实现应用刷新了。
