React Native集成安卓原生项目(踩坑)
根据官方中文站,将react native项目集成到原生项目中
# 核心概念
把 React Native 组件集成到 Android 应用中有如下几个主要步骤:
1.配置好 React Native 依赖和项目结构。
2.创建 js 文件,编写 React Native 组件的 js 代码。
3.在应用中添加一个RCTRootView。这个RCTRootView正是用来承载你的 React Native 组件的容器。
4.启动 React Native 的 Packager 服务,运行应用。
5.验证这部分组件是否正常工作。
# 开发环境准备
首先按照开发环境搭建教程来安装 React Native 在 Android 平台上所需的一切依赖软件。
# 1. 配置项目目录结构
首先创建一个空目录用于存放 React Native 项目,然后在其中创建一个/android子目录,把你现有的 Android 项目拷贝到/android子目录中。
# 2. 安装 JavaScript 依赖包
在项目根目录下创建一个名为package.json的空文本文件,然后填入以下内容:
{
"name": "MyReactNativeApp",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "yarn react-native start"
}
}
2
3
4
5
6
7
8
接下来我们使用 yarn 或 npm(两者都是 node 的包管理器)来安装 React 和 React Native 模块。请打开一个终端/命令提示行,进入到项目目录中(即包含有 package.json 文件的目录),然后运行下列命令来安装:
$ yarn add react-native
这样默认会安装最新版本的 React Native,同时会打印出类似下面的警告信息(你可能需要滚动屏幕才能注意到)
warning "react-native@0.52.2" has unmet peer dependency "react@16.2.0".
这是正常现象,意味着我们还需要安装指定版本的 React:
$ yarn add react@16.2.0
注意必须严格匹配警告信息中所列出的版本,高了或者低了都不可以。
#
# 把 React Native 添加到你的应用中
# 配置 maven
在你的 app 中 build.gradle 文件中添加 React Native 依赖:
dependencies {
implementation 'com.android.support:appcompat-v7:27.1.1'
...
implementation "com.facebook.react:react-native:+" // From node_modules
}
2
3
4
5
如果想要指定特定的 React Native 版本,可以用具体的版本号替换 +,当然前提是你从 npm 里下载的是这个版本。
在项目的 build.gradle 文件中为 React Native 添加一个 maven 依赖的入口,必须写在 "allprojects" 代码块中:
allprojects {
repositories {
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
...
}
...
}
2
3
4
5
6
7
8
9
10
这里注意node_modules的路径
# 配置权限
接着,在 AndroidManifest.xml 清单文件中声明网络权限:
<uses-permission android:name="android.permission.INTERNET" />
如果需要访问 DevSettingsActivity 界面(即开发者菜单),则还需要在 AndroidManifest.xml 中声明:
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
开发者菜单一般仅用于在开发时从 Packager 服务器刷新 JavaScript 代码,所以在正式发布时你可以去掉这一权限。
# Network Security Config (API level 28+)
# 1. Create the following resource files
app/src/debug/res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- allow cleartext traffic for React Native packager ips in debug -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">10.0.2.2</domain>
<domain includeSubdomains="false">10.0.3.2</domain>
</domain-config>
</network-security-config>
2
3
4
5
6
7
8
9
app/src/release/res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- deny cleartext traffic for React Native packager ips in release -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">10.0.2.2</domain>
<domain includeSubdomains="false">10.0.3.2</domain>
</domain-config>
</network-security-config>
2
3
4
5
6
7
8
9
# 2. Apply the config to your AndroidManifest.xml
<!-- ... -->
<application
android:networkSecurityConfig="@xml/network_security_config">
<!-- ... -->
</application>
<!-- ... -->
2
3
4
5
6
# 代码集成
# 1. 创建一个index.js文件
首先在项目根目录中创建一个空的index.js文件。(注意在 0.49 版本之前是 index.android.js 文件)
index.js是 React Native 应用在 Android 上的入口文件。而且它是不可或缺的!它可以是个很简单的文件,简单到可以只包含一行require/import导入语句。本教程中为了简单示范,把全部的代码都写到了index.js里(当然实际开发中我们并不推荐这样做)。
# 2. 添加你自己的 React Native 代码
在index.js中添加你自己的组件。这里我们只是简单的添加一个
import React from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class HelloWorld extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.hello}>Hello, World</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});
AppRegistry.registerComponent(
'MyReactNativeApp',
() => HelloWorld
);
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
# 3. 配置权限以便开发中的红屏错误能正确显示
如果你的应用会运行在 Android 6.0(API level 23)或更高版本,请确保你在开发版本中有打开悬浮窗(overlay)权限。你可以在代码中使用Settings.canDrawOverlays(this);来检查。之所以需要这一权限,是因为我们会把开发中的报错显示在悬浮窗中(仅在开发阶段需要)。在 Android 6.0(API level 23)中用户需要手动同意授权。具体请求授权的做法是在onCreate()中添加如下代码。其中OVERLAY_PERMISSION_REQ_CODE是用于回传授权结果的字段。
private final int OVERLAY_PERMISSION_REQ_CODE = 1; // 任写一个值
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
}
2
3
4
5
6
7
8
9
10
11
Finally, the onActivityResult() method (as shown in the code below) has to be overridden to handle the permission Accepted or Denied cases for consistent UX. Also, for integrating Native Modules which use startActivityForResult, we need to pass the result to the onActivityResult method of our ReactInstanceManager instance.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted
}
}
}
mReactInstanceManager.onActivityResult( this, requestCode, resultCode, data );
}
2
3
4
5
6
7
8
9
10
11
# 掌握核心科技:ReactRootView
我们还需要添加一些原生代码来启动 React Native 的运行时环境并让它开始渲染。首先需要在一个Activity中创建一个ReactRootView对象,然后在这个对象之中启动 React Native 应用,并将它设为界面的主视图。
如果你想在安卓 5.0 以下的系统上运行,请用 com.android.support:appcompat 包中的 AppCompatActivity 代替 Activity 。
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的MyReactNativeApp必须对应“index.js”中的
// “AppRegistry.registerComponent()”的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
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
如果你使用的是 Android Studio , 可以使用Alt + Enter快捷键来自动为 MyReactActivity 类补上缺失的 import 语句。注意BuildConfig应该是在你自己的包中自动生成,无需额外引入。千万不要从com.facebook...的包中引入!
我们需要把 MyReactActivity 的主题设定为 Theme.AppCompat.Light.NoActionBar ,因为里面有许多组件都使用了这一主题。
<activity
android:name=".MyReactActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>
2
3
4
5
下一步我们需要把一些 activity 的生命周期回调传递给ReactInstanceManager:
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
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
我们还需要把后退按钮事件传递给 React Native:
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
2
3
4
5
6
7
8
This allows JavaScript to control what happens when the user presses the hardware back button (e.g. to implement navigation). When JavaScript doesn't handle the back button press, your invokeDefaultOnBackPressed method will be called. By default this simply finishes your Activity.
Finally, we need to hook up the dev menu. By default, this is activated by (rage) shaking the device, but this is not very useful in emulators. So we make it show when you press the hardware menu button (use Ctrl + M if you're using Android Studio emulator):
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
2
3
4
5
6
7
8
# 测试集成结果
# 1. 运行 Packager
运行应用首先需要启动开发服务器(Packager)。你只需在项目根目录中执行以下命令即可:
$ yarn start
# 2. 运行你的应用
保持 packager 的窗口运行不要关闭,然后像往常一样编译运行你的 Android 应用(在命令行中执行./gradlew installDebug或是在 Android Studio 中编译运行)。
编译执行一切顺利进行之后,在进入到 MyReactActivity 时应该就能立刻从 packager 中读取 JavaScript 代码并执行和显示:
# 在 Android Studio 中打包
你也可以使用 Android Studio 来打 release 包!其步骤基本和原生应用一样,只是在每次编译打包之前需要先执行 js 文件的打包(即生成离线的 jsbundle 文件)。具体的 js 打包命令如下:
$ npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/
注意把上述命令中的路径替换为你实际项目的路径。如果 assets 目录不存在,则需要提前自己创建一个。 然后在 Android Studio 中正常生成 release 版本即可!
# 出问题的地方
# java.lang.UnsatisfiedLinkError: couldn’t find DSO to load: libhermes.so
集成RN到已有项目,运行时出现。
E/AndroidRuntime: FATAL EXCEPTION: create_react_context
Process: com.ado.example, PID: 25851
java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libhermes.so
at com.facebook.soloader.SoLoader.doLoadLibraryBySoName(SoLoader.java:738)
at com.facebook.soloader.SoLoader.loadLibraryBySoName(SoLoader.java:591)
at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:529)
at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:484)
at com.facebook.hermes.reactexecutor.HermesExecutor.(HermesExecutor.java:20)
at com.facebook.hermes.reactexecutor.HermesExecutorFactory.create(HermesExecutorFactory.java:27)
at com.facebook.react.ReactInstanceManager$5.run(ReactInstanceManager.java:949)
at java.lang.Thread.run(Thread.java:818)
2
3
4
5
6
7
8
9
10
11
原因 这是因为在React Native 0.60之后,新增了Facebook自研的js引擎Hermes。没有在项目的build.gradle中配置Hermes的仓库,所以找不到so文件。使用RN创建新项目时,RN会自动为我们生成标准的build.gradle。 解决方式 修改android根目录的build.gradle
...
allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
google()
}
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
修改app目录的build.gradle
//最外层加上
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
]
def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);
android{
.....
}
dependencies{
.....
//在这里添加依赖配置
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
点击sync now,再次运行项目,就不会报错了。