ReactNative调用jni的so技术分享
一.前言
项目内容需要与同事进行协同开发,主要为安卓方面,考虑到后续有混合开发的需求,本人在RN和Flutter进行相关测试。简单说下两种框架在此次项目上的区别。
1.Flutter,项目上如果使用 技术方向是Dart驱动java代码再使用jni的so库.(参考RN调用的思路)
优势:期间测试发现调用普通so库方面和C/C++源码非常方便(ps:本身自带的diff的方法非常的好用。),
总而言之很强大,功能使用上复杂度比RN要方便很多,文件架构清晰,后起之秀!
Android 上使用 dart:ffi 调用本地代码 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter
缺点:需要学会dart和了解flutter的框架,学习周期成本比较高 (无奈放弃=w=)
2.ReactNavtive,项目上如果使用 技术方向是Js使用NativeModule驱动java代码再使用jni的so库.
Android 原生模块 · React Native 中文网
优势:熟悉React和JS的使用降低学习成本....
缺点:在c/c++调取方面 官网上说明使用新的框架,相关配置比flutter复杂很多...
简单总结下:flutter不愧是后起之秀,感觉很多方面使用下来都比前辈RN上方便许多。希望RN之后使用新的框架能够改善此类问题。
二.项目上使用总结
项目总体思路:Js 使用 nativeModule 在调用java代码 使用jni的so库
1.安装RN的环境
参考官方文档,注意一点新的0.72版本,java8用不了,最低11
React Native 需要 Java Development Kit [JDK] 11。你可以在命令行中输入 javac -version
(请注意是 javac,不是 java)来查看你当前安装的 JDK 版本。如果版本不合要求,则可以去Temurin或Oracle JDK上下载(后者下载需注册登录)。
低于 0.67 版本的 React Native 需要 JDK 1.8 版本(官方也称 8 版本)。
2.配置nativeModule
首先贴一个官方模板,我是参考这个的原生模块配置 · React Native 中文网
java
npx @react-native-community/bob create react-native-awesome-module
Android 原生模块 · React Native 中文网
按照官网所要求的指定目录:android/app/src/main/java/com/model/ModuleFuncManager.java
此java 意义则是模块功能管理
(1)其中getName()这个方法等于是 告知RN你这里的模块名字是啥 我这里是MyModule,记住方法复写 @Override
(2)@ReactMethod则是RN可调用的方法,isBlockingSynchronousMethod = true代表同步调用,下面就可以写被调用的逻辑
这里我使用的是调取JNI的so库 并调用它的stringFromJNI()方法,当然输入值也是收到啥输出啥
注意!!
import com.example.mylib.sdklib; 是调取jni库的函数,这里要使用sdklib类所以这样引入!
java
package com.testdemo;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.example.mylib.sdklib;
public class ModuleFuncManager extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
public ModuleFuncManager(ReactApplicationContext context) {
super(context);
reactContext = context;
}
@Override
public String getName() {
return "MyModule";
}
//@ReactMethod
@ReactMethod(isBlockingSynchronousMethod = true)
public String myMethod(String message) {
// 在这里编写你的Java代码逻辑
// 例如,可以打印消息到日志
//System.out.println("Received message: " + message);
//Toast.makeText(getReactApplicationContext(), message).myMethod();
// promise.resolve(message);
//System.out.println("Received message: " + message);
sdklib sdk=new sdklib();
String a=sdk.stringFromJNI();
// // //return message+a;
System.out.println("Received message: " + a);
return message+a;
}
}
此java 意义注册模块的包
基本上此处参考官网的案例写的
java
package com.testdemo;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
public class ModulePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ModuleFuncManager(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
此java主要由模板脚手架生成,主要是把 注册-java包添加到reactapp里
packages.add(new ModulePackage()); 这是修改的内容
后面都是模板所写,项目上用不到
java
package com.testdemo;
import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new ModulePackage());
//new ModeulePackage();
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
}
3.使用jni的库注意事项
这里有大坑!!!!请仔细阅读可解决读不到so和读取报错等问题!!!
这里使用的jni方法,注意C++那边的命名方式,c++那边命名为Java_com_example_mylib_sdklib_stringFromJNI。所以这里package包是com.example.mylib,这样jni就能读取正确!
java
package com.example.mylib;
public class sdklib {
static{
System.loadLibrary("mylib");
}
public native String stringFromJNI();
}
下面的步骤是我在androidStudio上调用jni遇到的坑,所以到RN上的安卓模块可能也会遇到!
\android\app\build.gradle
这部分我是从androidStudio直接拷贝过来的, 这里sourceSets指向也请务必写进去
css
android {
.....
sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']
}
}
}
dependencies {
.....
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
.....
}
注意\android\app\jniLibs 请创建 并放入jni生成各个框架so文件
当然最关键的部分还是build之后 so库的获取,这个jnilib创建,并指向到了指定目录
会在自动在android\app\build\intermediates\merged_jni_libs\debug\out 生成框架so文件,如果没有手动放入也是可以的!!!
可能会发生重构clean的操作,可以在build.gradle 写一个搬运代码
举个例子:
typescript
task copyTask(type: Copy) {
from '.jnilib/x86_64/mylib.so'
into '../build/app/intermediates/merged_jni_libs/debug/out/lib/x86_64'
}
可以解决找不到so的问题,关键就是这路径里没有框架so库导致!!
4.在主程部分正常使用NativeModules
来到ts这块,声明了一个同步和异步的方法,用的是同步,这里MyModule 就是 ModuleFuncManager.java 声明的名字
typescript
import { NativeModules } from 'react-native';
type ModuleType = {
myMethod(a:string): Promise<string>;
};
type ModuleType1 = {
myMethod(a:string): string;
};
const { MyModule } = NativeModules;
export default MyModule as ModuleType1;
接下来就是正常调用
javascript
type SectionProps = PropsWithChildren<{
title: string;
}>;
function Section({children, title}: SectionProps): JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
}
function App() {
const TextValue =MyModule.myMethod("你好,hello Java!");
return (
<>
.....
<Section title="Step One">
Edit <Text style={styles.highlight}>App1.tsx</Text> to change this
screen and then come back to see your edits.你好{TextValue}
</Section>
</>
命令行运行 yarn android,真机调试如下所示:
三.总结
希望让遇到同样问题的人能够少走一些弯路!就是我分享的动力=w=