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
来实现页面。
在初步运行时可能会存在问题:
- 在真机调试时由于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/
- 部分真机由于so库对于64位的不兼容导致错误
解决:限制使用32位的so库
gradle.properties
中添加android.useDeprecatedNdk=true
- app目录下的
build.gradle
添加下属代码:
android { ... defaultConfig { ... ndk { abiFilters "armeabi-v7a", "x86" } }}
2. RN与原生组件的通信
有时候 App 需要访问平台 API,但 React Native 可能还没有相应的模块包装;或者你需要复用一些 Java 代码,而不是用 Javascript 重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。
这时就需要实现通信了。
这里以toast
为例子来实现一个。
-
创建一个
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" }}
-
注册模块
我们需要在应用的 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() }}
-
在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 }}
-
实现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);
-
Last but not least:在实现的原生页面中(MyReactActivity)也需要添加包
这一点并没有说明,我也是摸索了很久才想起来的。(当然要是有不足之处请给位指出)
.addPackage(ReactPackage())
以上便实现了原生通信。
更多相关文章
- Android中的gen文件为空或者不存在的处理方法
- 第一行代码:AlertDialog
- Android 自动更新代码
- Android APK 扩展文件
- Android 自制一个工作日历 原代码
- Android颜色值XML文件
- android 查看apk中资源文件
- Android 根文件系统启动分析
- android 网络视频代码