美文网首页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