React Native踩坑:集成到现有Android原生应用、RN与Android相互调用
React Native踩坑:集成到现有Android原生应用、RN与Android相互调用
- 前言
- 效果
- 搭建开发环境
- 集成到现有Android原生应用
- 创建项目结构
- 安装 React 和 React Native 模块
- 把 React Native 添加到 Android 应用中
- 添加依赖
- 配置权限
- Network Security Config (API level 28+)
- 编写 React Native 组件
- Activity 中插入 React Native 组件
- 测试集成结果
- Android 调用 RN
- RN 调用 Android
- 创建Module
- 创建Package
- 在 Application 中提供
- 在 RN 组件中使用
- 在 ReactInstanceManager 中增加 Package
- 完事
前言
本来打算从开发环境搭建开始写的,但是网上已经有挺多这类文章了,同时RN与Android相互调用这类文章也比较多,但是我翻来翻去都是那几个例子,而且没法解决我的问题 RN调用Android方法出错,找不到我的原生模块 ,所以从这个集成到现有Android原生应用开始写一下,免得之后自己也忘了。
效果
搭建开发环境
这部分我的参考资料是官方中文网站和常见问题
- React Native 中文网 搭建开发环境
- React Native的常见问题
主要出现问题会在新建项目那里
npm config set registry https://registry.npm.taobao.org --globalnpm 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应用,这部分不详细描述了,相信能找到这篇文章的都是Android开发者。
参考资料是官方说明:React Native 中文网 集成到现有原生应用
创建项目结构
首先我们需要创建一个目录,名字是你的项目名称,如ReactNativeTest
然后将你的Android项目整个复制到这个文件夹里面,然后修改Android项目文件夹名称为android
,这样你就得到了以下这种文件目录
- ReactNativeTest
-
- android
-
-
- app
-
-
-
- build
-
-
-
- gradle
-
-
-
- … …
-
接着,继续在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 服务的命令。
安装 React 和 React Native 模块
接下来我们使用 yarn 或 npm(两者都是 node 的包管理器)来安装 React 和 React Native 模块。请打开一个终端/命令提示行,进入到项目目录中(即包含有 package.json 文件的目录),然后运行下列命令来安装:
yarn add react-native
这样默认会安装最新版本的 React Native,同时会打印出类似下面的警告信息(你可能需要滚动屏幕才能注意到):
warning "react-native@0.62.2" has unmet peer dependency "react@16.11.0".
这是正常现象,意味着我们还需要安装指定版本的 React:
yarn add react@16.11.0
注意必须严格匹配警告信息中所列出的版本,高了或者低了都不可以。
把 React Native 添加到 Android 应用中
添加依赖
打开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" />
Network Security Config (API level 28+)
在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>
编写 React Native 组件
在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);
Activity 中插入 React Native 组件
回到 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 attachedc853ef19device
继续输入
adb reverse tcp:8081 tcp:8081
回复
8081
最后运行Android项目,顺利的话应该能看到中间的Hello, World
Android 调用 RN
现在我们的目标是点击按钮+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显示。
RN 调用 Android
这个就比较复杂了,可以参考官方的这个介绍:React Native 中文网 原生模块
不过官方资料也是有坑的,我下面会有提到
创建Module
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) }}
创建Package
创建文件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 中提供
打开你的 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) }}
在 RN 组件中使用
修改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的时候都是报错的话,那你就要往下再看了,这摸索了我一天,头发都掉了不少
在 ReactInstanceManager 中增加 Package
修改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() }}
完事
更多相关文章
- Android用户界面 UI组件--TextView及其子类(一) TextView
- Android基本组件TextView和EditView
- Android彻底组件化—如何使用Arouter
- Android彻底组件化—UI跳转升级改造
- HNU_团队项目_Android和数据库对接出现问题总结
- Android Studio共用Eclipse的Android项目文件
- Android 应用框架 —— 组件
- TS3.0 引入 opengl es 1.x, opengl es 2.0模块..支持android
- 将一个Android项目作为另一个Android Library给其他项目使用