转载

【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...