转载

【Android】 如何进行JNI回调?

回调作为一种常见的方法间通讯方式,有着不可取代的作用。当处理一些无法立即给出结果的逻辑时,往往会选择开放一个回调接口,让上层用户注册,然后等结果处理结束后调用上层传来的回调对象进行结果返回。这样的设计模式,在 JNI 中也会有很高的实用价值,因此这篇文章主要为大家介绍两种 JNI 层通知 JAVA 层的回调方式。

同步回调方式 】 

同步回调方式,顾名思义,就是在 JNI C/C++ 普通函数内直接调用 JAVA 层回调对象的方式。需要 JAVA 层实现设置一个 CallBack 对象到 JNI 层。但不管是同步还是异步方式,我们都需要先在 JAVA 层定义一个回调接口 TestCallBack.java ,实现如下:

【Android】 如何进行JNI回调?

接口中我们声明了两个方法, OnCall OnCallArgu ,写这两个函数主要用于示范不同类型函数如何在 JNI 层定义使用。 JAVA 层的回调接口写好后,我们将这个回调对象当作一个参数设置到需要的地址,比如一个 init 函数:

public   native   void  init(TestCallback callBack);   

这个示例函数的含义是 JAVA 层调用 init 方法,在 init 方法中,调用 JAVA 层的 callBack 对象通知到 JAVA 层。

我们在 JNI 层具体实现这个方法:(注意本文 Native 层使用的是 C++ ,使用 C 的话, JNI 的语法会稍微不同,主要集中在 env 的调用和方法的函数中,这并不是本文介绍的重点在此不再赘述) 

【Android】 如何进行JNI回调?

细心的人可能注意到了,在调用 JNI GetMethodID 方法时,最后一个参数看起来有些奇怪。这里要介绍一下,这个参数传递的内容叫做“ java 方法签名”,无需死记硬背,可通过“ javap -s 包名 . 类名”查询获取,但注意你要在 build/intermediates/classes (如果你是 Android 开发者并且使用的是 AndroidStudio 开发工具,这个路径是准确的;其他的 IDE 工具请自行查找这个文件夹在哪里)文件夹下执行这个命令才能查询到。这里对于 void 类型的无参方法,它的签名是“ ()V ”,对于有参数的 int OnCallArgu(int arg) ,它的方法签名就是这样“ (I)I ”。

这样一个简单的 JNI 同步回调就处理完了。

异步回调方式 】 

上一节中为大家介绍了同步的回调方式,即直接在调用的 JNI 方法中使用参数中传递过来的 JAVA 回调对象执行回调操作。但是很多时候特别是涉及到网络请求、大量数据计算等耗时操作时, Native 层本身自己就做了异步事件,如果我们要把 Native 层的异步事件转成 JAVA 层的回调模式的话,就不能像上节所述那样直接在调用这些 Native 方法的时候直接传入 callback 等待回调结果了,这些 callback 要移步到 Native 的回调事件中进行调用。这时,就有两个问题必须解决:

1、 Native 层的回调函数里没有 JAVA CallBack 参数

2、 Native 代码中自己实现的回调方法,不带 JNIEnv 对象,无法调用 JAVA

第一个问题,在 Native 自己的回调方法里是不会有 jobject callback 这个参数的。我们得专门设计一个 setCallBack JNI 方法,保存一个全局的 jobject 对象,对象的内容实际上就是 JAVA CallBack 类,让这个 jobject 可以在 Native 层各处可以调用,这样也就可以解决在 Native 层的回调中取不到 JAVA CallBack 对象的问题。

第二个问题,纯 Native 层的代码里,并不带有 JNIEnv 对象。这里有必要简单地介绍一下这个对象。在 JNI 层的代码里, JNIEnv 类型是一个指向全部 JNI 方法的指针,该指针只在创建它的线程有效,不能跨线程传递。因此我们可以看到所有的 JNI 方法都默认地传递了 JNIEnv *env 作为参数,没有这个 env ,是无法调用 JNI 方法的。除此之外,还有一个 JVM 对象,也需要简单了解下, JavaVM 是虚拟机在 JNI 中的表示,一个 JVM 中只有一个 JavaVM 对象,这个对象是线程共享的。换句话说, JVM 更像是一个进程级别的对象。一个进程全局只有一个,和 Android Application 概念非常相似。这个对象因为是线程共享的,因此它提供了可以获取到 JNIEnv 对象的方法,这就非常有用了。如果我拿到了当前的 JVM 对象,我在通过这个对象再获取到 JNIEnv ,那么第二个问题也就迎刃而解了!下面就来看看怎么做吧:

1、在 JAVA 层,声明 JNI 方法:

public   native   void  setCallBack(TestCallback callBack);  

2、在 JNI 层,实现该方法:

【Android】 如何进行JNI回调?

上面代码中的注释请了解一下,这很重要,没有经过 NewGlobalRef 修饰过的 jobject 对象会在函数执行完后直接释放掉。

3、在创建了全局的 CallBack 对象后,我们还需要可以获得 JavaVM 对象的方法,具体实现如下:

JNI 的入口函数是 JNI_OnLoad 的方法,我们可以在这个方法里获取到当前 JVM 对象,因为 JVM 对象是线程共享的全局变量,因此直接在 JNI 层代码中,定义一个全局的 JavaVM *javaVM 变量,用于保存 JVM 的指针即可。

【Android】 如何进行JNI回调?

AttachCurrentThread :前面我们提到该指针只在创建它的线程有效,不能跨线程传递。如果想要在代码的任意地方获取到 JNIEnv 指针,可以通过调用接口( invocation interface )中的 AttachCurrentThread 方法来获取。与其配套的是 DetachCurrentThread 方法, DetachCurrentThread AttachCurrentThread 必须配套使用!因此我加了一个 hasAttated 方法,来避免重复获取对应线程的 JNIEnv 和错误的 Detach

经过上述步骤,异步回调就实现好了。这样 JNI 的同步和异步回调方式就介绍完了。谢谢阅读。

【Android】 如何进行JNI回调?

恒生技术原创文章,未经授权禁止转载。详情见(点击) 转载须知

原文  http://rdc.hundsun.com/portal/article/893.html
正文到此结束
Loading...