RN加載Bundle的方式(三)

前面我們討論了如何啟動(dòng)服務(wù)來(lái)展示js頁(yè)面,那么能不能不開啟服務(wù)就能實(shí)現(xiàn)同樣的功能呢,答案顯而易見。
首先我們來(lái)看一段源碼,這段代碼主要功能是生成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();
  }

這里我們著重關(guān)注下setJSBundleFile和setBundleAssetName兩個(gè)方法,其分別對(duì)應(yīng)的是從文件夾路徑和Assets目錄去加載bundle。

那么這邊就有兩個(gè)疑問(wèn):
1.bundle文件是怎么生成的
2.我們是如何選擇去文件夾路徑還是去Assets路徑讀取bundle

那么接下來(lái),我將帶領(lǐng)大家去解釋這兩個(gè)疑問(wèn)
1.先來(lái)看看打包命令
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

幾個(gè)主要的參數(shù):
--entry-file: RN的入口文件
--bundle-output : 輸出bundle文件的輸出路徑
--assets-dest:輸出的asset資源目錄

看一下目錄結(jié)構(gòu):
1525245648853.jpg

針對(duì)這個(gè)項(xiàng)目,完整打包命令如下圖
11.png

最終效果,在output目錄下生成了bundle文件以及資源文件
1525246039110.jpg

Nice,到目前為止,我們搞定了第一個(gè)問(wèn)題,萬(wàn)里長(zhǎng)征才踏出第一步,我們繼續(xù)

我們注意到有這么一段判斷邏輯

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

現(xiàn)在我們來(lái)一步一步分析這段代碼
1525246399793.jpg

可以看到getJSBundleFile()默認(rèn)返回為null,這說(shuō)明我們默認(rèn)是進(jìn)入的else邏輯分支,即從assets目錄下去加載,加載assets目錄下哪個(gè)文件呢,看一下getBundleAssetName()的源代碼
1525248766275.jpg
可以發(fā)現(xiàn)默認(rèn)返回index.android.bundle文件,即默認(rèn)加載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()這一行代碼
我們繼續(xù)跟蹤進(jìn)入getReactNativeHost()

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

發(fā)現(xiàn)其最終是調(diào)用的是Application里的getReactNativeHost()方法,ok,那我們就去自定義Application里實(shí)現(xiàn)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()方法,假設(shè)這里xxx的路徑存在,那么就返回xxx這個(gè)路徑,結(jié)合
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
} else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
這段代碼可以知道jsBundleFile不為null,那么其走的是if邏輯分支,從而實(shí)現(xiàn)了從文件夾里加載bundle

通過(guò)以上的分析,我們對(duì)RN的加載方式有了一個(gè)初步的認(rèn)識(shí),下面來(lái)總結(jié)下加載的過(guò)程:

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方法,發(fā)現(xiàn)其使用了代理的方式傳遞到了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();
 }

這里正常運(yùn)行會(huì)調(diào)到loadApp方法,ok,繼續(xù)往下跟蹤

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設(shè)置布局,該布局在這里為ReactRootView,緊接著最為關(guān)鍵的地方就是startReactApplication,這個(gè)后面的文章詳細(xì)分析,這里只關(guān)注getReactNativeHost().getReactInstanceManager(),跟蹤進(jìn)入

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

發(fā)現(xiàn)就到了文章開頭提到的createReactInstanceManager方法,里面處理了加載bundle的邏輯,大家可以回過(guò)頭再去看,ok,大致的分析完畢,路要一步一步走,先理解這些吧,祝你們好運(yùn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容