React Native嵌入到Android原生应用
React native不仅可以在自己的js界面和现有工程的界面之间跳转,而且可以把js写的界面当成一个控件,嵌入到现有的activity,作为原生界面的一部分使用。但是看了React Native中文网的把React Native组件植入到Android,仿佛置身云雾里,硬着头皮尝试着去集成,遇到了很多坑,索性就打算写出来,为让后来的兄弟们少挖一些坑,废话少说,看:
注:最下面有我遇到的坑和解决办法,希望对你有所帮助。
今天(2018年1月16号),我用最RN新版(0.52.0),按照这篇文章,成功了。
一、把rn页面放在一个activity,在原生应用里启动该activity
1.在Android Studio(以后用AS代替)创建Android原生应用工程HelloRN。
2.在HelloRN工程的根目录中,安装react-native。可以直接在AS底部菜单栏Terminal命令行中运行
npm init
或者通过cmd命令行进入你工程的根目录,运行上面的命令。输入这个命令后,此时会提示你输入一系列参数,按照提示输入,我输入的参数如下:
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (myAppWithTest) helloas // 输入项目名称
version: (1.0.0) // 回车,使用默认版本号
description: test native for react // 输入项目描述
entry point: (index.android.js) // 我们建立的入口文件是index.android.js所以填写index.android.js
test command: // 回车,使用默认值
git repository: // 回车留空或填入Git地址
keywords: react test // 填写关键字react和test
author: andy // 填写作者
license: (ISC) // 回车,使用默认值
这里项目名helloas 要求小写。
3.创建js文件
npm install --save react
npm install --save react-native
之后会创建一个node_modules/的目录和package.json文件。
npm install --save react@16.0.0-alpha.6
npm install --save react-native@0.44.3
4.在根目录创建.flowconfig文件
mac环境:
curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig
windows环境:
没有curl命令,可以直接用浏览器打开https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig ,将显示的内容直接保存到.flowconfig文件。
(后记)注:文件最后需要改成当前你用的RN版本
[version]
^0.44.3
5.打开package.json文件,在“scripts”内添加如下语句:"start": "node node_modules/react-native/local-cli/cli.js start"。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node node_modules/react-native/local-cli/cli.js start"
}
其中的test节点是自动生成的,我们可以把它删除。
6.在根目录中创建index.android.js,加入简单的代码,你可以将如下内容粘贴到文件中
'use strict';
import React from 'react';
import { AppRegistry, StyleSheet, Text, View} from 'react-native';
class helloas extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.hello}>Hello, World</Text>
</View>
)
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
AppRegistry.registerComponent('helloas', () => helloas);
注意,最后一句AppRegistry.registerComponent,第一个参数是前面package.json里设置的name,第二个参数是自定义的component。
7.在app的build.gradle中添加React Native的编译依赖
dependencies {
...
compile "com.facebook.react:react-native:+" // From node_modules.
}
如果想要指定特定的React Native版本,可以用具体的版本号替换 +,当然前提是你从npm里下载的是这个版本(package.json里有) 。
8.在project的build.gradle中添加本地的React Native的marven目录
allprojects {
repositories {
...
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/node_modules/react-native/android"
}
}
...
}
9.在AndroidManifest中添加如下的权限
<uses-permission android:name="android.permission.INTERNET"/>
/**设置调试 的权限**/
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
10.在AndroidManifest的Application节点,添加如下Activity
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
注意,该Activity仅在开发模式从开发服务器reload js时使用,所以在release模式下可精简。
index.android.js就是hello world界面。手机在debug模式下从自己的计算机下载index页面。下载的过程会有个提示的dialog,显示它需要上面的权限。6.0及以上的系统,除了在Manifest里授权,还需要在系统设置里的“应用-》在其他应用的上层显示”里,找到我们的应用,勾选上允许。
注:不同厂家的设置不太一样
11.添加原生代码
想要通过原生代码调用 React Native ,就像这样,我们需要在一个 Activity 中创建一个 ReactRootView 对象,将它关联一个 React application 并设为界面的主视图。
如果你想在安卓5.0以下的系统上运行,请用 com.android.support:appcompat 包中的 AppCompatActivity 代替 Activity 。
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
public static int OVERLAY_PERMISSION_REQ_CODE = 1234;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
//.setJSMainModuleName("index.android")(这个已经没了)
.setJSMainModulePath("index.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的helloas必须对应“index.android.js”中的
// “AppRegistry.registerComponent()”的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "helloas", null);
setContentView(mReactRootView);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
如果你的项目名字不是叫“HelloWorld”,则需要将“index.android.js”中的“AppRegistry.registerComponent()”方法中的第一个参数替换为对应的名字。
12.在AndroidManifest.xml文件中,为刚才创建的activity指定一个主题
<activity
android:name=".MyReactActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>
13.把一些activity的生命周期回调传递给ReactInstanceManager:
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause();
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy();
}
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
14.在MyReactActivity中添加按键响应函数:
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
15.配置权限以便开发中的红屏错误能正确显示
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);
}
}
@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...
}
}
}
}
16.在HelloRN程序的界面上�添加一个按钮,加载MyReactActivity。
Button bt = (Button)findViewById(R.id.start_react);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,MyReactActivity.class);
startActivity(intent);
}
});
17.运行你的应用
到这里基本可以在android studio中运行程序了,先在命令行启动js所需的服务器,执行下面的命令即可:
npm start
//或者
react-native start
运行截图如下:
Paste_Image.png
最后,在android studio,像启动其他程序一样运行程序,点击按钮就可以加载react native界面了。激动吧。如下图:
Paste_Image.png二、将react native 写的界面当成组建嵌入到现有的activity
这个其实比较简单,新建一个布局xml文件,在上面的创建的MyReactActivity的onCreate函数中,像原生一样调用setContentView(R.layout.native_js); 然后通过addView 把reactnative 的界面,加入进入。下面是完整的onCreate函数代码:
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.native_js);
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null);
LinearLayout view = (LinearLayout) findViewById(R.id.react_root);
view.addView(mReactRootView);
}
编译运行,如下图,灰色是原声界面部分,蓝色为react native界面:
Paste_Image.png三、遇到的坑
(后记)按照版本0.44来嵌入,我只遇到了 错误1 和 错误3 ,之后就成功了。
错误提示1:
Warning:Conflict with dependency 'com.google.code.findbugs:jsr305'. Resolved versions for app (3.0.1) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.
解决办法:
在项目的app根目录中build.gradle中的
android {
...
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
}
...
}
错误提示2:
com.facebook.react.devsupport.JSException: Could not get BatchedBridge, make sure your bundle is packaged correctly
Paste_Image.png
解决办法:
方法1:修改项目中的package.json文件(亲测:不怎么好用)
看下你的文件中的是否有scripts模块,添加bundle-android,如图
“bundle-android”: “react-native bundle –platform android –dev false –entry-file index.android.js –bundle-output android/app/src/main/assets/index.android.bundle –sourcemap-output android/app/src/main/assets/index.android.map –assets-dest android/app/src/main/res/”
注意:命令中的assets路径按照自己的项目自行调整
方法2:使用命令行直接生成,不用修改package.json,不管有没有bundle-android模块
首先cd到项目的根目录中,执行命令react-native bundle –platform android –dev false –entry-file index.android.js –bundle-output src/main/assets/index.android.bundle –assets-dest src/main/res/
运行效果如下图:
注意:assets路径自己按照项目情况自行修改
这种方法可能出现会遇到一个问题,说是没有找到文件或文件集,如下图
我们根据错误的提示路径,我们进入路径里面,路径如下
android/app/src/main/assets/index.android.bundle
发现main的文件夹里面没有assets文件夹,那么我们就新建一个文件夹,并命名为assets,然后在assets的文件夹里面新建一个空白文档命名为index.android.bundle然后保存即可。
至上面的两种方法最后生成的效果是一样的,都可以看到在项目的assets目录下生成了了来了两个文件,
Paste_Image.png其中第二个文件 .meta亲测可以删除。
摇晃设备,reload js,是不是就大功告成了?结果屏幕继续飘红:
com.facebook.react.devsupport.DebugServerException: Could not connect to development server.
解决办法:点击Reload(R,R),打开调试菜单,点击Dev Settings,选Debug server host for device,输入你的正在运行packager的那台电脑的局域网IP加:8081(同时要保证手机和电脑在同一网段,且没有防火墙阻拦),再按back键返回,再按Menu键,在调试菜单中选择Reload JS,就应该可以看到运行的结果了。
这次应该出现“Hello, World”页面了吧,然而,如下图
Paste_Image.png目前这个问题我也没有解决,预计原因是RN版本的问题。此时我的版本是0.45.1。
后来把版本改为了,如下图 Paste_Image.png
就成功了,啦啦啦。。。
错误提示3: Paste_Image.png
解决办法:关闭电脑的防火墙,如关闭360安全卫士、金山毒霸等。
错误提示4: Paste_Image.png
解决办法:需设置ip和端口。
最后,
附:查询电脑ip地址命令:ipconfig/all
连接网线时,采用 Paste_Image.png
连接wifi时,采用
Paste_Image.png