找回密码
 立即注册
收起左侧

[转][译]Qt安卓教程(6): 初识JNI

7
回复
12834
查看
[复制链接]

尚未签到

来源: Android 2015-1-5 14:30:52 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
先说个实话,本人除了学校学过一点Java,其它真不懂,弄的我真不知道怎么去翻译这篇文章:http://www.kdab.com/qt-android-episode-5/

只好去外面“自主创新”一下,然后找到了一篇不错的: http://blog.csdn.net/foruok/article/details/41826085

以下文字基本来自这个csdn博客:


为什么需要 JNI

    因为 Qt 要实现 Android 的所有功能是不现实的。要想使用 Android 系统已经具备的功能,就需要通过 JNI 来访问它们。 JNI 是在 Java 和 C++ 之间相互调用的唯一途径。

JNI 简介

    这篇文章我们来学习 JNI 的基本知识,下一篇文章呢,我们将研究如何使用本文介绍的 JNI 知识来扩展我们的 Qt 应用。

    网上有太多太多关于 JNI 的讨论了,本文将聚焦于如何在 Android 系统上通过 Qt 来使用 JNI 。从 5.2 开始, Qt 携带了 Qt Android Extras 这个模块。当我们不得不使用 JNI 时,它提供给我们更舒适的体验。

    我们会讨论两件事:


  • 从 C++ 中调用一个 Java 函数
  • 从 Java 中回调一个 C++ 函数

从 C++ 中调用一个 Java 函数

    使用 Qt Android Extras 来调用一个 Java 函数是相当简单的。

    首先,我们来创建一个 Java 的静态方法:

  1. // java file android/src/com/kdab/training/MyJavaClass.java
  2. package com.kdab.training;

  3. public class MyJavaClass
  4. {
  5.     // this method will be called from C/C++
  6.     public static int fibonacci(int n)
  7.     {
  8.         if (n < 2)
  9.             return n;
  10.         return fibonacci(n-1) + fibonacci(n-2);
  11.     }
  12. }
复制代码
    如你所见,我们在 com.kdab.training 包内的 MyJavaClass 类内定义了 fibonacci 这个静态方法,这个方法会计算 fibonacci 数并返回结果。

    现在我们来看看怎么从 Qt 中调用 fibonacci 这个方法。

    第一步,因为我们要用到 Qt Android Extras ,所以要修改一下 .pro 文件,如下:

  1. # Changes to your .pro file
  2. # ....
  3. QT += androidextras
  4. # ....
复制代码
    第二步,执行调用:
  1. // C++ code
  2. #include <QAndroidJniObject>
  3. int fibonacci(int n)
  4. {
  5.     return QAndroidJniObject::callStaticMethod<jint>
  6.                         ("com/kdab/training/MyJavaClass" // class name
  7.                         , "fibonacci" // method name
  8.                         , "(I)I" // signature
  9.                         , n);
  10. }
复制代码
    Yes ! 这就是所有的事儿喽。

    让我们仔细地来看看这段代码,看看里面都有什么:


  • 我们使用 QAndroidJniObject::callStaticMethod 来调用一个 Java 方法
  • 第一个参数是全路径类名,包名后跟一个类名,包名中的 . 被替换为 /
  • 第二个参数是方法名
  • 第三个参数是方法签名
  • 最后一个参数是我们要传递给 Java 方法的参数

    请阅读  QAndroidJniObject 的文档来了解方法签名和参数类型的细节。

从 Java 中回调一个 C++ 函数

    为了从 Java 回调 C++ 方法,你可以按下面的步骤来做:


  • 在 Java 中使用 native 关键字来声明 native 方法
  • 在 C++ 中注册 native 方法
  • 调用 Java 里的 native 方法

使用 native 关键字声明 native 方法

    让我们来稍稍改动一下之前的 Java 代码:

  1. // java file android/src/com/kdab/training/MyJavaClass.java
  2. package com.kdab.training;

  3. class MyJavaNatives
  4. {
  5.     // declare the native method
  6.     public static native void sendFibonaciResult(int n);
  7. }

  8. public class MyJavaClass
  9. {
  10.     // this method will be called from C/C++
  11.     public static int fibonacci(int n)
  12.     {
  13.         if (n < 2)
  14.             return n;
  15.         return fibonacci(n-1) + fibonacci(n-2);
  16.     }

  17.     // the second method that will be called from C/C++
  18.     public static void compute_fibonacci(int n)
  19.     {
  20.         // callback the native method with the computed result.
  21.         MyJavaNatives.sendFibonaciResult(fibonacci(n));
  22.     }
  23. }
复制代码
    让我们仔细瞄一瞄这段代码里都有什么:

  • 我个人比较倾向把所有的 native 方法都隔离到一个独立的类里,所以我在 com.kdab.training 包内定义了 MyJavaNatives 类,声明了 sendFibonaciResult 这个 native 方法。静态的 compute_fibonacci 方法会调用 sendFibonaciResult 来回调 C++ ,发送计算结果,而不再通过 fibonacci 的返回值来做这件事。
  • sendFibonaciResult ,这个方法会被 C++ 代码调用,但它不像 fibonacci 那样直接返回计算结果,它使用 sendFibonaciResult 这个 native 方法来回调 C++ 世界来传递计算结果。


    如果你尝试运行现在的代码,会失败。因为 sendFibonaciResult 还没注册, JVM 根本不知道它是神马玩意儿。

使用 Java_Fully_Qualified_ClassName_MethodName 注册函数

    代码:

  1. #include <jni.h>
  2. #include <QDebug>

  3. #ifdef __cplusplus
  4. extern "C" {
  5. #endif

  6. JNIEXPORT void JNICALL
  7.   Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv *env,
  8.                                                     jobject obj,
  9.                                                     jint n)
  10. {
  11.     qDebug() << "Computed fibonacci is:" << n;
  12. }

  13. #ifdef __cplusplus
  14. }
  15. #endif
复制代码
    让我们仔细瞄一瞄,看看这段代码有何神奇之处:

  • 我们看到的第一件事,就是,所有的函数都必须导出为 C 函数,而不是 C++ 函数
  • 函数名字必须遵循下面的模板:Java 关键,包名,类名,方法名,之间用短下划线分割
  • 当 JVM 加载 so 文件时,会扫描这个模板,自动为你注册你所有符合这个模板的函数
  • 第一个参数 env 是 JNIEnv 类型的指针,指向一个 JNIEnv 对象
  • 第二个参数 obj ,代表你声明这个 native 方法的那个 Java 对象
  • 对于你要注册的每一个函数,第一和第二个参数是强制的,必须的
  • 从第三个参数开始,对应 Java 类里声明的 native 方法的参数,顺次哦,一一对应。所以呢,我们的 C++ 代码里,第三个参数就对应 Java 代码里 sendFibonaciResult 的第一个参数。

    使用这种方式来注册和声明 native 方法是很简单的,但是它有几个缺点:


  • 函数名巨长,比如 Java_com_kdab_training_MyJavaNatives_sendFibonaciResult ,鬼才记得住,记得住敲得也烦不是
  • 库必须导出所有的函数
  • 不安全, JVM 没办法检查函数的签名,因为函数是以 C 的方式导出的,而不是 C++ 的方式

使用 JNIEnv::RegisterNatives 来注册 native 函数

    为了使用 JNIEnv::RegisterNatives 来注册 native 函数,我们需要做下面四步:


  • 第一步,我们需要访问 JNIEnv 指针。最简单的方法是定义和导出 JNI_OnLoad 方法,每个 so 文件一次,可以定义在任何的 cpp 文件里。
  • 第二步,为我们想导出的 C++ 方法创建一个数组
  • 第三步,使用 JniEnv::FindClass 找到声明这些 native 方法的 Java 类的 ID
  • 第四步,调用 JNIEnv::RegisterNatives(java_class_ID, methods_vector, n_methods)

    代码如下:

  1. // C++ code
  2. #include <jni.h>
  3. #include <QDebug>

  4. // define our native method
  5. static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n)
  6. {
  7.     qDebug() << "Computed fibonacci is:" << n;
  8. }

  9. // step 2
  10. // create a vector with all our JNINativeMethod(s)
  11. static JNINativeMethod methods[] = {
  12.     { "sendFibonaciResult", // const char* function name;
  13.         "(I)V", // const char* function signature
  14.         (void *)fibonaciResult // function pointer
  15.     }
  16. };

  17. // step 1
  18. // this method is called automatically by Java VM
  19. // after the .so file is loaded
  20. JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
  21. {
  22.     JNIEnv* env;
  23.     // get the JNIEnv pointer.
  24.     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)
  25.            != JNI_OK) {
  26.         return JNI_ERR;
  27.     }

  28.     // step 3
  29.     // search for Java class which declares the native methods
  30.     jclass javaClass = env->FindClass("com/kdab/training/MyJavaNatives");
  31.     if (!javaClass)
  32.         return JNI_ERR;

  33.     // step 4
  34.     // register our native methods
  35.     if (env->RegisterNatives(javaClass, methods,
  36.                             sizeof(methods) / sizeof(methods[0])) < 0) {
  37.         return JNI_ERR;
  38.     }
  39.     return JNI_VERSION_1_6;
  40. }
复制代码
    让我们核对一下代码以便更好地理解:

  • static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n),这是我们注册的方法, JVM 将会调用它。
  • 第一个参数 env 指向 JNIEnv 对象
  • 第二个参数 obj 是声明 fibonaciResult 这个本地方法的 Java 对象的引用
  • 第一、第二个参数对于要导出的函数是强制的、必须的
  • 从第三个参数开始,对应 Java 类里声明的 native 方法的参数,顺次哦,一一对应。所以呢,我们的 C++ 代码里,第三个参数就对应 Java 代码里 sendFibonaciResult 的第一个参数。
  • 我们把这个方法加到了  JNINativeMethod 类型的方法数组里
  • JNINativeMethod 结构体有下列成员:
    • const char* name - 函数名字,必须和 Java 里声明的 native 方法名字一致
    • const char* signature - 函数签名,必须和 Java 里声明的 native 方法的参数一致
    • void* fnPtr - C++函数指针,我们的代码里呢,就是 fibonaciResult 。如你所见, C++ 函数名字是无所谓的,因为 JVM 只需要一个指针。
  • 剩下的代码简单、清晰,没必要再解释了吧亲,还有注释呢哈。

这种方式用起来看着有那么一点点复杂,但是它有下列好处:


  • C++ 方法的名字可以随你的便亲
  • 库只需要导出一个函数
  • 安全, JVM 会校验函数签名

    用哪种方式来注册 native 函数是个人偏好问题,但我还是推荐使用 JNIEnv::RegisterNatives 这种方式,因为它提供了额外的保护:当 JVM 检测到函数签名不匹配时会抛出一个异常。

总结

    这篇文章我们学习了 JNI 的基本知识,下一篇文章呢,我们将研究如何使用本文介绍的 JNI 知识来扩展我们的 Qt 应用。我们会更多的讨论 Qt on Android 应用的架构、如何扩展你应用的 Java 部分,我们还会提供一个实际的例子来说明如何在 Qt 的线程和 Java 的 UI 线程之间相互调用。


回复

使用道具 举报

累计签到:1564 天
连续签到:1 天
2015-1-5 14:59:06 显示全部楼层
呵呵,终于更新了。学习这些内容,对Qt Android开发可以有更清楚的认识。
回复 支持 反对

使用道具 举报

尚未签到

2015-1-5 19:19:38 显示全部楼层
yafeilinux 发表于 2015-1-5 14:59
呵呵,终于更新了。学习这些内容,对Qt Android开发可以有更清楚的认识。

可惜是抄别人的
回复 支持 反对

使用道具 举报

累计签到:6 天
连续签到:1 天
2015-1-13 17:55:02 显示全部楼层
欢迎转载哈,希望更多人看到。大家也可以到我博客[url]http://blog.csdn.net/foruok[/url]逛逛,还有很多其它的内容。
回复 支持 反对

使用道具 举报

尚未签到

2015-1-13 19:40:33 显示全部楼层
foruok 发表于 2015-1-13 17:55
欢迎转载哈,希望更多人看到。大家也可以到我博客http://blog.csdn.net/foruok[/url]逛逛,还有很多其它的 ...

回复 支持 反对

使用道具 举报

尚未签到

2016-3-14 16:45:01 显示全部楼层
呵呵你真厉害,佩服哦
回复 支持 反对

使用道具 举报

尚未签到

2016-4-14 08:41:17 显示全部楼层
回复 支持 反对

使用道具 举报

累计签到:8 天
连续签到:1 天
2017-4-12 10:39:20 显示全部楼层
很厉害的样子!攒。。。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了