NDK使用技巧、多线程调用注意、ndk中的工具使用

//NDK 使用技巧和多线程调用注意
//http://www.ibm.com/search/csass/search/?q=ndk&sn=dw&lang=zh&cc=CN&en=utf&hpp=20&dws=cndw&lo=zh
void demo(JNIEnv* env, jobject thiz) {
	//这JNI接口指针可以存储,但只在当前线程仍然是有效的。
	/*
	 A JNI environment pointer (JNIEnv*) is passed as an argument for each native function mapped to a Java method, allowing for interaction with the JNI environment within the native method. This JNI interface pointer can be stored, but remains valid only in the current thread. Other threads must first call AttachCurrentThread() to attach themselves to the VM and obtain a JNI interface pointer. Once attached, a native thread works like a regular Java thread running within a native method. The native thread remains attached to the VM until it calls DetachCurrentThread() to detach itself.[4]

	 To attach to the current thread and get a JNI interface pointer:

	 JNIEnv *env;
	 (*g_vm)->AttachCurrentThread (g_vm, (void **) &env, NULL); 挂载到当前线程
	 To detach from the current thread:

	 (*g_vm)->DetachCurrentThread (g_vm); 分离当前线程

	 自己创建的子线程不能用主线程的JNIEnv了,得用AttachCurrentThread生成自己的JNIEnv ,用完后记得调用DetachCurrentThread。
	 */
	//jni多线程 在底层c++生成的子线程中调用Java,需要JniEnv。而这个JniEnv的生成,需要用AttachCurrentThread方法。
	//java字符串
	jstring javaString = NULL;

	// Correct way: Create and release native string from Java string
	const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
	printf("%s", nativeString);
	(*env)->ReleaseStringUTFChars(env, javaString, nativeString);
	env->DeleteLocalRef(javaString);

	//java整形数组求和
	jintArray arr = NULL;
	jint buf[10];
	jint i, sum = 0;
	// This line is necessary, since Java arrays are not guaranteed to have a continuous memory layout like C arrays.
	env->GetIntArrayRegion(arr, 0, 10, buf);
	for (i = 0; i < 10; i++) {
		sum += buf[i];
	}

	//程序员在使用 Global Reference 时,需要仔细维护对 Global Reference 的使用。如果一定要使用 Global Reference,务必确保在不用的时候删除。
	int JniLoad(JavaVM* jvm, void* reserved) {
		g_InterfaceObject = env->NewGlobalRef(obj);
	}
	void JniUnLoad(JavaVM* jvm, void* reserved) {
		//全局变量
		env->DeleteGlobalRef(g_InterfaceObject);
	}

	// jni抛出异常
	jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");
	//抛出
	env->ThrowNew(exceptionClazz, "Unable to find method--");
	jclass jcls;//需要DeleteLocalRef
	jobject jcls;//需要DeleteLocalRef
	jstring jcls;//需要ReleaseStringUTFChars DeleteLocalRef
	jarray jcls;//需要DeleteLocalRef
	jmethodid jfieldid//不需要DeleteLocalRef
	//当用 malloc() 在进程堆中动态分配内存时,JNI 程序在使用完后,应当调用 free() 将内存释放。
}


http://www.android100.org/html/201308/19/3994.html

一、jni调用java对象

JNI提供的功能之一是在本地代 码中使用Java对象。包括:创建一个java类对象和通过函数传递一个java对象。创建一个java类对象,首先需要得到得到使用 FindClass/GetObjectClass函数得到该类,然后使用GetMethodID方法得到该类的方法id,然后调用该函数。 Java 和 Native 代码之间函数调用时,如果是简单类型,也就是内置类型,比如 int, char 等是值传递(pass by value),而其它 Java 对象都是引用传递(pass by reference),这些对象引用由 JVM 传给 Native 代码。

在本地方法中调用Java对象的方法的步骤:

1)获取你需要访问的Java对象的类

FindClass通过传java中完整的类名来查找java的class

GetObjectClass通过传入jni中的一个java的引用来获取该引用的类型。

他们之间的区别是,前者要求你必须知道完整的类名,后者要求在Jni有一个类的引用。

2)获取MethodID,调用方法

GetMethodID 得到一个实例的方法的ID

GetStaticMethodID 得到一个静态方法的ID

3)获取对象的属性

GetFieldID 得到一个实例的域的ID

GetStaticFieldID 得到一个静态的域的ID

JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。

二、jni中引用的java对象的生命周期

Java 对象做为引用被传递到本地方法中,所有这些Java对象的引用都有一个共同的父类型jobject(相当于java中的 Object类是所有类的父类一样)。 这些对象引用都有其生命周期。在JNI中对Java对象的引用根据生命周期分为:全局引用,局部引用、弱全局引用

1、Local Reference 本地引用,

函数调用时传入jobject或者jni函数创建的jobejct,都是本地引用.

其特点就是一旦JNI层函数返回,jobject就被垃圾回收掉,所以需要注意其生命周期。可以强制调用DeleteLocalRef进行立即回收。

jstring pathStr = env->NewStringUTF(path)

....

env->DeleteLocalRef(pathStr);

2、Global Reference 全局引用 ,这种对象如不主动释放,它永远都不会被垃圾回收

创建: env->NewGlobalRef(obj);

释放: env->DeleteGlobalRef(obj)

若 要在某个 Native 代码返回后,还希望能继续使用 JVM 提供的参数, 或者是过程中调用 JNI 函数的返回值(比如 g_mid), 则将该对象设为 global reference,以后只能使用这个 global reference;若不是一个 jobject,则无需这么做。

3、Weak Global Reference 弱全局引用

一种特殊的 Global Reference ,在运行过程中可能被垃圾回收掉,所以使用时请务必注意其生命周期及随时可能被垃圾回收掉,比如内存不足时。

使用前可以利用JNIEnv的 IsSameObject 进行判定它是否被回收

env->IsSameObject(obj1,obj2);

三、本地线程中调用java对象

问题1:

JNIEnv是一个线程相关的变量

JNIEnv 对于每个 thread 而言是唯一的

JNIEnv *env指针不可以为多个线程共用

解决办法:

但是java虚拟机的JavaVM指针是整个jvm公用的,我们可以通过JavaVM来得到当前线程的JNIEnv指针.

可以使用javaAttachThread保证取得当前线程的Jni环境变量

static JavaVM *gs_jvm=NULL;

gs_jvm->AttachCurrentThread((void **)&env, NULL);//附加当前线程到一个Java虚拟机

jclass cls = env->GetObjectClass(gs_object);

jfieldID fieldPtr = env->GetFieldID(cls,"value","I");

问题2:

不能直接保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它。

解决办法:

用env->NewGlobalRef创建一个全局变量,将传入的obj(局部变量)保存到全局变量中,其他线程可以使用这个全局变量来操纵这个java对象

注意:若不是一个 jobject,则不需要这么做。如:

jclass 是由 jobject public 继承而来的子类,所以它当然是一个 jobject,需要创建一个 global reference 以便日后使用。

而 jmethodID/jfieldID 与 jobject 没有继承关系,它不是一个 jobject,只是个整数,所以不存在被释放与否的问题,可保存后直接使用。

static jobject gs_object=NULL;

JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj)

{

env->GetJavaVM(&gs_jvm); //保存到全局变量中JVM

//直接赋值obj到全局变量是不行的,应该调用以下函数:

gs_object=env->NewGlobalRef(obj);

}

jni部分代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#include<jni.h>
#include<android/log.h>

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))

#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__))

#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "native-activity", __VA_ARGS__))

//全局变量

JavaVM *g_jvm = NULL;

jobject g_obj = NULL;

void *thread_fun(void* arg)

{

JNIEnv *env;

jclass cls;

jmethodID mid;

//Attach主线程

if((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK)

{

LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);

return NULL;

}

//找到对应的类

cls = (*env)->GetObjectClass(env,g_obj);

if(cls == NULL)

{

LOGE("FindClass() Error.....");

goto error;

}

//再获得类中的方法

mid = (*env)->GetMethodID(env, cls, "fromJNI", "(I)V");

if (mid == NULL)

{

LOGE("GetMethodID() Error.....");

goto error;

}

//最后调用java中的静态方法

(*env)->CallVoidMethod(env, cls, mid ,(int)arg);

error:

//Detach主线程

if((*g_jvm)->DetachCurrentThread(g_jvm) != JNI_OK)

{

LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);

}

pthread_exit(0);

}

//由java调用以创建子线程

JNIEXPORT void Java_com_test_JniThreadTestActivity_mainThread( JNIEnv* env, jobject obj, jint threadNum)

{

int i;

pthread_t* pt;

pt = (pthread_t*) malloc(threadNum * sizeof(pthread_t));

for (i = 0; i < threadNum; i++){

//创建子线程

pthread_create(&pt[i], NULL, &thread_fun, (void *)i);

}

for (i = 0; i < threadNum; i++){

pthread_join (pt[i], NULL);

}

LOGE("main thread exit.....");

}

//由java调用来建立JNI环境

JNIEXPORT void Java_com_test_JniThreadTestActivity_setJNIEnv( JNIEnv* env, jobject obj)

{

//保存全局JVM以便在子线程中使用

(*env)->GetJavaVM(env,&g_jvm);

//不能直接赋值(g_obj = obj)

g_obj = (*env)->NewGlobalRef(env,obj);

}

//当动态库被加载时这个函数被系统调用

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)

{

JNIEnv* env = NULL;

jint result = -1;

//获取JNI版本

if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK)

{

LOGE("GetEnv failed!");

return result;

}

return JNI_VERSION_1_4;

}

需要全部源码的,可以打开这个链接下载

http://download.csdn.net/detail/mfcai_blog/5772377

本文欢迎转载,转载请注明出处与作者

出处:http://blog.sina.com.cn/staratsky

作者:流星

android ndk中的工具使用

1. 动态so处理:

arm-linux-androideabi-readelf.exe -a XX.so   > xx.txt 
输出所有函数

arm-linux-androideabi-objdump.exe -dx XX.so > xx.txt 
反汇编so包,此时使用 $(JNI_PROJ_PATH)/obj\local\armeabi下面带符号表的so包。JNI_PROJ_PATH为编译so包时jni文件夹的根目录

2. 静态a处理:

arm-linux-androideabi-ar.exe -t xx.a > xx.txt
输出.a内所有函数

arm-linux-androideabi-nm.exe xx.a > xx.txt 
输出.a内所有函数

3. ndk编译

上面两种库文件,.a和.so都可以直接通过 arm-linux-androideabi-g++.exe 工具编译,编译语法跟linux上的g++一致。 也可以直接使用ndk-build命令! 

4. crash定位

1)使用addr2line将地址转化成代码行数, 输入的so为带符号表的,即为strip过的:
arm-linux-androideabi-addr2line.exe -f -e E:\dev_code\Sosomap-old\Sosomap-jni\obj\local\armeabi\libXX.so 000263ae

2)使用ndk-stack.exe还原堆栈:
ndk-stack -sym E:\dev_code\Sosomap-old\Sosomap-jni\obj\local\armeabi -dump D:\android-ndk-r9b-windows-x86\txmap_log.txt
-sym为带符号表的so路径, -dump为crash的堆栈信息,必须包含:********** Crash dump: ********** 


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值