React Native in Android的项目实战01--从RN的官方Demo讲起

首先我们从React Native官方的教程开始,先研究清楚RN默认新建的工程的结构,方便我们后续进行进一步的定制。
这一章讲的比较基础,不会过多展开。

基础知识

JavaScript和ECMAScript

如果只是从事客户端这边的开发,即使是和RN协同开发,负责中间桥的相关业务,其实不了解JavaScript也不要紧。不过简单了解下也有好处,而且我们最终的项目Demo也有一些RN端的代码。

推荐一本JavaScript入门书籍 JavaScript 语言入门教程

ECMAScript又是什么呢?引入一段说明:

ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。

目前可以简单的了解下ES6.ECMAScript 6 入门

node js,npm和package.json

node js官网

首先要了解node js是什么?

JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”(script language),指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序(比如浏览器)的“脚本”。

JavaScript 也是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多,只能用来做一些数学和逻辑运算。JavaScript 本身不提供任何与 I/O(输入/输出)相关的 API,都要靠宿主环境(host)提供,所以 JavaScript 只合适嵌入更大型的应用程序环境,去调用宿主环境提供的底层 API。

目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是浏览器,另外还有服务器环境,也就是 Node 项目。

也就是说,如果我们平时想练习js的语法,和python,java之类的语言可以直接在命令行执行不同,如果不借助浏览器的话,通常我们需要安装node,通过node来加载,编译和运行我们的js脚本。

npm是nodejs提供的包管理工具。类似brew之于mac os,apt之于linux。

对我们来说,其实主要就是两条命令:

1
2
npm install
npm start

关于npm更多的信息和package.json,可以参考下面这篇文章:

npm 与 package.json 快速入门

React和React Native

终于说到了我们的主角React Native。说之前有必要了解什么事React。React是针对View层的使用Javascript的UI组件开发库,从设计初衷来说,React是不关心View层具体技术实现的(比如是否web,android,iOS甚至是windows还是macOS)。

A JavaScript library for building user interfaces

ReactNative更多的是一个基于React进行iOS和Android原生App开发的框架,包括封装好的UI组件库,同时提供了React组件生成原生APP的能力。

A framework for building native apps using React

ReactNative

初探示例工程

项目初始化

按照教程的要求,安装好node,脚手架工具react-native-cli,然后利用react-native-cli快速搭建Demo工程。

1
react-native init AwesomeProject

执行成功后,我们可以看到文件夹下,包含着这些文件。

运行示例

运行rn工程

  • 在工程根目录下,执行命令行
1
npm install

安装node依赖包,成功后,执行

1
npm install

这时,系统会执行package.json下scripts里面的start对应的命令:

这时,rn工程这边的测试服务就开启了,在本地8081端口号上,rn运行起了Metro Bundler服务。关于Metro我们之后再讲。接下来只需要在手机上运行起安卓工程就可以进行测试环境的调试了。

运行安卓工程

本文默认是安卓工程师的角度,所以运行安卓工程就不多说了。额外的,我们需要执行一条命令:

1
adb reverse tcp:8081 tcp:8081

成功连接上本地的rn服务后,会看到Metro Bundler服务开始进行bundle打包,传入正在运行工程的安卓上,代码也就跑起来了。

了解工程结构

其中android和ios分别对应着两端的工程项目。package.json是node工程的配置文件, node_module里面是node依赖模块。app.json文件很简单,内容就是

1
2
3
4
{
"name": "AwesomeProject",
"displayName": "AwesomeProject"
}

其中name待会我们在安卓工程代码中会用到。我们重点关心下面两个文件:

App.js和index.js

先看index.js的代码:

1
2
3
4
5
6
7
8
9
/**
* @format
*/

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

很简单,就是根据app.json文件中的name,将App这个组件注册为app的根组件。接下来来看App.js的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow
*/

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';

const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});

type Props = {};
export default class App extends Component<Props> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

App是继承自Component的RN组件,在render()方法中,创建了相应的View,并最终作为app的根组件被注册进来。
这里就不多说了,毕竟我们的重点是Android工程中的代码。

Android工程

我们来看看RN的模版demo里面,在安卓项目中做了哪些事?

  • build gradle文件夹下,添加了RN库的依赖
1
implementation "com.facebook.react:react-native:+"

java文件只有两个MainApplication和MainActivity

MainActivity的代码很简单,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.awesomeproject;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "AwesomeProject";
}
}

这里示例直接继承了ReactActivity,ReactActivity内部封装了很多native和rn具体配置的接口设置,后面会详细讲到,这里先不多说了。getMainComponentName()方法返回的字符串正是前面rn工程根目录下,app.json文件里面的name。这两者需要确保对应的上。

再看MainApplication的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.awesomeproject;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}

@Override
protected String getJSMainModuleName() {
return "index";
}
};

@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}

@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}

可以看到,RN在Application里实现了ReactApplication,创建了ReactNativeHost对象并通过getReactNativeHost()方法返回。而ReactNativeHost这里实现了有三个方法:

getUseDeveloperSupport()方法只有当打的debug渠道的包,才会开启dev模式。

getJSMainModuleName()返回了”index”,看一下方法的注释:

1
2
3
4
5
6
7
8
9
/**
* Returns the name of the main module. Determines the URL used to fetch the JS bundle
* from the packager server. It is only used when dev support is enabled.
* This is the first file to be executed once the {@link ReactInstanceManager} is created.
* e.g. "index.android"
*/
protected String getJSMainModuleName() {
return "index.android";
}

这里的意思是,仅仅当dev模式下,手机上运行的进程会通过这个字符串名称,去加载此前rn传过来的bundle包的主模块。其实对应着的就是index.js文件。这里提到了ReactInstanceManager类,我们后面会具体讲。

getPackages()方法返回的是ReactPackage的列表。关于ReactPackage,其实也很简单,我们Android封装给RN的接口也好,View组件也好,都可以通过ReactPackage在启动RN服务的时候,注册给rn这边。这个我们也放到后面再说。

本节小结

这里简单介绍了rn的一些基础,如何创建,运行rn的模版工程,以及简单的梳理了rn模版工程的目录结构,主要代码的逻辑等。不过随之而来的问题也来了:

  • rn模版工程提供了Activity的实现方法,那么如果想要运行在fragment甚至view里,该如何处理
  • rn模版工程当中的ReactInstanceManagerReactPackageReactNativeHost都有什么作用?
  • Android客户端这边如何给rn提供接口?Android客户端这边如何给rn封装view?

……等等问题,可以看到,模版代码虽然简单,不过也由于封装层级比较高,很多细节目前还看不出来。所以下一章,我们就深入到内部,研究一下我们刚才提出的问题。