Qt on Android Episode 5(翻译)

原文地址:http://www.kdab.com/qt-android-episode-5/

我们已经知道了如何搭建 Qt on Android 开发环境怎样使用 Qt on Android有哪些可用的部署策略以及如何为应用签名,是时候继续前进了。这篇文章,我们来讲 JNI 。(BogDan 啊,我等你等了好久,当时我写《Qt on Android核心编程》时没等到……)

为什么需要 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 的静态方法:

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

public class MyJavaClass
{
    // this method will be called from C/C++
    public static int fibonacci(int n)
    {
        if (n < 2)
            return n;
        return fibonacci(n-1) + fibonacci(n-2);
    }
}

如你所见,我们在 com.kdab.training 包内的 MyJavaClass 类内定义了 fibonacci 这个静态方法,这个方法会计算 fibonacci 数并返回结果。

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

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

# Changes to your .pro file
# ....
QT += androidextras
# ....

第二步,执行调用:

// C++ code
#include <QAndroidJniObject>
int fibonacci(int n)
{
    return QAndroidJniObject::callStaticMethod<jint>
                        ("com/kdab/training/MyJavaClass" // class name
                        , "fibonacci" // method name
                        , "(I)I" // signature
                        , n);
}

Yes ! 这就是所有的事儿喽。

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

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

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

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

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

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

使用 native 关键字声明 native 方法

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

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

class MyJavaNatives
{
    // declare the native method
    public static native void sendFibonaciResult(int n);
}

public class MyJavaClass
{
    // this method will be called from C/C++
    public static int fibonacci(int n)
    {
        if (n < 2)
            return n;
        return fibonacci(n-1) + fibonacci(n-2);
    }

    // the second method that will be called from C/C++
    public static void compute_fibonacci(int n)
    {
        // callback the native method with the computed result.
        MyJavaNatives.sendFibonaciResult(fibonacci(n));
    }
}

让我们仔细瞄一瞄这段代码里都有什么:

  • 我个人比较倾向把所有的 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 注册函数

代码:

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

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT void JNICALL
  Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv *env,
                                                    jobject obj,
                                                    jint n)
{
    qDebug() << "Computed fibonacci is:" << n;
}

#ifdef __cplusplus
}
#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 函数,我们需要做下面四步:

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

代码如下:

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

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

// step 2
// create a vector with all our JNINativeMethod(s)
static JNINativeMethod methods[] = {
    { "sendFibonaciResult", // const char* function name;
        "(I)V", // const char* function signature
        (void *)fibonaciResult // function pointer
    }
};

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

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

    // step 4
    // register our native methods
    if (env->RegisterNatives(javaClass, methods,
                            sizeof(methods) / sizeof(methods[0])) < 0) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

让我们核对一下代码以便更好地理解:

  • 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 线程之间相互调用。

回顾一下我翻译的 Qt on Android Episode 系列文章:

时间: 2024-09-29 16:09:26

Qt on Android Episode 5(翻译)的相关文章

Qt on Android Episode 7(翻译)

原文链接:http://www.kdab.com/qt-android-episode-7/,May 27,2015 by BogDan Vatra. 译者 foruok ,转载请注明出处. 在最近的两篇Qt on Android中学习了怎么使用基础的JNI以及如何使用外部IDE来管理Qt应用的Java部分.这章呢,我们继续前进,关注如何扩展我们的Qt on Android应用的Java部分以及怎样安全地使用JNI来交互. 这部分我们准备实现一个SD卡监听器.对于那些想使用SD卡来存储数据的应用

Qt on Android Episode 6(翻译)

原文链接:http://www.kdab.com/qt-android-episode-6/,Aprile 16, 2015 by BogDan Vatra 译者foruok,转载请保留出处. 在Qt on Android Episode 5中我们学习了用Qt的方式在Android上使用JNI的基本知识.这次,我想把重点放在那些(当我们使用JNI扩展Qt on Android应用时)可以帮助我们提高生产率的工具上. 使用外部IDE来管理Java文件 不幸的是,Qt Creator对Java的支持

Qt on Android

Qt on Android Episode 7(翻译) http://blog.csdn.net/foruok/article/details/46323129 Android基础整理之四大组件Activity http://www.cnblogs.com/net168/p/3969374.html

Qt on Android: Android SDK安装

之前我在 <Windows下Qt 5.2 for Android开发入门>一文中介绍了 Windows 下 Qt on Android 开发环境的搭建,略过了 Android SDK 的安装,实际还是有一些朋友会在这里遇到问题.这里再专门介绍一下 Android SDK 的安装. 版权全部,转载请注明出处:http://blog.csdn.net/foruok Android SDK 能够到这里下载:http://developer.android.com/sdk/index.html . 假

Qt on Android: http下载与Json解析

百度提供有查询 ip 归属地的开放接口,当你在搜索框中输入一个 ip 地址进行搜索,就会打开由 ip138 提供的百度框应用,你可以在框内直接输入 ip 地址查询.我查看了页面请求,提取出查询 ip 归属地的接口,据此使用 Qt 写了个简单的 ip 归属地查询应用.可以在电脑和 Android 手机上运行.这里使用了百度 API ,特此声明,仅可作为演示使用,不能用作商业目的. 版权所有 foruok,转载请注明出处( http://blog.csdn.net/foruok ). 这个例子会用到

Qt on Android:创建可伸缩界面

使用 Qt 来开发 Android 应用,也需要适配不同移动设备,适配多种多样的屏幕和分辨率.这次我们大概来讲一下如何使用 Qt 提供的机制来创建可伸缩的界面. 博客之星评选,点击投我一票,谢谢.投过了也可以点哦,每天都可以投投一票. DPI 必须要解释一下 DPI . DPI , dot per inch ,即每英寸包含的点数.还有一个概念是 PPI ,即每英寸包含的像素数. 这个值越大,像素密度越大,小尺寸的屏幕就可以有大分辨率.比如有的 Android 手机, 3.7 吋屏幕就能提供 96

Qt on Android:qrc与assets

使用 Qt 为 Android 开发应用时,有时我们的应用会携带一些资源文件,如 png . jpg 等,也可能有一些配置文件,如 xml 等,这些文件放在哪里呢? 有两种方式: qrc assets 咱们分别来看下. the Qt Resource System qrc , Qt 的资源文件系统,很好用哦.看个图: 图1 qrc 示意图 你放在 qrc 里的文件,如 copy.png ,会被编译到 exe 文件中(Android应用是 libapplication.so ).当你运行应用时,这

Qt on Android:添加分享功能

前几天(2015-4-15)看到老外的一篇文章,讲如何使用 Qt on Android 来向社交网络分享信息,使用 Qt 提供的 JNI 功能来实现,和我之前写的 QtAndroid 详解系列文章很搭啊,特此翻译过来. 原文在这里:Sharing with Qt on Android .是 2014 年 12 月 12 日的文章,恨不相逢未嫁时--Sigh,又胡扯了--还是 Google 强大,可以搜索到好东西. 原文作者 zagge ,译者foruok(http://blog.csdn.net

《Qt on Android核心编程》目录

有朋友想看看<Qt on Android核心编程>的目录,So-- 目录 <Qt on Android核心编程>目录,截了10张图才弄好,都在下面了. 内容说明 本书内容基于第一个正式支持 Qt on Android 的 Qt SDK 版本,5.2.0 ,写作过程中 Qt 5.3.0 发布,我调整了部分内容,对有变化的部分作了对应介绍. 从目录可以看出,<Qt on Android核心编程>是从 Qt 的核心概念--元对象系统--讲起的,涵盖了信号与槽.事件.多线程.网