Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Native不重启App实现热更新 #95

Open
hushicai opened this issue Oct 22, 2021 · 0 comments
Open

React Native不重启App实现热更新 #95

hushicai opened this issue Oct 22, 2021 · 0 comments

Comments

@hushicai
Copy link
Owner

hushicai commented Oct 22, 2021

我们知道react native app可以动态下发js bundle实现热更新,但是一般需要用户重启app才能应用更新。

React Native: 0.59.10

reload

RN内置的开发菜单有一个reload功能:

image

实际上,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());;
        }
    }
});

这样就可以实现应用刷新了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant