本来打算从开发环境搭建开始写的,但是网上已经有挺多这类文章了,同时RN与Android相互调用这类文章也比较多,但是我翻来翻去都是那几个例子,而且没法解决我的问题 RN调用Android方法出错,找不到我的原生模块 ,所以从这个集成到现有Android原生应用开始写一下,免得之后自己也忘了。
这部分我的参考资料是官方中文网站和常见问题
npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global
还有就是要将react-native记录到环境变量里,不然以后会出现下面这种问题。
react-native:command not found
zsh: command not found: react-native
参考方法:@曹九朵_ 配置reactNative(RN)过程中 出现react-native:command not found 和 zsh: command not found: react-native
这里我们就新建一个空白的Android来模拟现有的Android应用,这部分不详细描述了,相信能找到这篇文章的都是Android开发者。
参考资料是官方说明:React Native 中文网 集成到现有原生应用
首先我们需要创建一个目录,名字是你的项目名称,如ReactNativeTest
然后将你的Android项目整个复制到这个文件夹里面,然后修改Android项目文件夹名称为android
,这样你就得到了以下这种文件目录
接着,继续在ReactNativeTest
里创建一个新文件package.json
{
"name": "ReactNativeTest",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"start": "react-native start"
}
}
示例中的version字段没有太大意义(除非你要把你的项目发布到 npm 仓库)。scripts中是用于启动 packager 服务的命令。
接下来我们使用 yarn 或 npm(两者都是 node 的包管理器)来安装 React 和 React Native 模块。请打开一个终端/命令提示行,进入到项目目录中(即包含有 package.json 文件的目录),然后运行下列命令来安装:
yarn add react-native
这样默认会安装最新版本的 React Native,同时会打印出类似下面的警告信息(你可能需要滚动屏幕才能注意到):
warning "[email protected]" has unmet peer dependency "[email protected]".
这是正常现象,意味着我们还需要安装指定版本的 React:
yarn add react@16.11.0
注意必须严格匹配警告信息中所列出的版本,高了或者低了都不可以。
打开Android项目,在app的build.gradle
中输入如下内容
apply plugin: 'com.android.application'
...
// 这个不用会报错
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 {
...
// 这个不用会报错
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
// 下面用+号代表版本,会直接引用模块内的版本
implementation "com.facebook.react:react-native:+"
// 这个不用会报错
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
在项目的build.gradle
中输入以下内容
buildscript {
...
}
allprojects {
repositories {
...
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")
}
}
}
...
接着,在 AndroidManifest.xml 清单文件中声明网络权限:
<uses-permission android:name="android.permission.INTERNET" />
在Android9为target的项目上要加Network Security Config。
在res文件夹中创建xml文件夹,再创建network_security_config.xml
输入以下内容
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:android="http://schemas.android.com/apk/res/android">
<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>
再打开AndroidManifest.xml
,在application
节点里输入
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.dlong.reactnativetest">
<uses-permission android:name="android.permission.INTERNET" />
<application
...
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="n">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
在ReactNativeTest
文件夹里创建一个新文件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('ReactNativeTest', () => HelloWorld);
回到 Android studio 打开页面布局activity_main.xml
,修改成以下内容
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="以下是接入RN页面"
android:textColor="@color/colorAccent"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="clickButton"
android:text="点击+1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<LinearLayout
android:id="@+id/ll_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
我里面用了DataBinding,不清楚的朋友可以查看我这篇文章
Android Kotlin学习 Jitpack 组件之DataBinding
接着编写MainActivity
,主要就是启动一个react-native的组件
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var mReactRootView: ReactRootView
private lateinit var mReactInstanceManager: ReactInstanceManager
private var mTouchTime = 0
companion object{
private var mInstance: MainActivity? = null
@JvmStatic
fun getInstance() = mInstance
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mInstance = this
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mReactRootView = ReactRootView(this)
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(application)
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build()
mReactRootView.startReactApplication(mReactInstanceManager, "ReactNativeTest", null);
// 使用 ViewGroup 的 addView 方法将 react-native 组件加进来显示
binding.llView.addView(mReactRootView)
}
@Synchronized
fun clickButton(view: View) {
mTouchTime ++
}
运行应用首先需要启动开发服务器(Packager)。你只需在项目根目录中执行以下命令即可
yarn start
USB连接手机,手机需要打开开发者调试,然后新打开一个终端输入
adb devices
回复一个设备列表,查看设备列表
List of devices attached
c853ef19 device
继续输入
adb reverse tcp:8081 tcp:8081
回复
8081
最后运行Android项目,顺利的话应该能看到中间的Hello, World
现在我们的目标是点击按钮+1,mTouchTime变量+1,RN组件实时显示mTouchTime变量的值,我们首先修改一下index.js
import React from 'react';
import {AppRegistry, StyleSheet, Text, View, DeviceEventEmitter, Button, NativeModules} from 'react-native';
class HelloWorld extends React.Component {
constructor(){
super();
// 预设一个变量来显示原生代码传过来的值
this.state = {
strValue: "HelloWorld"
}
}
UNSAFE_componentWillMount() {
// 注册接收器
this.updateListener = DeviceEventEmitter.addListener("update", e => {
// 改变变量
this.setState({
strValue: e.strValue
});
});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.hello}>{this.state.strValue}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ffffff',
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10,
backgroundColor: '#00ff00',
},
buttonContainer: {
margin: 20
},
});
AppRegistry.registerComponent('ReactNativeTest', () => HelloWorld);
修改MainActivity
class MainActivity : AppCompatActivity() {
...
@Synchronized
fun clickButton(view: View) {
mTouchTime ++
updateTouchTimeUI()
}
/** Android调用RN */
private fun updateTouchTimeUI() {
val map = Arguments.createMap()
map.putString("strValue", "$mTouchTime")
mReactInstanceManager.currentReactContext
?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
?.emit("update", map)
}
}
重新运行项目,点击按钮+1验证。能看到数字也+1显示。
这个就比较复杂了,可以参考官方的这个介绍:React Native 中文网 原生模块
不过官方资料也是有坑的,我下面会有提到
Module就是存放提供给RN调用的方法函数,继承了ReactContextBaseJavaModule
,我们创建一个CustomModule
,内容如下:
class CustomModule (
reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
/** 这里返回的名字将是在RN中调用的变量名 */
override fun getName(): String {
return "CustomModule"
}
/** RN调用Android */
@ReactMethod
fun setIntValue(value: Int) {
Log.e("测试", "$value")
MainActivity.getInstance()?.setTouchTime(value)
}
}
创建文件CustomPackage
class CustomPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
val list = mutableListOf<NativeModule>()
// 添加提供RN调用类
list.add(CustomModule(reactContext))
return list
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<View, ReactShadowNode<*>>> {
return emptyList()
}
}
打开你的 Application
class MainApplication : Application(), ReactApplication {
override fun getReactNativeHost(): ReactNativeHost {
return object : ReactNativeHost(this) {
override fun getPackages(): MutableList<ReactPackage> {
val list = mutableListOf<ReactPackage>()
list.add(MainReactPackage(null))
// 加入你的Package
list.add(CustomPackage())
return list
}
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getJSMainModuleName(): String {
return "index"
}
}
}
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
}
}
修改index.js
import React from 'react';
import {AppRegistry, StyleSheet, Text, View, DeviceEventEmitter, Button, NativeModules} from 'react-native';
class HelloWorld extends React.Component {
...
UNSAFE_componentWillMount() {
// 注册接收器
...
}
// 点击事件
_onPressButton() {
NativeModules.CustomModule.setIntValue(0);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.hello}>{this.state.strValue}</Text>
<View style={styles.buttonContainer}>
<Button
onPress={this._onPressButton}
title="归0"
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
...
buttonContainer: {
margin: 20
},
});
AppRegistry.registerComponent('ReactNativeTest', () => HelloWorld);
官方介绍,和网上搜索出来的介绍都是到这里了,但是如果你发现你去测试,点击归0的时候都是报错的话,那你就要往下再看了,这摸索了我一天,头发都掉了不少
修改MainActivity
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
mReactRootView = ReactRootView(this)
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(application)
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(MainReactPackage())
// 最重要的地方
.addPackage(CustomPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build()
...
}
...
fun setTouchTime(time: Int) {
mTouchTime = time
updateTouchTimeUI()
}
}