React Native
Learn once, write anywhere.
React Native为前端开发工程师开发Native应用提供了一种能力。保障开发效率,同时兼顾平台的性能。
React
相比于传统的DOM的优势
- VirtualDOM 高效diff算法(O(n^3) -> O(n)),实现局部刷新
- 对外暴露Component,js & css 统一管理
相比于传统WebView H5优势
- 不需要兼容各个WebView了
- 效率会更高,它最终会调用native组件本地的渲染。
Get Started
参考官网或者中文网的Get Started,里面介绍了如何配置环境,以及通过简单的命令很快就能run出一个简单的示例工程,然后在index.ios.js
或者index.android.js
里简单改吧改吧就能开启React Native开发之旅了。
NPM(Yarn)
npm 全称是 Node Package Manager,是随同NodeJS一起安装的包管理工具,包括包(模块)的下载,安装, 和发布,以及管理。Yarn是Facebook提供的npm的替代工具,一般我们通过一个package.json
文件做配置。当我们执行npm init
命令的时候,会自动提示我们输入以下信息,得到package.json
。
{
"name": "ls",
"version": "1.0.0",
"description": "this is a test",
"main": "index.js",
"dependencies": {},
"devDependencies": {},
"scripts": {
"test": "test"
},
"repository": {
"type": "git",
"url": "\"\""
},
"keywords": [
"123"
],
"author": "sl",
"license": "UNLICENSED"
}
对应我们在React Native中的配置,举例
{
"name": "RN",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.4.2",
"react-native": "0.44.0",
"react-native-linear-gradient": "^2.0.0"
}
}
当我们执行npm start
命令的时候,其实执行的就是node node_modules/react-native/local-cli/cli.js start
,其中cli
是Command line interface
的缩写,在local-cli
里相当丰富的命令。
构建Android应用
1、新建一个普通的Adroid工程,以ReactApplication
为例。
2、在工程目录里配置package.json
,如上配置即可,执行npm install
,安装npm组件和下载react native 包。
3、在Project的build.gradle配置,上步会把react-native的相关代码打成arr,jar,pom等格式放到/node_modules/react-native/android目录下,新版React Native只通过npm发布,所以,需要在jcenter外配置本地仓库,获取依赖包。
repositories {
jcenter()
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$projectDir/../node_modules/react-native/android"
}
}
4、在主module里配置
compile "com.facebook.react:react-native:0.44.0"
5、 工程目录下,新建index.android.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image
} from 'react-native';
class App extends Component {
render() {
return (
Welcome to React Native
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
note: {
fontSize: 20
},
sss: {
width : 200,
height : 100
},
});
AppRegistry.registerComponent('react-native-module', () => App);
在React Native中,JS端对外暴露的是一个个Component
(组件),AppRegistry
模块是React Native应用运行JS的入口,所有的根组件都可以registerComponent
来注册,这里是App
,react-native-module
是对外(Native)暴露的组件的名称。
6、自定义ReactInstanceManager
可以理解成React的超级管家,我们可以进行各种配置。 然后内部会做一系列的初始化工作:包括index.android.bundle
的加载,js和native module的注册等等,在调试模式下会生成DevSupportManager
,所有调试相关的如:调试弹窗(RedBox),与本地server端的通信,bundle的更新与加载都由它统一管理。
@Override
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager reactInstanceManager = ReactInstanceManager.builder().setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.setJSBundleFile("XXX")
.addPackage(new MainReactPackage())
.addPackage(new ReactCellPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.setUIImplementationProvider(new MyUIImplementationProvider())
.build();
return reactInstanceManager;
简单来说index.android.bundle
就是对React Native里JS端代码的打包。
1) 一般release打包会放到assets目录下,然后它的资源文件也会放到assets目录下。通过设置BundleAssetName就可以利用AssetManager读取bundle,看到XX工程里的bundle接近1MB。这也是我们后面可以优化的点。
- bundle包的拆分,按需加载,优化页面的加载时间,携程专门有篇文章是讲bundle的拆分的。
- 提前加载bundle
- 动态下发
2)通过设置JSBundleFile,可以读取本地的bundle,RN的热更新就可以通过这个下发,然后Native重新加载一下即可。
3)ReactCellPackage就是我们自定义的Module和组件。
7、加载React布局
1)继承ReactActiviy
public class MyReactActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "react-native-module";
}
}
ReactActivity是FB为我们提供的基础类,内部会在根布局里设置成ReactRootView(实际上就是一个FrameLayout),并做一些RN的初始化工作。JS端的Component最终会被加载成为ReactRootView的子布局。
- 自定义ReactRootView加载
ReactRootView可以设置成根布局或者页面的一个子View。startReactApplication
方法首次就会异步做一些初始化工作,ReactActivity里也是调用这个方法。
public abstract class BaseActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler{
private ReactRootView mReactRootView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
mReactRootView.startReactApplication(SSApplication.inst().getReactNativeHost().getReactInstanceManager(), getRNComponentName(), null);
setContentView(mReactRootView);
}
protected abstract String getRNComponentName();
@Override
protected void onResume() {
super.onResume();
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onHostResume(this, this);
}
@Override
protected void onPause() {
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onHostPause();
super.onPause();
}
@Override
protected void onDestroy() {
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onHostDestroy(this);
SSApplication.inst().getReactNativeHost().getReactInstanceManager().detachRootView(mReactRootView);
super.onDestroy();
}
@Override
public void onBackPressed() {
super.onBackPressed();
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onBackPressed();
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
8、自定义ReactPackage
public class ReactCellPackage implements ReactPackage {
//需要注册的自定义的NativeModule列表
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
//JS Module 接口,需要在JS中做对等实现,并打包的bundle中。
@Override
public List> createJSModules() {
return Collections.emptyList();
}
//自定义View的ViewManager列表
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Arrays.asList(new ReactCellViewManager()
, new MyReactImageViewManager()
, new MyReactTextViewManager()
, new RealRecyclerItemViewManager()
, new RealRecyclerViewManager());
}
}
至此我们就可以开始玩React Native了。
React Native 通信
这张图比较省略,Java和JS之间还有一个Bridge(C/C++层),简单理解来说,Java和JS各自维护了一份对等的映射config,比如Java传入A就可以调用js config表里,A对应的模块。反之亦然。这个配置是在初始化React Native 上下文的时候生成的。
初始化流程
1)生成ReactApplicationContext实例.
2)初始化3个线程,UI线程,NativeModule线程,JSThread,以及 jsExecutor。
3)处理各类Package,解析并在Java侧维护了各个JavaScriptModule和NativeModule表
- initBridge,将NativeModule表下发给C/C++层
- runJSBundle 加载JSBundle
Java -> JS
可以这样理解,JavaScriptModule就是JS暴露给Java的调用接口。当Java调用JS方法的时候:
- 先找到对应的JS Module,并填充相应的参数。
2)Proxy通过jni调用生成JS Module的实例
3)jni调用 实例,方法,参数作为产生 下发的到Bridge层
4)Bridge层 执行对应的 JS 方法
JS -> JAVA
- JS Module的方法调用时,会把module,methond,params放入到MessageQueue里
2)当message的消息间隔大于5ms时,触发Queue的情况操作。
3)Bridge层 通过JNI找到 对应的NativeModule
4)NativeModule执行相应的method.
View(UIManagerModule)
JS端触发view刷新等都会走到UIManagerModule中,这里会将这些“消息”封装成一个一个Operation,由UIViewOperationQueue统一调度。这个就回到在UI Thread里了,后续处理的就是Native本地的渲染了。
痛点
性能
React Native相比于Native性能还是偏弱。包括渲染效率,加载延时,以及View的复用等等,大厂们纷纷都在对React Native 做特定场景的优化。比如是listview。目前我们也在做listview的优化。
稳定性
1、crash
2、国产手机的兼容性
AnyWay
React Native 确实为Native 开发提供一种思路,也许后面性能不再是瓶颈,那么未来是光明的。眼下,我们还是要多踩坑。
参考
React 源码剖析系列 - 不可思议的 react diff
React Native Android 从学车到补胎和成功发车经历
React Native Android 源码框架浅析
其实没那么复杂!探究react-native通信机制