【稀饭】react native 实战系列教程之热更新原理分析与实现

本文详细分析了React Native Android应用中bundle的加载过程,探讨了热更新的实现,包括bundle patch的下载、加载流程。通过理解RN的加载机制,自建热更新方案,涉及冷加载和热加载策略,并提供了JS端与Android端的通信实现,实现完整的热更新流程。文章还介绍了项目源码地址,鼓励进一步优化和研究。
摘要由CSDN通过智能技术生成

很多人在技术选型的时候,会选择RN是因为它具有热更新,而且这是它的一个特性,所以实现起来会相对比较简单,不像原生那样,原生的热更新是一个大工程。那就目前来看,RN的热更新方案已有的,有微软的CodePush和reactnative中文网的pushy。实话说,这两个我还没有体验过。一来是当初选择RN是因为它不但拥有接近原生的体验感还具有热更新特性,那么就想自己来实现一下热更新,研究一下它的原理;二来,把自己的东西放在别人的服务器上总是觉得不是最好的办法,为什么不自己实现呢?因此,这篇文章便是记录自己的一些研究。

react native加载bundle过程

这篇文章是基于RN android 0.38.1

当我们创建完RN的基础项目后,打开android项目,项目只有MainActivity和MainApplication。

打开MainActivity,只有一个重写方法getMainComponentName,返回主组件名称,它继承于ReactActivity。

我们打开ReactActivity,它使用了代理模式,通过ReactActivityDelegate mDelegate对象将Activity需要处理的逻辑放在了代理对象内部,并通过getMainComponentName方法来设置(匹配)JS端AppRegistry.registerComponent端启动的入口组件。

Activity渲染出界面前,先是调用onCreate,所以我们进入代理对象的onCreate方法

//ReactActivityDelegate.java


protected void onCreate(Bundle savedInstanceState) {
    //判断是否支持dev模式,也就是RN常见的那个红色弹窗
    if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
      // Get permission to show redbox in dev builds.
      if (!Settings.canDrawOverlays(getContext())) {
        Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
        getContext().startActivity(serviceIntent);
        FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
        Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
      }
    }

    if (mMainComponentName != null) {
      //加载app
      loadApp(mMainComponentName);
    }
    //android模拟器dev 模式下,双击R重新加载
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }

上面的代码并没什么实质的东西,主要是调用了loadApp,我们跟进看下

protected void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    mReactRootView = createRootView();
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      appKey,
      getLaunchOptions());
    getPlainActivity().setContentView(mReactRootView);
  }

生成了一个ReactRootView对象,然后调用它的startReactApplication方法,最后setContentView将它设置为内容视图。再跟进startReactApplication里

//ReactRootView.java

public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle launchOptions) {
    UiThreadUtil.assertOnUiThread();

    // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
    // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
    // it in the case of re-creating the catalyst instance
    Assertions.assertCondition(
        mReactInstanceManager == null,
        "This root view has already been attached to a catalyst instance manager");
    //配置项管理
    mReactInstanceManager = reactInstanceManager;
    //入口组件名称
    mJSModuleName = moduleName;
    //用于传递给JS端初始组件props参数
    mLaunchOptions = launchOptions;
    //判断是否已经加载过
    if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
      //去加载bundle文件
      mReactInstanceManager.createReactContextInBackground();
    }

    // We need to wait for the initial onMeasure, if this view has not yet been measured, we set which
    // will make this view startReactApplication itself to instance manager once onMeasure is called.
    if (mWasMeasured) {
      //去渲染ReactRootView
      attachToReactInstanceManager();
    }
  }

startReactApplication传入三个参数,第一个ReactInstanceManager配置项管理类(非常重要);第二个是MainComponentName入口组件名称;第三个是Android Bundle类型,用于传递给JS端初始组件的props参数。首先,会根据ReactInstanceManager的配置去加载bundle过程,然后去渲染ReactRootView,将UI展示出来。现在我们不用去管attachToReactInstanceManager是如何去渲染ReactRootView,我们主要是研究如何加载bundle的,所以,我们跟进createReactContextInBackground,发现它是抽象类ReactInstanceManager的一个抽象方法。那它具体实现逻辑是什么呢?那我们就需要知道ReactInstanceManager的具体类的实例对象是谁了【1】。

好了,现在我们回到ReacActivityDelegate.java的loadApp,在ReactRootView的startReactApplication传入的ReactInstanceManager对象是getReactNativeHost().getReactInstanceManager()

//ReacActivityDelegate.java

mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      appKey,
      getLaunchOptions());

getReactNativeHost(),又是什么呢?

//从Application获取ReactNativeHost
protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
  }

所以我们在打开MainApplication类

public class MainApplication extends Application implements ReactApplication {
   

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage()
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }
}

MainApplication实现了ReactApplication接口,在getReactNativeHost()方法返回配置好的ReactNativeHost对象。由于我们把项目的Application配置成了MainApplication,所以ReacActivityDelegate的getReactNativeHost方法,返回的就是MainApplication mReactNativeHost对象。接着我们看下ReactNativeHost的getReactInstanceManager()方法,里面直接调用了createReactInstanceManager()方法,所以我们直接看createReactInstanceManager()

//ReactNativeHost.java

protected ReactInstanceManager createReactInstanceManager() {
    ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModuleName(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setUIImplementationProvider(getUIImplementationProvider())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

    for (ReactPackage reactPackage : getPackages()) {
      builder.addPackage(reactPackage);
    }

    String jsBundleFile = getJSBundleFile();
    if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
    } else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
    }
    return builder.build();
  }

createReactInstanceManager()通过使用ReactInstanceManager.Builder构造器来设置一些配置并生成对象。从这里看,我们可以从MainApplication的mReactNativeHost对象来配置ReactInstanceManager,比如JSMainModuleName、UseDeveloperSupport、Packages、JSBundleFile、BundleAssetName等,也可以重写createReactInstanceManager方法,自己手动生成ReactInstanceManager对象。

这里看下jsBundleFile的设置,先判断了getJSBundleFile()是否为null,项目默认是没有重写的,所以默认就是null,那么走builder.setBundleAssetName分支,看下getBundleAssetName(),默认是返回”index.android.bundle”

//builder.setBundleAssetName

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值