安卓JNI提高

JAVA层的调用

so库的加载

在使用调用JNI的类中,总是可以看到如下的代码片段。

static {
        Log.d(TAG,"Loading JNI Library");
        System.loadLibrary("btntest_jni");//加载so库
    }
称之为静态代码块,在类加载的时候,静态代码块的代码被执行,优先于构造函数,具体可以参考java中的Class类中有较详细的介绍

JNI函数的声明

	private native boolean  classInitNative();
	private native void     btnTestAdd();
	private native int      btnTestAdd1(int a,int b);
	private native void     btnTestCallback();
	private native void     btnTestCreatThread();
	private native String   btnTestString();

需要跟注册的函数名和签名一致,并在函数前加上native修饰就可以供java类调用了,调用的方式和java其他的方法一致!

JNI的实现

JNI_OnLoad的实现

安卓jni必须实现JNI_OnLoad接口,在JVM加载so库时首先会调用这个函数。
/*
 * JNI Initialization
 */
jint JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *e;
    int status;

    // Check JNI version
    if (jvm->GetEnv((void **)&e, JNI_VERSION_1_6)) {
        ALOGE("JNI version mismatch error");
        return JNI_ERR;
    }

	status = android::register_com_jalon_button_MainActivity(e);

    
    return JNI_VERSION_1_6;
}

JavaVM java虚拟机对象,可以通过虚拟机对象获取当前进程的java环境!

JNIEnv类型实际上代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。例如,创建java类中对象,调用Java对象的方法,获取Java对象中的属性等等。JNIEnv的指针会被JNI传入到本地方法的实现函数中来对Java端的代码进行操作。

JNIEnv类中有很多函数可以用:
NewObject:创建Java类中的对象
NewString:创建Java类中的String对象
New<Type>Array:创建类型为Type的数组对象
Get<Type>Field:获取类型为Type的字段
Set<Type>Field:设置类型为Type的字段的值
GetStatic<Type>Field:获取类型为Type的static的字段
SetStatic<Type>Field:设置类型为Type的static的字段的值
Call<Type>Method:调用返回类型为Type的方法
CallStatic<Type>Method:调用返回值类型为Type的static方法
等许多的函数,具体的可以查看jni.h文件中的函数名称。

Type的定义也是在jni.h文件,对应关系如下表所示:
安卓JNI提高_第1张图片
对应的jType类型能被java直接理解!

int register_com_jalon_button_MainActivity(JNIEnv* env)
{
    return <span style="color:#3366ff;">jniRegisterNativeMethods</span>(env, "com/jalon/button/MainActivity",
                                    sMethods, NELEM(sMethods));//向<span style="font-family: Arial, Helvetica, sans-serif;">MainActivity类注册本地方法

}

jniRegisterNativeMethods向某个java类注册JNI实现的本地方法。

static JNINativeMethod sMethods[] = {
    /* name, signature, funcPtr */
    {"classInitNative", "()Z", (jboolean *) classInitNative},
	{"btnTestAdd","()V",(void *)btnTestAdd},
	{"btnTestAdd1","(II)I",(jint *)btnTestAdd1},
	{"btnTestCallback","()V",(void *)btnTestCallback},
	{"btnTestCreatThread","()V",(void *)btnTestCreatThread},
  	{"btnTestString","()Ljava/lang/String;",(jstring *)btnTestString}
}
 style="font-size:18px;">typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
JNINativeMethod是在jni.h中定义,jni的本地方法都需通过JNINativeMethod的方式注册给java类。name对应java层调用的函数名,signature对应函数的参数和返回值,fnPtr函数指针。
类型           相应的签名  
boolean        Z  
byte           B  
char           C  
short          S  
int            I  
long           J  
float          F  
double         D  
void           V  
object         L用/分隔包的完整类名:   Ljava/lang/String; 
Array          [签名          [I      [Ljava/lang/Object;  
Method         (参数1类型签名 参数2类型签名···)返回值类型签名
列如:
安卓JNI提高_第2张图片 
下方即为上方函数的签名。

注册接口的实现

以{"classInitNative", "()Z", (jboolean *) classInitNative},sMethods注册的第一个函数为列,说明下整个过程:
static jboolean classInitNative(JNIEnv* env, jobject obj) {
	//前两个参数好像还可以为JNIEnv* env, jclass clazz
	//eg :com_android_bluetooth_btservice_AdapterService.cpp initNative(JNIEnv* env, jobject obj)</span>
	ALOGE("in the btnjni init!\n");
/*
	The JNIEnv pointer, passed as the first argument to every native method, 
	can only be used in the thread with which it is associated. 
	It is wrong to cache the JNIEnv interface pointer obtained from one thread, 
	and use that pointer in another thread.
	The same as jclass!
*/
//save the global virtual machine and jobject to use in the another thread!
	gs_jvm = AndroidRuntime::getJavaVM();
	//获取obj中对象的class对象  
	//jclass clazz = env->GetObjectClass(obj); 
	gs_object=env->NewGlobalRef(obj);  
/*
	gs_jvm = env->GetJavaVM(&gs_jvm); 	  //another way to get the globle virtual machine	
	jclass jniCallbackClass = env->FindClass("com/jalon/callback"); //callback is the callback class
*/
	return true;
}


因为函数的签名为()Z,所以函数没有参数,返回值为jboolean。jboolean classInitNative(JNIEnv* env, jobject obj)
而后面的参数(JNIEnv* env, jobject obj)是JNI的固定实现形式,JVM在调用本地方法时,会传入当前线程的java环境和当前类的jobject对象。

同理{"btnTestAdd1","(II)I",(jint *)btnTestAdd1},函数类型应该如下:
static jint btnTestAdd1(JNIEnv* env, jobject obj,jint a,jint b){ ....
}
在java层申明应和签名一致:
private native boolean classInitNative();
private native int btnTestAdd1(int a,int b);

java参数的传递

基本类型的传递:

static jint btnTestAdd1(JNIEnv* env, jobject obj,jint a,jint b){
	
	jint result=0;
	result = (jint)ADD((jint)a,(jint)b);
	return result;
}
基本类型的传递在jni中是直接可以使用的。

数组和字串的传递:

static jboolean setReportNative(JNIEnv *env, jobject object, jbyteArray address, jbyte reportType, jstring report) {
    ALOGD("%s: reportType = %d", __FUNCTION__, reportType);
    bt_status_t status;
    jbyte *addr;
    jboolean ret = JNI_TRUE;
    if (!sBluetoothHidInterface) return JNI_FALSE;

    addr = env->GetByteArrayElements(address, NULL);
    if (!addr) {
        ALOGE("Bluetooth device address null");
        return JNI_FALSE;
    }
    jint rType = reportType;
    const char *c_report = env->GetStringUTFChars(report, NULL);

    if ( (status = sBluetoothHidInterface->set_report((bt_bdaddr_t *) addr, (bthh_report_type_t)rType, (char*) c_report)) !=
             BT_STATUS_SUCCESS) {
        ALOGE("Failed set report, status: %d", status);
        ret = JNI_FALSE;
    }
    env->ReleaseStringUTFChars(report, c_report);
    env->ReleaseByteArrayElements(address, addr, 0);

    return ret;
}
源代码请参考com_android_bluetooth_hid.cpp,上述过程称之为局部引用,引用完成之后需要释放,后面会详细介绍相关的概念

类的传递

在每一个jni接口中都有一个类的传递,只不过是当前类罢了。其他的内也类似,使用和当前的jobject obj一样。

C/C++对java的引用

对基本类型的引用

static void btnTestAdd(JNIEnv* env, jobject obj){
	
	//获取obj中对象的class对象
	jclass clazz = env->GetObjectClass(obj);  
	//获取Java中的a,b,result字段的id(最后一个参数是number的签名)  
	jfieldID id_a = env->GetFieldID(clazz,"a","I");  
	jfieldID id_b = env->GetFieldID(clazz,"b","I");  
	jfieldID id_r = env->GetFieldID(clazz,"result","I");  
	//获取a,b的值  
	jint a = env->GetIntField(obj,id_a);
	jint b = env->GetIntField(obj,id_b);
	//设置result的值
	env->SetIntField(obj,id_r,(a+b));  

}
在jni.h中有一系列的Get/Set,要使用是请仔细查阅!

对对象的引用

void btnTestThread(void *args){
	
	....

	jfieldID strId =env->GetFieldID(clazz,"str","Ljava/lang/String;");
	if(strId==NULL)
		ALOGE("get java class btntest str failed!\n");
	//获取当前类的String对象str FieldID
	....
	env->SetObjectField(gs_object,strId,env->NewStringUTF("I come from btnJNItest thread!")); 
	//设置当前类String对象str的值
	....
	
}

对java方法的回调

static void btnTestCallback(JNIEnv* env, jobject obj){

	jclass clazz = env->GetObjectClass(obj);  

	jmethodID callbackid = env->GetMethodID(clazz,"callback","()V");//获取回调方法的ID
	if(callbackid==NULL)
		ALOGE("get java callback failed!");

	env->CallVoidMethod(obj,callbackid,NULL);//调用java的回调方法

}
jni.h中申明了各种返回值的Call<Type>Method,应该根据实际情况选择调用!

jni创建java对象

// 指定编码且以零('\0')结束的 char 数组转为 Java String
jstring pchar2jstring(JNIEnv*		env,
						const char* pcInputString, 
						const char* pcEncoding)
{
    // 待返回的 Java String
    jstring jniReturnString = NULL;
    // Java 字符串的类和获取字节的方法ID
    jclass    jniStringClass = NULL;
    jmethodID jniInitMethodID = NULL;
    // 指定转换字符串时用的编码格式
    jstring jniEncoding = NULL;
    // 取得 字符串 转换成 字节数组
    jbyteArray jniByteArray = NULL;
	// 要转换的字符串或要传出字节个数的变量指针为 NULL 时
	if ( ( NULL == env )           || 
		 ( NULL == pcInputString ) )
	{
		ALOGE("pchar2jstring Parameter is invalid!" );
		goto exit;
	}
	// 指定字符串编码格式
	if ( NULL  == pcEncoding )
	{
		jniEncoding = env->NewStringUTF("utf-8" );
	}
	else
	{
		jniEncoding =env->NewStringUTF(pcEncoding );
	}
	if ( NULL == jniEncoding )
	{
		ALOGE( "jstring2pchar Call NewStringUTF to get specific encoding failed!" );
		goto exit;
	}

	<span style="color:#ff6666;">//需要创建的类</span>
	<span style="color:#ff6666;">jniStringClass = env->FindClass( "java/lang/String" );  </span>
	if ( NULL == jniStringClass )
	{
		ALOGE( "jstring2pchar Call FindClass to get java/lang/String failed!" );
		goto exit;
	}
	<span style="color:#ff6666;">//获取类的构造方法</span>
	jniInitMethodID = <span style="color:#ff0000;">env->GetMethodID</span>(jniStringClass,
										   "<init>",
										   "([BLjava/lang/String;)V" );
	if ( NULL == jniInitMethodID )
	{
		ALOGE( 
							 "jstring2pchar",
							 "Call GetMethodID to get failed!" );
		goto exit;
	}
	// 新建一个字节数组
	jniByteArray = env->NewByteArray(strlen( pcInputString ) );
	// 将基本类型数组中的某一区域从缓冲区中复制过来
	env->SetByteArrayRegion( jniByteArray,            // Java 数组
								0,                       // 起始下标
								strlen( pcInputString ), // 要复制的元素个数
								(jbyte*)pcInputString ); // 来源缓冲区

	/<span style="color:#ff6666;">/ 用新建的字节数组来构建 Java String对象</span>
	jniReturnString = (jstring)<span style="color:#ff0000;">env->NewObject</span>(jniStringClass,
												  jniInitMethodID,
												  jniByteArray,
												  jniEncoding );
exit:	
    return jniReturnString;
}

jni创建线程

static void btnTestCreatThread(JNIEnv* env, jobject obj){
	
	ALOGE("in btnJNI btnTestCreatThread\n");
	AndroidRuntime::createJavaThread("btnTestThread", btnTestThread, NULL);

}

void btnTestThread(void *args){
	
	JNIEnv *env=NULL;  
	//gs_jvm->AttachCurrentThread(&env, NULL); <span style="color:#ff0000;">//因为已经是java进程,所以不需要Attach java 虚拟机
	//但是若是pthread_create创建的普通linux进程,若需回调java类函数或者属性,必须调用AttachCurrentThread</span>
	env = AndroidRuntime::getJNIEnv();  
	if(env==NULL)
		ALOGE("virtual machine env failed!\n");

	jclass clazz = env->GetObjectClass(gs_object); 
 
	if(clazz==NULL)
		ALOGE("get GetObjectClass failed!\n");

	jfieldID strId =env->GetFieldID(clazz,"str","Ljava/lang/String;");
	if(strId==NULL)
		ALOGE("get java class btntest str failed!\n");
	
	jmethodID callbackid = env->GetMethodID(clazz,"callbackThread","()V");
	if(callbackid==NULL)
		ALOGE("get java class btntest callbackid failed!");

	env->SetObjectField(gs_object,strId,env->NewStringUTF("I come from btnJNItest thread!")); 

	while(true){

		env->CallVoidMethod(gs_object,callbackid,NULL);
		ALOGD("the btnTestThread is runnig!\n");
		sleep(5);
	}

	//全局引用不再使用时,一定要DeleteGlobalRef
	env->DeleteGlobalRef(gs_object);
	
}

当然创建线程时也可以用pthread_create来实现,唯一的区别是AndroidRuntime::createJavaThread创建线程已经是java线程,而pthread_create是linux 的普通进程,当进程中需要回调java类中的方法时,需要AttachCurrentThread到java环境,AndroidRuntime::createJavaThread则不需要。
回调java方法时需注意:
The JNIEnv pointer, passed as the first argument to every native method, can only be used in the thread with which it is associated. It is wrong to cache the JNIEnv interface pointer obtained from one thread, and use that pointer in another thread.
意思就是JNIEnv指针不能直接保存在全局变量中在多线程中共享使用。
回调时的线程和之前保存变量的线程不能共享了这个JNIEnv *env指针和jobject obj变量
JNIEnv *env  jobject obj 都是线程相关。在多进程中可以通过AndroidRuntime::getJNIEnv();  获取当前线程的java环境,而jobject obj 必须创建全局引用gs_object=env->NewGlobalRef(obj);  在多进程中共享。上面的列子已经很好的解决了这个问题。

点击下载源码

你可能感兴趣的:(安卓JNI提高)