一、静态注册
1)生成头文件 javah
javah -classpath src/ -d jni/ -jni 包名.类名
package com.test.bbb;
public class JniTest{
static {
System.loadLibrary("JNI_ANDROID_TEST");
}
public static native String getObjectName(Object o);
public static native String getTitleName();
public static native int getAge(Object o);
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_test_bbb_JniTest */
#ifndef _Included_com_test_bbb_JniTest
#define _Included_com_test_bbb_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_test_bbb_JniTest
* Method: getObjectName
* Signature: (Ljava/lang/Object;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_test_bbb_JniTest_getObjectName
(JNIEnv *, jclass, jobject);
/*
* Class: com_test_bbb_JniTest
* Method: getTitleName
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_test_bbb_JniTest_getTitleName
(JNIEnv *, jclass);
/*
* Class: com_test_bbb_JniTest
* Method: getAge
* Signature: (Ljava/lang/Object;)I
*/
JNIEXPORT jint JNICALL Java_com_test_bbb_JniTest_getAge
(JNIEnv *, jclass, jobject);
#ifdef __cplusplus
}
#endif
#endif
2)编写JNI层
上一步在jni目录里生成com_test_bbb_JniTest.h头文件,新建C+文件,后缀cpp,include头文件,这里可以C++再调回java层代码。
#include <stdio.h>
#include <jni.h>
#include <JNIHelp.h>
#include <stdlib.h>
#include <android/log.h>
#include <string.h>
#include <com_test_bbb_JniTest.h>
#define UNUSED(x) (void)x
#ifndef LOG_TAG
#define LOG_TAG "jni_zxz"
#endif
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
JNIEXPORT jstring JNICALL Java_com_test_bbb_JniTest_getObjectName (JNIEnv *env, jclass clazz, jobject obj) {
UNUSED(clazz);
jclass native_class = env->GetObjectClass(obj);
jmethodID methodId = env->GetMethodID(native_class, "getPackageName", "()Ljava/lang/String;");
jstring packageName = static_cast<jstring>(env->CallObjectMethod(obj, methodId));
LOGD("Java_com_test_bbb_JniTest_getObjectName");
return packageName;
}
JNIEXPORT jstring JNICALL Java_com_test_bbb_JniTest_getTitleName (JNIEnv *env, jclass clazz) {
UNUSED(clazz);
jstring title = env->NewStringUTF("NULL");
jclass native_class = env->FindClass("com/test/bbb/People");
LOGD("Java_com_test_bbb_JniTest_getTitleName start");
jmethodID methodId = env->GetStaticMethodID(native_class, "getPeopleName", "()Ljava/lang/String;");
title = static_cast<jstring>(env->CallStaticObjectMethod(native_class, methodId));
LOGD("Java_com_test_bbb_JniTest_getTitleName end");
return title;
}
JNIEXPORT jint JNICALL Java_com_test_bbb_JniTest_getAge (JNIEnv *env, jclass clazz, jobject obj) {
UNUSED(clazz);
jclass native_class = env->GetObjectClass(obj);
jfieldID fieldId = env->GetStaticFieldID(native_class, "age", "I");
jint age = env->GetStaticIntField(clazz, fieldId);
LOGD("Java_com_test_bbb_JniTest_getAge %d", age);
return age;
}
package com.test.bbb;
public class People {
public People() {}
private static String name = "haha";
public static int age = 55;
public static String getPeopleName() {
return name;
}
}
3)编译jni
make文件编译模块JNI_ANDROID_TEST
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNI_ANDROID_TEST
LOCAL_LDLIBS := -lm -llog
LOCAL_SRC_FILES := com_test_bbb_JniTest.cpp
include $(BUILD_SHARED_LIBRARY)
# This finds and builds the test apk as well, so a single make does both.
#include $(call all-makefiles-under,$(LOCAL_PATH))
遇到的问题有:
1)undefined reference to `__android_log_print' 的解决办法
a.在mk文件里加入
LOCAL_LDLIBS := -lm -llog
b.文件头部引入
#include <android/log.h>
c.宏定义
#define LOG_TAG "Native"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
2)找不到so文件,AndroidRuntime: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/system/priv-app/BBB/BBB.apk"],nativeLibraryDirectories=[/system/priv-app/BBB/lib/arm64, /system/lib64, /vendor/lib64, /system/lib64, /vendor/lib64]]] couldnt find "libJNI_ANDROID_TEST.so"
adb push <out>/system/lib64/JNI_ANDROID_TEST.so system/priv-app/BBB/lib/arm64
<out>/system/lib64/JNI_ANDROID_TEST.so system/lib64/
mv JNI_ANDROID_TEST.so libJNI_ANDROID_TEST.so
3) C++调用java层方法,GetStaticMethodID 静态方法找不到,但是其他方法和静态字段能够找到,然后确定是在编译的时候优化掉了。只需要在java代码中调用一下静态方法就可以找到。
二、动态注册JNINativeMethod的结构来保存(Java native函数与JNI函数的)一一对应关联关系。
当Java层通过System.loadLibrary加载完JNI动态库后,就会查找该库中一个叫JNI_OnLoad的函数。
如果有就调用它,而动态注册的工作就是在这里完成的。
在system.load时就会去掉JNI_OnLoad,有就注册,没就不注册。
需要重写下如下方法JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
if (register_android_media_MediaScanner(env) < 0) {
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
}
1)导入包以及定义log
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <Android/log.h>
#define TAG "result" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
2)添加JNINativeMethod方法
static const JNINativeMethod gMethods[] = {
{"getObjectName", "(Ljava/lang/Object;)Ljava/lang/String;", (jstring*)com_test_bbb_JniTest_getObjectName},
{"getTitleName","()Ljava/lang/String;",(jstring*)com_test_bbb_JniTest_getTitleName}
};
3)Jni_OnLoad中注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
JNI层函数的参数,第一个参数是JNIEnv,第二个参数jobject代表Java层的调用对象。
JNIEnv 是一个与线程相关的代表JNI环境的结构体。提供了一些JNI系统函数,通过这些函数可以做到:调用Java函数和操作jobject对象等很多事情。
JavaVM的AttachCurrentThread函数,获取线程的JNIEnv结构体,用来回调Java函数。
后台线程退出前,需要调用JavaVM的DetachCurrentThread函数释放对应的资源。
1.通过JNIEnv操作jobject
JNI基本类型数组
1.获取JNI基本类型数组元素
Get<Type>ArrayElements函数用来获取基本类型JNI数组的元素,这里面的<Type>需要被替换成实际的类型,比如GetIntArrayElements,GetLongArrayElements等
eg: jint *intArray=env->GetIntArrayElements(array,NULL);;//JNIEnv* env, jobject thiz,jintArray array
2.获取JNI基本类型数组的子数组
Get<Type>ArrayRegion函数用来获取JNI数组的子数组,这里面的<Type>需要被替换成实际的类型,比如GetIntArrayRegion,GetLongArrayRegion等
eg: env->GetIntArrayRegion(array,0,3,subArray);//JNIEnv* env, jobject thiz,jintArray array
3.设置JNI基本类型数组的子数组
Set<Type>ArrayRegion函数用来获取JNI基本类型数组的子数组,这里面的<Type>需要被替换成实际的类型,比如SetIntArrayRegion,SetLongArrayRegion等
eg: SetIntArrayRegion(array,0,3,subArray);//JNIEnv* env, jobject thiz,jintArray array
4.JNI对象数组
GetObjectArrayElement函数用来获取JNI对象数组元素
SetObjectArrayElement函数用来设置JNI对象数组元素
eg: //JNIEnv* env, jobject thiz,jobjectArray array
int len=env->GetArrayLength(array);
for(int i=0;i<len;i++)
{
jobject item=env->GetObjectArrayElement(array,i);
}
5.获取JNI数组的长度
GetArrayLength用来获取数组的长度
eg: int len=env->GetArrayLength(array);//JNIEnv* env, jobject thiz,jobjectArray array
6.JNI NIO缓冲区相关的函数
使用NIO缓冲区可以在Java和JNI代码中共享大数据,性能比传递数组要快很多,当Java和JNI需要传递大数据时,推荐使用NIO缓冲区的方式来传递。
NewDirectByteBuffer函数用来创建NIO缓冲区
GetDirectBufferAddress函数用来获取NIO缓冲区的内容
GetDirectBufferCapacity函数用来获取NIO缓冲区的大小
eg:
const char *data="hello world";
int len=strlen(data);
jobject obj=env->NewDirectByteBuffer((void*)data,len);
long capicity=env->GetDirectBufferCapacity(obj);
char *data2=(char*)env->GetDirectBufferAddress(obj);
三、JNI访问Java类的方法和字段
1.Java类型签名映射表
JNI获取Java类的方法ID和字段ID,都需要一个很重要的参数,就是Java类的方法和字段的签名,这个签名需要通过下面的表来获取,这个表很重要,建议大家一定要记住。
Java类型签名
Boolean Z
Byte B
Char C
Short S
Integer I
Long J
Float F
Double D
Void V
任何Java类的全名 L任何Java类的全名;比如Java String类对应的签名是Ljava/lang/String;
type[] [这个就是Java数组的签名,比如Java int[]的签名是[I,Java long[]的签名就是[J,Java String[]的签名是 [Ljava/lang/String;
方法类型
(参数类型)返回值 类型,
比如Java方法void hello(String msg,String msg2)对应的签名就是(Ljava/lang/String; Ljava/lang/String;)V
再比如Java方法String getNewName(String name)对应的签名是(Ljava/lang/String;) Ljava/lang/String;
再比如Java方法long add(int a,int b)对应的签名是(II)J
四、JNI访问Java
jfieldId和jmethodId分别表示Java类的成员变量和成员函数。
通过GetFieldID和GetMethodID得到。
IsJavaInstanceOf(env, object, "java/lang/Float")
1.JNI访问Java类的实例方法
GetObjectClass函数用来获取Java对象对应的类类型
GetMethodID函数用来获取Java类实例方法的方法ID
Call<Type>Method函数用来调用Java类实例特定返回值的方法,比如CallVoidMethod,调用java没有返回值的方法,CallLongMethod用来调用Java返回值为Long的方法,等等。
Value result = MakeNullValue();
jmethodID method = env->GetMethodID(env->GetObjectClass(object), "booleanValue", "()Z");
result = MakeIntValue(env->CallBooleanMethod(object, method) == JNI_TRUE ? 1 : 0);
jmethodID method = env->GetMethodID(env->GetObjectClass(object), "floatValue", "()F");
result = MakeFloatValue(env->CallFloatMethod(object, method));
jclass clazz = env->FindClass("java/lang/Integer");
jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(I)V");
result = env->NewObject(clazz, constructorID, GetIntValue(value));
FindClass是通过传java中完整的类名来查找java的class,
而GetObjectClass是通过传入jni中的一个java的引用来获取该引用的类型。
2.JNI访问Java类的静态方法
GetObjectClass函数用来获取Java对象对应的类类型
GetStaticMethodID函数用来获取Java类静态方法的方法ID
CallStatic<Type>Method函数用来调用Java类特定返回值的静态方法,比如CallStaticVoidMethod,调用java没有返回值的静态方法,CallStaticLongMethod用来调用Java返回值为Long的静态方法,等等。
eg:
jmethodID methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
3.JNI访问Java类字段相关的函数
//获取fieldID后,可调用Get<Type>Field系列函数获取jobject对应的成员变量的值。
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID)
//或者调用Set<Type>Field系列函数来设置jobject对应的成员变量的值。
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value)
GetFieldID函数用来获取Java字段的字段ID
Get<Type>Field用来获取Java类字段的值,比如用GetIntField函数获取Java int型字段的值,用GetLongField函数获取Java long字段的值,用GetObjectField函数获取Java引用类型字段的值
eg:
jclass clazz=env->GetObjectClass(person);
jfieldID name_fieldID=env->GetFieldID(clazz,"name","Ljava/lang/String;");
jstring name=(jstring) env->GetObjectField(person, name_fieldID);
4.JNI访问Java类静态字段
GetStaticFieldID函数用来获取Java静态字段的字段ID
GetStatic<Type>Field用来获取Java类静态字段的值,比如用GetStaticIntField函数获取Java 静态int型字段的值,用GetStaticLongField函数获取Java 静态long字段的值,用GetStaticObjectField函数获取Java静态引用类型字段的值
eg:
jclass clazz=env->GetObjectClass(person);
jfieldID name_fieldID=env->GetStaticFieldID(clazz,"name_static","Ljava/lang/String;");
jstring name=(jstring) env->GetStaticObjectField(clazz,name_fieldID);
五、JNI线程同步相关的函数-JNI可以使用Java对象进行线程同步
MonitorEnter函数用来锁定Java对象
MonitorExit函数用来释放Java对象锁
eg:
env->MonitorEnter(obj);
//do something
env->MonitorExit(obj);
六、JNI处理Java异常
1.当JNI函数调用的Java方法出现异常的时候,并不会影响JNI方法的执行,但是我们并不推荐JNI函数忽略Java方法出现的异常继续执行,这样可能会带来更多的问题。我们推荐的方法是,当JNI函数调用的Java方法出现异常的时候,JNI函数应该合理的停止执行代码。
ExceptionOccurred函数用来判断JNI函数调用的Java方法是否出现异常
ExceptionClear函数用来清除JNI函数调用的Java方法出现的异常
eg:
env->CallVoidMethod(thiz,helloWorld_methodID);
if(env->ExceptionOccurred()!=NULL){
env->ExceptionClear();
__android_log_print(ANDROID_LOG_VERBOSE,"hello","%s","program end with java exception");
return;
}
2.JNI抛出Java类型的异常
jclass clazz=env->FindClass("java/lang/NullPointerException");
if(clazz==NULL) return;
env->ThrowNew(clazz,"null pointer exception occurred");
七、JNI对象的全局引用和局部引用
对象的传递和操作
而GetObjectClass是通过传入jni中的一个java的引用来获取该引用的类型。
GetXXXField/GetStaticXXXField简介