前面我们讨论了如何启动服务来展示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资源目录
Nice,到目前为止,我们搞定了第一个问题,万里长征才踏出第一步,我们继续
我们注意到有这么一段判断逻辑
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
现在我们来一步一步分析这段代码可以看到getJSBundleFile()默认返回为null,这说明我们默认是进入的else逻辑分支,即从assets目录下去加载,加载assets目录下哪个文件呢,看一下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()这一行代码
我们继续跟踪进入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,大致的分析完毕,路要一步一步走,先理解这些吧,祝你们好运。