在本地开发时, React Native 是加载本地Node服务, 可以通过npm start 启动, package.json 代码如下:
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
}
首次在电脑上面打开该地址,被庞大的源代码吓一跳。一个简单的HelloWorld App 足足有5万多行JS代码(开发模式)。仔细分析和梳理调用流程后,也没有那么的恐怖。代码主要包括React源码, 所有初始化定义的Native组件定义,Bridge层调用相关的MessageQueue,NativeModules,原生JS常用方法polyfill等代码定义实现。
如果是正式发布包,在应用运行时,是不存在本地nodejs服务器这个概念的,所以JS整合文件都是预先打包到asset资源文件里的,减少网络下载JS耗时。当然也可以从网络下载JSBundle,这时就需要考虑首次启动下载JSBundle的网络耗时和下载失败的情况处理。在项目开发时,其实可以在打包时内置一份JSBundle文件,然后启动后异步去下载最新JSBundle,下次启动时就可以加载新的JSBundle。
针对如此庞大的JSBundle文件,首次启动加载和解析的性能如何呢?
一.远程本地调试
通过创建ReactInstanceManager.builder 设置setUseDeveloperSupport(true)支持远程本地调试。
远程调试时,如果是通过Android studio 打包时,可以先通过npm start启动启动本地服务,启动后服务地址:
如果想加载asset下的JSBundle文件,需要先把JSBundle打到本地assets目录下面,可以通过react-native bundle实现。命令自动会分析图片依赖,然后拷贝到res目录下面。
react-native bundle --entry-file ./index.android.js --bundle-output ./app/src/main/assets/index.android.jsbundle --platform android --assets-dest ./app/src/main/res/ --dev false
然后setUseDeveloperSupport(false),之后重新打包即可。
二.远程加载JSBundle文件
在ReactInstanceManager 类里面提供了setJSBundleFile方法,这个就是动态更新的入口.
public Builder setJSBundleFile(String jsBundleFile) {
mJSBundleFile = jsBundleFile;
return this;
}
由于React Native加载的js文件都打包在bundle中,通过这个方法,可以设置app加载的bundle来源。若检测到远端存在更新的bundle文件,下载好后重新加载即可。
在ReactInstanceManager 类里面提供了recreateReactContextInBackground方法, 可以通过调用该方法重新加载JSBundle文件.
private void recreateReactContextInBackground(JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
UiThreadUtil.assertOnUiThread();
ReactContextInitParams initParams = new ReactContextInitParams(jsExecutor, jsBundleLoader);
if (!mIsContextInitAsyncTaskRunning) {
// No background task to create react context is currently running, create and execute one.
ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask();
initTask.execute(initParams);
mIsContextInitAsyncTaskRunning = true;
} else {
// Background task is currently running, queue up most recent init params to recreate context
// once task completes.
mPendingReactContextInitParams = initParams;
}
}
目前该方法访问权限上private,需要通过反射才能调用, 希望未来 React Native 能够从官方支持. 代码如下:
private void onJSBundleLoadedFromServer() {
try {
Class> RIManagerClazz = mReactInstanceManager.getClass();
Method method = RIManagerClazz.getDeclaredMethod("recreateReactContextInBackground", JavaScriptExecutor.class, JSBundleLoader.class);
method.setAccessible(true);
method.invoke(mReactInstanceManager, new JSCJavaScriptExecutor(),
JSBundleLoader.createFileLoader(getApplicationContext(), JS_BUNDLE_LOCAL_PATH));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
三.开启ReactNative日志打印
React Native 增加了关键日志自定义listener回调接口MarkerListener,只要在React Activity onCreate设置ReactMarker.setMarkerListener方法,
实现MarkerListener接口logMarker方法,即可实现控制台日志打印。我们可以记录下每个关键路径的当前时间,即可计算出每个关键路径的执行时间。
ReactMarker.setMarkerListener(new ReactMarker.MarkerListener(){
@Override
public void logMarker(String name) {
Log.i("ReactNativeJS", name.toLowerCase() + " cost:" + System.currentTimeMillis());
}
});
09-03 20:33:47.637 I/ReactNativeJS: process_packages_end cost:1472387627637
09-03 20:33:47.637 I/ReactNativeJS: build_native_module_registry_start cost:1472387627637
09-03 20:33:47.639 I/ReactNativeJS: build_native_module_registry_end cost:1472387627639
09-03 20:33:47.646 I/ReactNativeJS: create_catalyst_instance_start cost:1472387627646
09-03 20:33:47.688 I/ReactNativeJS: create_catalyst_instance_end cost:1472387627688
09-03 20:33:47.688 I/ReactNativeJS: run_js_bundle_start cost:1472387627688
09-03 20:33:47.717 I/ReactNativeJS: loadapplicationscript_startstringconvert cost:1472387627717
09-03 20:33:47.833 I/ReactNativeJS: loadapplicationscript_endstringconvert cost:1472387627832
09-03 20:33:48.787 I/ReactNativeJS: create_react_context_end cost:1472387628786
09-03 20:33:48.787 I/ReactNativeJS: run_js_bundle_end c