RN加载Bundle的方式(三)

前面我们讨论了如何启动服务来展示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,大致的分析完毕,路要一步一步走,先理解这些吧,祝你们好运。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345