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踩坑:集成到现有Android原生应用、RN与Android相互调用_第1张图片

搭建开发环境

这部分我的参考资料是官方中文网站和常见问题

  • React Native 中文网 搭建开发环境
  • React Native的常见问题

主要出现问题会在新建项目那里
React Native踩坑:集成到现有Android原生应用、RN与Android相互调用_第2张图片

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
React Native踩坑:集成到现有Android原生应用、RN与Android相互调用_第3张图片
然后将你的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
React Native踩坑:集成到现有Android原生应用、RN与Android相互调用_第4张图片
输入以下内容

<?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()    }}

完事

更多相关文章

  1. Android用户界面 UI组件--TextView及其子类(一) TextView
  2. Android基本组件TextView和EditView
  3. Android彻底组件化—如何使用Arouter
  4. Android彻底组件化—UI跳转升级改造
  5. HNU_团队项目_Android和数据库对接出现问题总结
  6. Android Studio共用Eclipse的Android项目文件
  7. Android 应用框架 —— 组件
  8. TS3.0 引入 opengl es 1.x, opengl es 2.0模块..支持android
  9. 将一个Android项目作为另一个Android Library给其他项目使用

随机推荐

  1. Android(安卓)kernel和标准Linux Kernel
  2. Android(安卓)4.4+ 实现半透明状态栏(Tran
  3. sendBroadcast和sendStickyBroadcast的区
  4. Android实现网络多线程断点续传下载
  5. Get the meta-data value in Android(安
  6. Android(安卓)SQLite使用
  7. Android(安卓)数据传递-通过静态变量传递
  8. Android(安卓)进阶 APP优化 布局优化
  9. Android(安卓)ApiDemos示例解析(52):Grap
  10. Android(安卓)log analysis