前面我們討論了如何啟動(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資源目錄



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)一步一步分析這段代碼
可以看到getJSBundleFile()默認(rèn)返回為null,這說(shuō)明我們默認(rèn)是進(jìn)入的else邏輯分支,即從assets目錄下去加載,加載assets目錄下哪個(gè)文件呢,看一下getBundleAssetName()的源代碼

我們截取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)。