美文网首页Hybrid开发React-Native随笔
RN加载Bundle的方式(三)

RN加载Bundle的方式(三)

作者: sleeping_7e17 | 来源:发表于2018-05-02 16:41 被阅读1042次

前面我们讨论了如何启动服务来展示js页面,那么能不能不开启服务就能实现同样的功能呢,答案显而易见。
首先我们来看一段源码,这段代码主要功能是生成ReactInstanceManager:

protected ReactInstanceManager createReactInstanceManager() {
    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModulePath(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
      .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();
  }

这里我们着重关注下setJSBundleFile和setBundleAssetName两个方法,其分别对应的是从文件夹路径和Assets目录去加载bundle。

那么这边就有两个疑问:
1.bundle文件是怎么生成的
2.我们是如何选择去文件夹路径还是去Assets路径读取bundle

那么接下来,我将带领大家去解释这两个疑问
1.先来看看打包命令
react-native bundle -h

Options:

--entry-file <path>                Path to the root JS file, either absolute or relative to JS root
--platform [string]                Either "ios" or "android" (default: ios)
--transformer [string]             Specify a custom transformer to be used
--dev [boolean]                    If false, warnings are disabled and the bundle is minified (default: true)
--bundle-output <string>           File name where to store the resulting bundle, ex. /tmp/groups.bundle
--bundle-encoding [string]         Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer). (default: utf8)
--max-workers [number]             Specifies the maximum number of workers the worker-pool will spawn for transforming files. This defaults to the number of the cores available on your machine.
--sourcemap-output [string]        File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map
--sourcemap-sources-root [string]  Path to make sourcemap's sources entries relative to, ex. /root/dir
--sourcemap-use-absolute-path      Report SourceMapURL using its full path
--assets-dest [string]             Directory name where to store assets referenced in the bundle
--verbose                          Enables logging
--reset-cache                      Removes cached files
--read-global-cache                Try to fetch transformed JS code from the global cache, if configured.
--config [string]                  Path to the CLI configuration file
-h, --help                         output usage information

几个主要的参数:
--entry-file: RN的入口文件
--bundle-output : 输出bundle文件的输出路径
--assets-dest:输出的asset资源目录

看一下目录结构: 1525245648853.jpg 针对这个项目,完整打包命令如下图 11.png 最终效果,在output目录下生成了bundle文件以及资源文件 1525246039110.jpg

Nice,到目前为止,我们搞定了第一个问题,万里长征才踏出第一步,我们继续

我们注意到有这么一段判断逻辑

String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
} else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
现在我们来一步一步分析这段代码 1525246399793.jpg
可以看到getJSBundleFile()默认返回为null,这说明我们默认是进入的else逻辑分支,即从assets目录下去加载,加载assets目录下哪个文件呢,看一下getBundleAssetName()的源代码 1525248766275.jpg 可以发现默认返回index.android.bundle文件,即默认加载assets目录下的index.android.bundle。那怎么样才能加载文件夹路径下的bundle呢,我们回归源码

我们截取ReactActivityDelegate文件里部分代码

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);
}

注意getReactNativeHost().getReactInstanceManager()这一行代码
我们继续跟踪进入getReactNativeHost()

protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}

发现其最终是调用的是Application里的getReactNativeHost()方法,ok,那我们就去自定义Application里实现ReactApplication重写getReactNativeHost()方法,代码如下

public class MyApplication extends MultiDexApplication implements ReactApplication {
    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Nullable
        @Override
        protected String getJSBundleFile() {
            File file = new File(xxx);
            if (file != null && file.exists()) {
                return xxx;
            } else {
                return super.getJSBundleFile();
            }
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }

        @Override
        public boolean getUseDeveloperSupport() {
             return false;
        }

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

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }
}
注意xxx的地方即为文件夹下的bundle路径,可以看到除了重写getReactNativeHost()方法,我们还重写了getJSBundleFile()方法,假设这里xxx的路径存在,那么就返回xxx这个路径,结合
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
} else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
这段代码可以知道jsBundleFile不为null,那么其走的是if逻辑分支,从而实现了从文件夹里加载bundle

通过以上的分析,我们对RN的加载方式有了一个初步的认识,下面来总结下加载的过程:

public abstract class ReactActivity extends Activity
    implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {

  private final ReactActivityDelegate mDelegate;

  protected ReactActivity() {
    mDelegate = createReactActivityDelegate();
  }

  /**
   * Returns the name of the main component registered from JavaScript.
   * This is used to schedule rendering of the component.
   * e.g. "MoviesApp"
   */
  protected @Nullable String getMainComponentName() {
    return null;
  }

  /**
   * Called at construction time, override if you have a custom delegate implementation.
   */
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName());
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mDelegate.onCreate(savedInstanceState);
  }
  ......
}

我们省略部分代码,着重看ReactActivity的onCreate方法,发现其使用了代理的方式传递到了ReactActivityDelegate=>onCreate方法

protected void onCreate(Bundle savedInstanceState) {
   boolean needsOverlayPermission = false;
   if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
     // Get permission to show redbox in dev builds.
     if (!Settings.canDrawOverlays(getContext())) {
       needsOverlayPermission = true;
       Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getContext().getPackageName()));
       FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
       Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
       ((Activity) getContext()).startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);
     }
   }

   if (mMainComponentName != null && !needsOverlayPermission) {
     loadApp(mMainComponentName);
   }
   mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
 }

这里正常运行会调到loadApp方法,ok,继续往下跟踪

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);
  }

这里我们看到了熟悉的方法setContentView,即给Activity设置布局,该布局在这里为ReactRootView,紧接着最为关键的地方就是startReactApplication,这个后面的文章详细分析,这里只关注getReactNativeHost().getReactInstanceManager(),跟踪进入

 public ReactInstanceManager getReactInstanceManager() {
    if (mReactInstanceManager == null) {
      mReactInstanceManager = createReactInstanceManager();
    }
    return mReactInstanceManager;
  }

发现就到了文章开头提到的createReactInstanceManager方法,里面处理了加载bundle的逻辑,大家可以回过头再去看,ok,大致的分析完毕,路要一步一步走,先理解这些吧,祝你们好运。

相关文章

网友评论

    本文标题:RN加载Bundle的方式(三)

    本文链接:https://www.haomeiwen.com/subject/relcrftx.html