React Native接入现有Android原生工程并实现简单的RN与Android通信

对于一个新的工程,可以直接使用React Native进行开发,但是对于现有的项目如果全面改造将会是一项巨大的工程。好在RN提供了方式为我们接入现有工程。同时,对于Android中的一些组件,RN不一定有已经继承的实现方式,我们可以通过实现ReactMethod的方式实现RN使用Android原生组件。

1. 接入React Native

1. RN开发环境准备及Android原生项目

这里不再赘述,按照搭建开发环境便可以轻松实现对于RN环境的搭建。

原生项目相信大家也一定会建立的~

2. 安装 JavaScript 依赖包

在项目根目录下创建一个名为package.json的空文本文件,并在其中填入以下内容

{
  "name": "RNHybridDemo",//可自定义
  "version": "0.0.1",//可自定义
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start" //必填
  }
}

其中scripts中是用于启动 packager 服务的命令。

接下来我们使用 yarn 或 npm(两者都是 node 的包管理器)来安装 React 和 React Native 模块(推荐使用yarn)。打开一个终端/命令提示行,进入到项目根目录(即包含有 package.json 文件的目录),然后运行下列命令来安装:

$ yarn add react-native

这样默认会安装最新版本的 React Native,同时会打印出类似下面的警告信息

warning “react-native@0.52.2” has unmet peer dependency “react@16.6.1”.

这是正常的现象,我们还需要安装指定的React

$ yarn add react@16.6.1

注意必须严格匹配警告信息中所列出的版本,高了或者低了都不可以。

最后,所有 JavaScript 依赖模块都会被安装到项目根目录下的node_modules/目录中(这个目录我们原则上不复制、不移动、不修改、不上传,随用随装)。把node_modules/目录记录到.gitignore文件中(即不上传到版本控制系统,只保留在本地)。

3. 把 React Native 添加到你的应用中

首先我们配置gradle。在你的app中build.gradle文件中添加 React Native 依赖:

dependencies {
    ...
    implementation "com.facebook.react:react-native:+"
}

如果想要指定特定的 React Native 版本,可以用具体的版本号替换 +,当然前提是你从 npm 里下载的是这个版本。

接下来是在项目的build.gradle文件中为 React Native 添加一个 maven 依赖的入口,必须写在 allprojects代码块中:

allprojects {
    repositories {
    ...
	    maven {
	            url "$rootDir/node_modules/react-native/android"
	        }
	    }
}

这里需要主要一定是上述的,在RN教程中是url "$rootDir/../node_modules/react-native/android",但是这样会导致在gradle编译时找不到我们所需要的RN包。这里的坑我呆了半天才爬出来?

4. 配置权限

接着,在 AndroidManifest.xml 清单文件中声明网络权限:

<uses-permission android:name="android.permission.INTERNET" />

如果需要访问 DevSettingsActivity 界面(即开发者菜单),则还需要在 AndroidManifest.xml 中声明:

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

开发者菜单一般仅用于在开发时从 Packager 服务器刷新 JavaScript 代码,所以在正式发布时你可以去掉这一权限。

5. 代码继承

首先是在项目根目录中创建一个index.js文件,index.js是 React Native 应用在 Android 上的入口文件。而且它是不可或缺的!它可以是个很简单的文件,简单到可以只包含一行require/import导入语句。

其实是实现RN在原生页面的展现。如果你的应用会运行在 Android 6.0(API level 23)或更高版本,请确保你在开发版本中有打开悬浮窗(overlay)权限。

这里直接放出页面的代码吧(Kotlin),记得在Manifest中注册页面:

class MyReactActivity : Activity(), DefaultHardwareBackBtnHandler {

    private lateinit var mReactRootView: ReactRootView
    private lateinit var mReactInstanceManager: ReactInstanceManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestOverlayPermission()
        initView()
    }

    private fun initView() {
        mReactRootView = ReactRootView(this)
        mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(application)
            .setBundleAssetName("index.android.bundle")//设置加载bundle文件在asserts中的路径
            .setJSMainModulePath("index")//在开发模式时入口js文件名称,仅在开发模式时起作用
            .addPackage(MainReactPackage())//自定义Package
            .setUseDeveloperSupport(BuildConfig.DEBUG)//是否是Debug模式
            .setInitialLifecycleState(LifecycleState.RESUMED)//初始化React时的生命周期
            .build()
        // 注意这里的MyReactNativeApp必须对应“index.js”中的
        // “AppRegistry.registerComponent()”的第一个参数
        mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null)

        setContentView(mReactRootView)
    }

    private fun requestOverlayPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                val intent = Intent(
                    Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:$packageName")
                )
                startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE)
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        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)
    }

    override fun invokeDefaultOnBackPressed() {
        super.onBackPressed()
    }

    override fun onPause() {
        super.onPause()

        mReactInstanceManager.onHostPause(this)
    }

    override fun onResume() {
        super.onResume()

        mReactInstanceManager.onHostResume(this, this)
    }

    override fun onDestroy() {
        super.onDestroy()

        mReactInstanceManager.onHostDestroy(this)
        mReactRootView.unmountReactApplication()
    }

    override fun onBackPressed() {
        mReactInstanceManager.onBackPressed()
    }

    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            mReactInstanceManager.showDevOptionsDialog()
            return true
        }
        return super.onKeyUp(keyCode, event)
    }

    companion object {
        var OVERLAY_PERMISSION_REQ_CODE = 1234
    }
}

这样对于RN的集成就初步完成了,你可以编写index.js来实现页面。

在初步运行时可能会存在问题:

  1. 在真机调试时由于index.js获取不到导致的错误

解决:在App>src>main中创建assets目录,在调试前运行下面代码,这样在每次编译打包之前需要先执行 js 文件的打包(即生成离线的 jsbundle 文件)。:

react-native bundle --platform android --dev false --entry-file index.js --bundle-output app/src/main/assets/index.android.bundle --assets-dest app/src/main/res/
  1. 部分真机由于so库对于64位的不兼容导致错误

解决:限制使用32位的so库

  1. gradle.properties中添加android.useDeprecatedNdk=true
  2. app目录下的build.gradle添加下属代码:
android {
    ...
    defaultConfig {
       ...
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }
}

2. RN与原生组件的通信

有时候 App 需要访问平台 API,但 React Native 可能还没有相应的模块包装;或者你需要复用一些 Java 代码,而不是用 Javascript 重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。

这时就需要实现通信了。

这里以toast为例子来实现一个。

  1. 创建一个ToastModule:

    ReactContextBaseJavaModule要求派生类实现getName方法。这个函数用于返回一个字符串名字,这个名字在 JavaScript 端标记这个模块。这里我们把这个模块叫做ToastExample,这样就可以在 JavaScript 中通过NativeModules.ToastExample访问到这个模块。

    要导出一个方法给 JavaScript 使用,Java 方法需要使用注解@ReactMethod。方法的返回类型必须为void。React Native 的跨语言访问是异步进行的,所以想要给 JavaScript 返回一个值的唯一办法是使用回调函数或者发送事件。

    class ToastModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    
        override fun getName(): String {
            return "ToastExample"
        }
    
        override fun getConstants(): Map<String, Any>? {
            val constants = mutableMapOf<String, Any>()
            constants[DURATION_SHORT_KEY] = Toast.LENGTH_SHORT
            constants[DURATION_LONG_KEY] = Toast.LENGTH_LONG
            return constants
        }
    
        @ReactMethod
        fun showToast(message: String, duration: Int) {
            Toast.makeText(reactApplicationContext, message, duration).show()
        }
    
        companion object {
            private const val DURATION_SHORT_KEY = "SHORT"
            private const val DURATION_LONG_KEY = "LONG"
        }
    }
    
  2. 注册模块

    我们需要在应用的 Package 类的createNativeModules方法中添加这个模块。如果模块没有被注册,它也无法在 JavaScript 中被访问到。

    class ReactPackage : ReactPackage {
        override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
            val modules = mutableListOf<NativeModule>()
            modules.add(ToastModule(reactContext))
            return modules
        }
    
        override fun createViewManagers(reactContext: ReactApplicationContext?): MutableList<ViewManager<View, ReactShadowNode<*>>> {
            return Collections.emptyList()
        }
    }
    
  3. 在Application中引入包

    class MyApplication : Application(), ReactApplication {
    
        override fun onCreate() {
            super.onCreate()
            SoLoader.init(this, false)
        }
    
        private val mReactNativeHost = object : ReactNativeHost(this) {
            override fun getUseDeveloperSupport(): Boolean {
                return BuildConfig.DEBUG
            }
    
            override fun getPackages(): List<ReactPackage> {
                return Arrays.asList(
                    MainReactPackage(),
                    ReactPackage()
                )
            }
        }
    
        override fun getReactNativeHost(): ReactNativeHost {
            return mReactNativeHost
        }
    
    }
    
  4. 实现index.js

    在js文件中添加NativeModules的引用。

    import React, { Component } from 'react';
    import {
      AppRegistry,
      StyleSheet,
      NativeModules,
      Button,
      View
    } from 'react-native';
    
    var RNToast = NativeModules.ToastExample;
    
    export default class HelloWorld extends Component {
    
      render() {
        return (
          <View style={styles.container}>
            <View style={styles.buttonContainer}>
              <Button
                onPress={() => {
                    RNToast.showToast("test",RNToast.LONG);
                  }}
                title="Press Me"
              />
            </View>
          </View>
        );
      }
    }
    
    const styles = StyleSheet.create({
      container: {
       flex: 1,
       justifyContent: 'center',
      },
      buttonContainer: {
        margin: 20
      },
      alternativeLayoutButtonContainer: {
        margin: 20,
        flexDirection: 'row',
        justifyContent: 'space-between'
      }
    })
    
    AppRegistry.registerComponent('MyReactNativeApp', () => HelloWorld);
    
  5. Last but not least:在实现的原生页面中(MyReactActivity)也需要添加包

    这一点并没有说明,我也是摸索了很久才想起来的。(当然要是有不足之处请给位指出)

    .addPackage(ReactPackage())
    

以上便实现了原生通信。

Hello, React Native

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值