安卓jni进阶

关于引用的生命周期

1.JNI 支持三种引用:局部引用、全局引用、弱全局引用(下文简称“弱引用”)。
2.局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。
3.局部引用或者全局引用会阻止 GC回收它们所引用的对象,而弱引用则不会。 
4.不是所有的引用可以被用在所有的场合。例如,一个本地方法创建一个局部引用并返回后,再对这个局部引用进行访问是非法的。

局部引用

大多数JNI函数会创建局部引用。例如,NewObject 创建一个新的对象实例并返回一个对这个对象的局部引用。 
局部引用只有在创建它的本地方法返回前有效。本地方法返回后,局部引用会被自动释放。 
你不能在本地方法中把局部引用存储在静态变量中缓存起来供下一次调用时使用。

释放一个局部引用有两种方式,一个是本地方法执行完毕后 VM自动释放,另外一个是程序员通过 DeleteLocalRef手动释放。 
既然VM会自动释放局部引用,为什么还需要手动释放呢?因为局部引用会阻止它所引用的对象被 GC回收。 
局部引用只在创建它们的线程中有效,跨线程使用是被禁止的。不要在一个线程中创建局部引用并存储到全局引用中,然后到另外一个线程去使用。

关于局部引用溢出的问题,后面我们会单独再介绍

全局引用 

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被 GC 回收。 
与局部引用可以被大多数 JNI函数创建不同,全局引用只能使用一个 JNI函数创建:NewGlobalRef

下面这个版本的 MyNewString 演示了怎么样使用一个全局引用:

 jstring MyNewString(JNIEnv *env, jchar *chars, jint len) 
 { 
     static jclass stringClass = NULL; 
     ... 
     if (stringClass == NULL) { 
        jclass localRefCls = (*env)->FindClass(env, "java/lang/String"); 
         if (localRefCls == NULL) { 
             return NULL; /* exception thrown */ 
         } 
         /* Create a global reference */ 
         stringClass = (*env)->NewGlobalRef(env, localRefCls); 
  
         /* The local reference is no longer useful */ 
         (*env)->DeleteLocalRef(env, localRefCls); 
  
         /* Is the global reference created successfully? */ 
         if (stringClass == NULL) { 
             return NULL; /* out of memory exception thrown */ 
         } 
     } 
 ... 
} 


jstring pathStr = env->NewStringUTF(path)

....

env->DeleteLocalRef(pathStr);


弱引用

弱引用使用NewGlobalWeakRef创建,使用DeleteGlobalWeakRef释放。与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的 VM内部的对象。 
当本地代码中缓存的引用不一定要阻止 GC回收它所指向的对象时,弱引用就是一个最好的选择。

引用比较

给定两个引用(不管是全局、局部还是弱引用),你可以使用 IsSameObject 来判断它们两个是否指向相同的对象。例如: 
(*env)->IsSameObject(env, obj1, obj2) 
如果obj1和obj2指向相同的对象,上面的调用返回 JNI_TRUE(或者 1),否则返回JNI_FALSE(或者 0)。

JNI中的一个引用NULL 指向JVM中的null 对象。如果obj是一个局部或者全局引用,你可以使用(*env)->IsSameObject(env, obj, NULL)或者obj == NULL来判断obj是否指向一个 null对象。

在这一点儿上,弱引用有些有同,一个NULL弱引用同样指向一个JVM中的null对象,但不同的是,在一个弱引用上面使用IsSameObject时,返回值的意义是不同的: 
(*env)->IsSameObject(env, wobj, NULL) 
上面的调用中,如果 wobj已经被回收,会返回 JNI_TRUE,如果wobj 仍然指向一个活动对象,会返回 JNI_FALSE。

JDK提供了一系列的函数来管理局部引用的生命周期。这些函数包括:
EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame。

当你的本地代码不再需要一个全局引用时,你应该调用 DeleteGlobalRef 来释放它。如果你没有调用这个函数,即使这个对象已经没用了,JVM也不会
回收这个全局引用所指向的对象。

当你的本地代码不再需要一个弱引用时,应该调用 DeleteWeakGlobalRef 来释放它,如果你没有调用这个函数,JVM仍会回收弱引用所指向的对象,但
弱引用本身在引用表中所占的内存永远也不会被回收。

jni线程回调java方法

public class Test {  
  
    static{  
        try {  
            System.loadLibrary("test");  
            class_init_native();  
              
        } catch(UnsatisfiedLinkError ule){  
            System.err.println("WARNING: Could not load library libtest.so!");  
        }  
          
    }  
      
    public int initialize() {  
        return native_radio_init();  
    }  
  //jni的回调方法
    public void Receive(char buffer[],int length){  
        String msg = new String(buffer);  
        msg = "received from jni callback" + msg;  
        Log.d("Test", msg);  
    }   
    protected  static native void class_init_native();     
    protected  native int native_init();  
  
}

//test.h
#include <pthread.h>
//function type for receiving data from native
typedef void (*ReceiveCallback)(unsigned char *buf, int len);

/** Callback for creating a thread that can call into the Java framework code.
 *  This must be used to create any threads that report events up to the framework.
 */
typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);

typedef struct{
	ReceiveCallback recv_cb;
	CreateThreadCallback create_thread_cb;
}Callback;
定义的回调函数

//test.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
#include "test.h"

void *thread_entry(void *args)
{
	char *str = "i'm happy now";
	Callback cb = NULL;
	int len;
	if(args != NULL){
		cb = (Callback *)args;	
	}
	
	len = strlen(str);
	while(1)
	{
		printf("thread running...\n");
		//invoke callback method to java
		if(cb != NULL && cb->recv_cb != NULL){
			cb->recv_cb((unsigned char*)str, len);
		}
		sleep(1);
	}
		
}

void init(Callback *cb)
{	
	pthread_t thread;
	//pthread_create(&thread,NULL,thread_entry,(void *)NULL);
	if(cb != NULL && cb->create_thread_cb != NULL)
	{
		cb->create_thread_cb("thread",thread_entry,(void *)cb);
	}
}
创建线程,调用回调

//jni_test.c
#include <stdlib.h>
#include <malloc.h>
#include <jni.h>
#include <JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include "test.h"
#define RADIO_PROVIDER_CLASS_NAME "com/jalon/Test"
using namespace android;
static jobject mCallbacksObj = NULL;
static jmethodID method_receive;

static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
    if (env->ExceptionCheck()) {
        LOGE("An exception was thrown by callback '%s'.", methodName);
        LOGE_EX(env);
        env->ExceptionClear();
    }
}

static void receive_callback(unsigned char *buf, int len)
{
	int i;
    JNIEnv* env = AndroidRuntime::getJNIEnv();
	jcharArray array = env->NewCharArray(len);
	jchar *pArray ;
	
	if(array == NULL){
		LOGE("receive_callback: NewCharArray error.");
		return; 
	}

	pArray = (jchar*)calloc(len, sizeof(jchar));
	if(pArray == NULL){
		LOGE("receive_callback: calloc error.");
		return; 
	}

	//copy buffer to jchar array
	for(i = 0; i < len; i++)
	{
		*(pArray + i) = *(buf + i);
	}
	//copy buffer to jcharArray
	env->SetCharArrayRegion(array,0,len,pArray);
	//invoke java callback method
    env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
	//release resource
	env->DeleteLocalRef(array);
	free(pArray);
	pArray = NULL;
	
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
}

static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
    return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}

static Callback mCallbacks = {
	receive_callback,
	create_thread_callback
};

static void jni_class_init_native
(JNIEnv* env, jclass clazz)
{
	method_receive = env->GetMethodID(clazz, "Receive", "([CI)V");
}

static int jni_init
(JNIEnv *env, jobject obj)
{

	
	if (!mCallbacksObj)
		mCallbacksObj = env->NewGlobalRef(obj);
	
	return init(&mCallbacks);
}

static const JNINativeMethod gMethods[] = {  
	{ "class_init_native",			"()V",			(void *)jni_class_init_native },
    { "native_init",				"()I",			(void *)jni_init },
};  

static int registerMethods(JNIEnv* env) {  
	const char* const kClassName = RADIO_PROVIDER_CLASS_NAME;
    jclass clazz;   
  	/* look up the class */  
    clazz = env->FindClass(kClassName);  
    if (clazz == NULL) {  
        LOGE("Can't find class %s/n", kClassName);  
        return -1;  
    }  
	/* register all the methods */  
    if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK)  
    {  
        LOGE("Failed registering methods for %s/n", kClassName);  
        return -1;  
    }  
	/* fill out the rest of the ID cache */  
    return 0;  
}   

jint JNI_OnLoad(JavaVM* vm, void* reserved) { 
	JNIEnv* env = NULL;  
	jint result = -1;  
	LOGI("Radio JNI_OnLoad");  
	    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
	    LOGE("ERROR: GetEnv failed/n");  
	    goto fail;  
	}  

	if(env == NULL){
		goto fail;
	}
	if (registerMethods(env) != 0) { 
	    LOGE("ERROR: PlatformLibrary native registration failed/n");  
	    goto fail;  
	}  
	/* success -- return valid version number */      
	result = JNI_VERSION_1_4;  
fail:  
	return result;  
} 
实现test.h中定义的接口,实现与java的交互。

jstring和char的相互转化

jstring 转char 

方法一:
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 =<span style="color:#ff0000;"> env->GetStringUTFChars</span>(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;
}
方法二:
// 由 Java String 转为指定编码的 char 数组
// 注:调用者再传入 puiOutputStringLen 参数指针值时,一定要是 unsigned int 类型的变量指针,
//   如果将 unsigned char 类型的变量地址强制转换后传入,会在传出值时覆盖四个字节,就会出现内存越界的问题!!!
int jstring2pchar( JNIEnv*	   env,                 // JNI 本地接口指针
               jstring             jniInputString,      // Java层传下来的字符串
               const char*         pcEncoding,          // 可以指定转换为的编码
               char* const         pcOutputString,      // 返回转换后的字符串,若为 NULL 仅是取所需字节个数
               unsigned int* const puiOutputStringLen ) // 返回转换后的字节个数(不含‘\0’)
{
    // 返回值
    int iResult = 0; // 零为成功
	
    // Java 字符串的类和获取字节的方法ID
    jclass    jniStringClass = NULL;
    jmethodID jniGetBytesMethodID = NULL;

    // 指定转换字符串时用的编码格式
    jstring jniEncoding = NULL;

    // 取得 字符串 转换成 字节数组
    jbyteArray jniByteArray = NULL;
    // 取得 字符串 转换成 字节数组 的字节个数
    jsize jniByteArraySize = 0;
    // 基本类型 - 字节数组
    jbyte* jniPrimitiveByteArray = NULL;

	// 要转换的字符串或要传出字节个数的变量指针为 NULL 时
	if ( ( NULL == env )              ||
		 ( NULL == jniInputString )   ||
		 ( NULL == pcOutputString )   ||
		 ( NULL == puiOutputStringLen ) )
	{
		ALOGE( "jstring2pchar Parameter is invalid!" );
		iResult = -1;
		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!" );
		iResult = -2;
		goto exit;
	}

	// 获取 Java String 类和回调方法 I D信息,
	// 也可以用全局变量记下来,免得浪费时间重复执行。
	jniStringClass = env->FindClass( "java/lang/String" );

	if ( NULL == jniStringClass )
	{
 		ALOGE("jstring2pchar Call FindClass to get java/lang/String failed!" );
		iResult = -3;
		goto exit;
	}

	// 得到 String 类的 getBytes 方法
	jniGetBytesMethodID = env->GetMethodID(jniStringClass,
											   "getBytes",
											   "(Ljava/lang/String;)[B" );

	if ( NULL == jniGetBytesMethodID )
	{
		ALOGE("jstring2pchar Call GetMethodID to get getBytes method failed!" );
		iResult = -4;
		goto exit;
	}

	// 从 字符串 中得到指定编码的 字节数组
	jniByteArray = (jbyteArray)env->CallObjectMethod( jniInputString,
														 jniGetBytesMethodID,
														 jniEncoding );

	if ( NULL == jniByteArray )
	{
		ALOGE("jstring2pchar Call NewStringUTF to get specific encoding failed!" );
		iResult = -5;
		goto exit;
	}

	// 取得 字符串 转换成 字节数组 的字节个数
	jniByteArraySize = env->GetArrayLength(jniByteArray );

	if ( 0 >= jniByteArraySize )
	{
		ALOGE("jstring2pchar Call GetArrayLength to get array length failed!" );
		iResult = -6;
		goto exit;
	}

	// 返回原始数组的实体,需要对应调用 ReleaseByteArrayElements 来释放返回值
	jniPrimitiveByteArray = env->GetByteArrayElements(jniByteArray, NULL ); // 不关心是否产生拷贝

	if ( NULL == jniPrimitiveByteArray )
	{
		ALOGE( "jstring2pchar Call GetByteArrayElements to get array pointer failed!" );
		iResult = -7;
		goto exit;
	}

	if ( ( NULL != pcOutputString ) &&
		 ( jniByteArraySize > *puiOutputStringLen ) )
	{
		ALOGE("jstring2pchar pcOutputString is less than need!" );
		iResult = -8;
		goto exit;
	}

	if ( NULL != pcOutputString )
	{
		memset( pcOutputString, 0, *puiOutputStringLen );
		memcpy( pcOutputString, jniPrimitiveByteArray, jniByteArraySize );
	}

	// 返回转换后的字节个数(不含‘\0’) - 四字节内容覆盖
	*puiOutputStringLen = jniByteArraySize;

    if ( NULL != jniPrimitiveByteArray )
    {
        // 与 GetByteArrayElements 函数对应,释放其返回值指向的数组
        env->ReleaseByteArrayElements(jniByteArray,
                                          jniPrimitiveByteArray,
                                          0 ); // 复制回内容并释放 参数三 的缓冲区
        jniPrimitiveByteArray = NULL;
    }
exit:
    return iResult;
}
方法二是通用的方法。

char转jstring

方法一:
   jstring NewStringUTF(const char* bytes)
    { return functions->NewStringUTF(this, bytes); }
方法二:
// 指定编码且以零('\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;
	}

	// 获取 Java String 类和回调方法 I D信息,
	// 也可以用全局变量记下来,免得浪费时间重复执行。
	jniStringClass = env->FindClass( "java/lang/String" );

	if ( NULL == jniStringClass )
	{
		ALOGE( "jstring2pchar Call FindClass to get java/lang/String failed!" );
		goto exit;
	}


	jniInitMethodID = env->GetMethodID(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 ); // 来源缓冲区

	// 用新建的字节数组来构建 Java String
	jniReturnString = (jstring)env->NewObject(jniStringClass,
												  jniInitMethodID,
												  jniByteArray,
												  jniEncoding );
exit:	
    return jniReturnString;
}
方法二较复杂,但是比较通用的方法

jstring和char的相互转化参考源码: 点击获取源码





  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值