JNI层线程回调Java函数关键点及示例

时间:2022-04-04 18:16:33

最近做了一个screenrecorder 的任务,主要功能是通过java层调用jni 层的screenrecorder,其中有一些状态需要从jni 反馈回java (录像完成,出错,进度等状态),曾经郁闷了很长时间,发现下面2篇文章比较有帮助。 

第一篇主要把关键点指示出来了,第二篇例子比较全!


1 关键点:

JNI是Java Native Interface的缩写,是Java平台的重要特性,使得Java代码可以方便地与C/C++代码编译生成的动态链接库进行交互。本文主要给出一份示例代码(工程文件见附件),描述如何在Android的JNI层开启一个线程,并在线程中回调Java层的函数。


代码主要分为Java层(java代码)和JNI层(c语言代码),首先看看Java层的代码(Native.java)。


JNI层线程回调Java函数关键点及示例


如上所示,Java层与JNI层的接口代码主要封装在Native类中,该类定义了三个native函数,分别完成jni库的初始化,调用jni层开启线程,调用jni层关闭线程等功能。并且提供一个回调函数(onNativeCallback),供jni层调用,并在回调函数中打印count的值。


再看看JNI层是如何开启线程并回调Java层的(native.c),关键的地方都在代码中进行了注释:


   1. 头文件包含和全局变量的定义

           

JNI层线程回调Java函数关键点及示例

         

   2. 初始化函数的实现

             

JNI层线程回调Java函数关键点及示例


   3. 开启关闭线程的实现

 

JNI层线程回调Java函数关键点及示例

                                           

 4. 线程的实现(关键)


JNI层线程回调Java函数关键点及示例


   由上述代码可以看到,JNI层通过pthread库完成了线程的创建,需要特别注意的是,JNI层的线程中,必须通过全局的JavaVM来获取到环境变量,也必须通过全局的jobject获取java类对象,从而找到java端的函数,进行回调。

http://ticktick.blog.51cto.com/823160/1358558


                       深入了解android平台的jni---本地多线程调用java代码


一、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

作者:流星