- Java调用Native方法
- include
- ifndef TESTCP_TEST_LOG_H
- define TESTCP_TEST_LOG_H
- define LOGI(…) android_log_print(ANDROID_LOG_INFO, “========= Info ========= “, VAARGS_)
- define LOGE(…) android_log_print(ANDROID_LOG_ERROR, “========= Error ========= “, VAARGS_)
- define LOGD(…) android_log_print(ANDROID_LOG_INFO, “========= Debug ========= “, VAARGS_)
- define LOGW(…) android_log_print(ANDROID_LOG_WARN, “========= Warn ========= “, VAARGS_)
- endif //TESTCP_TEST_LOG_H
- Native调用Java方法
- JNI数据类型
- JNI方法签名
- JNIEnv和JavaVM的区别
JNI 使用 Native 库时 load 与 loadLibrary 方法的区别 如 libA.so 和 libB.so 有依赖关系,如果选择 System.load(“/sdcard/path/libA.so”),即使 libB.so 也放在 /sdcard/path/ 路径下,load 方法还是会因为找不到依赖的 libB.so 文件而失败,因为虚拟机在载入 libA.so 的时候发现它依赖于 libB.so,那么会先去 java.library.path 下载入 libB.so,而 libB.so 并不位于 java.library.path 下,所以会报错。 解决的方案就是先 System.load(“/sdcard/path/libB.so”) 再 System.load(“/sdcard/path/libA.so”),但是这种方式不太靠谱,因为必须明确知道依赖关系; 另一种解决方案就是使用 System.loadLibrary(“A”),然后把 libA.so 和 libB.so 都放在 java.library.path 下即可,也就是我们开发调用时尽量用loadLibrary方式。
Java调用Native方法
native-lib 采用Android Studio 自动生成的cpp目录下的文件,配置都没改
静态注册规则
非静态方法,不推荐
//1.MainActivitypublic class MainActivity extends AppCompatActivity {static {System.loadLibrary("native-lib");}public native String stringFromJNI();}//2.native-lib.cppextern "C" JNIEXPORT jstring JNICALLJava_com_example_testcp_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());}
静态方法写法如下
//1.MainActivitypublic class MainActivity extends AppCompatActivity {static {System.loadLibrary("native-lib");}public static native String stringFromJNI();}//2.native-lib.cppextern "C" JNIEXPORT jstring JNICALLJava_com_example_testcp_MainActivity_stringFromJNI(JNIEnv* env,jclass args) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());}
至于native-lib.cpp 对应方法怎么生成,可以利用这个指令对class文件进行操作
cmd 进入到 src/main/java 目录下 执行以下命令javah -d ../jni 包名(如com.example.test.JniTest) 回车会生成一个jni文件夹下的.h文件(com_example_test_JniTest.h)
JNIEXPORT 和 JNICALL 都是关键字,夹在 JNIEXPORT 和 JNICALL 宏中间的 jstring,表示函数的返回值类型,对应 Java 的String 类型
动态注册规则
为什么要动态注册,因为静态注册很繁琐,并且还要通过javah生成头文件或者自己写,方法名太长了。
对维护修改并不是很友好
//1.java文件public class MainActivity extends AppCompatActivity {// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("native-lib");}public native static void sayHello();}
- 引入android log,主要是用来看下运行打印的
CMakeLists 添加配置
add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cpp tuya_log.h)
define LOGI(…) android_log_print(ANDROID_LOG_INFO, “========= Info ========= “, VAARGS_)
define LOGE(…) android_log_print(ANDROID_LOG_ERROR, “========= Error ========= “, VAARGS_)
define LOGD(…) android_log_print(ANDROID_LOG_INFO, “========= Debug ========= “, VAARGS_)
define LOGW(…) android_log_print(ANDROID_LOG_WARN, “========= Warn ========= “, VAARGS_)
endif //TESTCP_TEST_LOG_H
4. Native动态注册之前Java定义的方法主要是利用JNI_OnLoad方法实现预注册,即当执行System.LoadLibrary()方法时候就把需要调用的方法给注册了```cpp//定义native对应的方法static void sayHello(JNIEnv *env, jobject, jlong handle) {LOGI( "native: say hello ###");}//java方法和native方法的映射static JNINativeMethod gJni_Methods_table[] = {{"sayHello", "(J)V", (void*)sayHello},};static const char *className = "com/example/testcp/MainActivity";static int jniRegisterNativeMethods(JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods){jclass clazz;LOGI("Registering %s natives\n", className);clazz = (env)->FindClass( className);if (clazz == NULL) {LOGE("Native registration unable to find class '%s'\n", className);return -1;}int result = 0;if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {LOGE("RegisterNatives failed for '%s'\n", className);result = -1;}(env)->DeleteLocalRef(clazz);return result;}//关键是这个方法jint JNI_OnLoad(JavaVM* vm, void* reserved){LOGI("enter jni_onload");JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {return result;}jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));return JNI_VERSION_1_4;}
Native调用Java方法
//java类MainActivity中定义这个方法
public static void getVoidFromC(int id, String name){
Log.e("JNI","test " + id +" name -> " + name);
}
//c调用java方法
static JavaVM *j_vm = NULL;
static jclass globalCallback = NULL;
static void callJavaString(){
JNIEnv *env;
j_vm->AttachCurrentThread(&env, nullptr);
jmethodID methodId = (*env).GetStaticMethodID(globalCallback,"getVoidFromC","(ILjava/lang/String;)V");
env->CallStaticVoidMethod(globalCallback,methodId,10,env->NewStringUTF("Native Test"));
}
//只是定义了globalCallback
static int jniRegisterNativeMethods(JNIEnv *env, const char *className,
const JNINativeMethod *gMethods, int numMethods) {
jclass clazz;
LOGI("Registering %s natives\n", className);
clazz = (env)->FindClass(className);
if (clazz == NULL) {
LOGE( "Native registration unable to find class '%s'\n", className);
return -1;
}
globalCallback = (jclass )env->NewGlobalRef(clazz);
......
(env)->DeleteLocalRef(clazz);
return result;
}
//代码和之前一样,只是加了j_vm赋值
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGI("enter jni_onload");
....
jniRegisterNativeMethods(env, className, gJni_Methods_table,
sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));
j_vm = vm;
return JNI_VERSION_1_4;
}
JNI数据类型
| 类型标示 | Java类型 |
|---|---|
| Z | boolean |
| B | byte |
| C | char |
| S | short |
| I | int |
| J | long |
| F | float |
| D | double |
JNI方法签名
由于Java支持方法重载,在JNI访问Java层方法时仅靠函数名是无法唯一确定一个方法,因此JNI提供了一套签名规则(如:Z、B、[Z等),用一个字符串来唯一确定一个方法,其规则:(参数1类型签名参数2类型签名…)返回值类型签名,比如Java方法long getDeviceId(int n, String s, int[] arr)、long getDeviceId(int n)的类型签名分别为(ILjava/lang/String;[I)J、(I)J。
- 基本类型:见上表
- 如果是类的类型:L+类全名,类名中的.用/代替,比如
java.lang.String就是Ljava/lang/String; 如果是数组类型:则在前面加上 然后加类型签名,几位数组就加几个,比如
int[]->[I,boolean[][]->[[Z,java.lang.Class[] -> [Ljava/lang/Class;可以通过javap -s来打印该方法的签名JNIEnv和JavaVM的区别
JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储TLS中。

总结下来即:
- JavaVM:JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个
- JNIEnv:JavaVM 在线程中的代码,每个线程都有一个,JNI可能有非常多个JNIEnv;
所以JNIEnv的作用也比较清楚了
- 调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码
- 操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象
那么JNIEnv如何创建?
//c++写法 c也类似
JNIEnv *env;
j_vm->AttachCurrentThread(&env, nullptr);
如何销毁呢?
j_vm->DetachCurrentThread();
上述的j_vm就是 JavaVM,在加载库的时候会触发onLoad方法
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
....
j_vm = vm;
}
参考资料
https://juejin.cn/post/6844904002405007373
https://juejin.cn/post/7028214429216931847
