原生 Android 项目集成 React Native

创建一个 React Native 项目并写一个纯的 React Native 应用可以参考官方指南

iOS 项目集成 React Native 可以参考 原生 iOS 项目集成 React Native

本文主要介绍原生 Android 项目集成 React Native 并用于部分页面开发的流程。开发环境为 macOS 10.12、Android Studio 2.2.1、React Native 0.35.0。而官方给出的 植入原生 Android 应用指南 只对应到 0.28 版本。最新版(当前为 0.35)的集成方案稍微有些变动。

1. 创建/修改 Android 项目

用 Android Studio 创建一个 Android 项目,注意 Minimum SDK 要设置为 API 16 或以上,因为 React Native 要求 Android 4.1 及以上的环境。

如果现有 Android 项目且 Minimum API 小于16则修改 Minimum SDK 到16,注意部分 API 变化。

2. 添加 package.json

在 Android 项目根目录新建文件 package.json,内容如下(参考 react-native init 生成的 package.json 文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "react-native-sample",
"version": "0.0.1",
"description": "sample of react native embedding android",
"main": "index.android.js",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"author": "danke77",
"license": "ISC",
"dependencies": {
"react": "^15.3.2",
"react-native": "^0.35.0"
},
"devDependencies": {
}
}

执行 npm install 就可以安装 dependencies 下的 npm 组件了。

这个时候在 Android 项目根目录就生成了 node_modules/ 文件夹,里面就是一些用到的组件。

.gitignore 中添加

1
2
3
# node.js
# node_modules/
npm-debug.log

执行 react-native upgrade 可以更新已有组件。

3. 添加 index.android.js

在 Android 项目根目录创建目录 js/,js 相关的代码就放在这个文件夹下。

js/ 下添加 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
import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'
export default class extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.text}>
Hello React Native!
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ffffff'
},
text: {
fontSize: 20,
color: '#333333'
}
})

在 Android 项目根目录新建文件 index.android.js,内容如下

1
2
3
4
import { AppRegistry } from 'react-native'
import App from './js/App'
AppRegistry.registerComponent('navigation', () => App)

这里的 navigation 一般会根据模块功能命名,后面还会用到。

当然也可以把 App.js 的内容写在 index.android.js 里,但这样写更清晰一些,尤其是项目大了文件多的情况。

4. Android 项目添加依赖

4.1 project 级别的 build.gralde

Android 默认的依赖包的源 jcenter() 不包含最新版的 React Native,新版的 React Native 都只在 npm 里发布,因此要依赖 node_modules/ 下的东西。

在 Android 项目根目录下的 build.gradle 文件添加如下内容

1
2
3
4
5
6
7
8
allprojects {
repositories {
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/node_modules/react-native/android"
}
}
}

4.2 module 级别的 build.gradle

在 Android 项目 app 目录下的 build.gradle 文件添加如下内容

1
2
3
4
5
6
7
8
9
defaultConfig {
// ...
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
// react native
compile 'com.facebook.react:react-native:0.35.0' // From node_modules

这里版本号要和 package.json 里的一致。

5. React Native 相关的 Activity 和 Application

5.1 Activity

创建一个继承自 com.facebook.react.ReactActivity 的 Activity

1
2
3
4
5
6
7
8
9
10
11
public class HelloReactActivity 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 "navigation";
}
}

重写 getMainComponentName() 方法,返回的字符串必须和前面的 AppRegistry.registerComponent('navigation', () => App) 里的 navigation 对应,表示该 Activity 会显示对应组件里的内容。

5.2 Application

Application 需要实现 com.facebook.react.ReactApplication 接口,并实现其方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new OtherReactPackage()
// ...
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}

getUseDeveloperSupport() 表示是否启动开发者模式。

getPackages() 是引用的模块列表,默认需要添加 MainReactPackage,如果需要在 js 里调用原生 Java 模块,需要添加自定义的模块(如 OtherReactPackage)。

新版这两个方法是写在 Application 里的,旧版都是些在 Activity 里的。

5.3 AndroidManifest.xml

AndroidManifest.xml 里需要添加自己创建的 Activity 和 React Native 提供的 DevSettingsActivity,还需要添加两个权限。

1
2
3
4
5
6
7
8
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application ... >
...
<activity android:name=".HelloReactActivity" />
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

HelloReactActivity 是自己创建的页面,DevSettingsActivity 是开发调试需要用到的设置页面。

android.permission.INTERNET 用于开发调试,android.permission.SYSTEM_ALERT_WINDOW 用于显示悬浮窗。

6. 启动服务

debug 模式下需要在 package.json 所在目录下执行 npm start,它等效于 package.json 里的 node node_modules/react-native/local-cli/cli.js start,相当于启动一个本地服务。

Terminal 显示如下表示服务已正常启动

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
> react-native-module@0.0.1 start /Users/danke77/Projects/react-native/HelloReactNative
> node node_modules/react-native/local-cli/cli.js start
Scanning 581 folders for symlinks in /Users/danke77/Projects/react-native/HelloReactNative/node_modules (17ms)
┌────────────────────────────────────────────────────────────────────────────┐
│ Running packager on port 8081. │
│ │
│ Keep this packager running while developing on any JS projects. Feel │
│ free to close this tab and run your own packager instance if you │
│ prefer. │
│ │
│ https://github.com/facebook/react-native │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Looking for JS files in
/Users/danke77/Projects/react-native/HelloReactNative
[2016-10-17 17:06:48] <START> Building Dependency Graph
[2016-10-17 17:06:48] <START> Crawling File System
[Hot Module Replacement] Server listening on /hot
React packager ready.
[2016-10-17 17:06:49] <END> Crawling File System (966ms)
[2016-10-17 17:06:49] <START> Building in-memory fs for JavaScript
[2016-10-17 17:06:49] <END> Building in-memory fs for JavaScript (260ms)
[2016-10-17 17:06:49] <START> Building in-memory fs for Assets
[2016-10-17 17:06:50] <END> Building in-memory fs for Assets (138ms)
[2016-10-17 17:06:50] <START> Building Haste Map
[2016-10-17 17:06:50] <START> Building (deprecated) Asset Map
[2016-10-17 17:06:50] <END> Building (deprecated) Asset Map (104ms)
[2016-10-17 17:06:50] <END> Building Haste Map (428ms)
[2016-10-17 17:06:50] <END> Building Dependency Graph (1825ms)

7. 开发调试

构建 Android 项目,打开应用,切换到 HelloReactActivity 页面,通过摇一摇开启调试菜单,选择 Dev Settings 进入 DevSettingsActivity

Dev Settings

设置 Debug server host & port for device 为本机 IP 地址,添加端口号

Debug server host & port for device

返回到 HelloReactActivity 页面,摇一摇选择 Reload,接下来就可以开始调试了。

每次修改 js 代码只需 Reload 即可,无需重新构建整个 Android 项目,修改 Java 代码需要重新构建。

8. 发布正式包

8.1 js bundle

React Native 的开发版需要有启动一个本地服务随时发送更新后的 js bundle 文件。如果要打正式包,需要把 js bundle 文件保存到 Android 项目的 assets/ 目录下。这样,正式包就不需要本地服务支持了。可参考官方文档

app/src/main/ 下创建 assets/ 文件夹,执行以下命令将 js bundle 保存到资源目录下

1
$ react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --assets-dest app/src/main/res/

app/src/main/assets/ 下就会生成 index.android.bundle 文件。

开发模式调试的时候 js 代码会立即生效,无需执行以上命令,但每次正式打包的时候如果改了 js 代码都必须先执行以上命令。

8.2 Proguard

集成 React Native 之后如果不加相关的混淆规则,打 release 包的时候就会报错。

参考 官方例子的混淆文件

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
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
@com.facebook.common.internal.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class com.facebook.react.** {*; }
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**

完成以上两步后就可以通过 ./gradlew assembleRelease 打正式包了。

本文是 慌不要慌 原创,发表于 https://danke77.github.io/,请阅读原文支持原创 https://danke77.github.io/2016/10/17/react-native-embedding-android/,版权归作者所有,转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!