Skip to content

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

@hushicai

Description

@hushicai

我们知道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());;
        }
    }
});

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions