原文地址:http://www.kdab.com/qt-android-episode-5/
我们已经知道了如何搭建 Qt on Android 开发环境,怎样使用 Qt on Android ,有哪些可用的部署策略以及如何为应用签名,是时候继续前进了。这篇文章,我们来讲 JNI 。(BogDan 啊,我等你等了好久,当时我写《Qt on Android核心编程》时没等到……)
因为 Qt 要实现 Android 的所有功能是不现实的。要想使用 Android 系统已经具备的功能,就需要通过 JNI 来访问它们。 JNI 是在 Java 和 C++ 之间相互调用的唯一途径。
这篇文章我们来学习 JNI 的基本知识,下一篇文章呢,我们将研究如何使用本文介绍的 JNI 知识来扩展我们的 Qt 应用。
网上有太多太多关于 JNI 的讨论了,本文将聚焦于如何在 Android 系统上通过 Qt 来使用 JNI 。从 5.2 开始, Qt 携带了 Qt Android Extras 这个模块。当我们不得不使用 JNI 时,它提供给我们更舒适的体验。
我们会讨论两件事:
使用 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);
}
}
现在我们来看看怎么从 Qt 中调用 fibonacci 这个方法。
第一步,因为我们要用到 Qt Android Extras ,所以要修改一下 .pro 文件,如下:
# Changes to your .pro file
# ....
QT += androidextras
# ....
// C++ code
#include
int fibonacci(int n)
{
return QAndroidJniObject::callStaticMethod
("com/kdab/training/MyJavaClass" // class name
, "fibonacci" // method name
, "(I)I" // signature
, n);
}
让我们仔细地来看看这段代码,看看里面都有什么:
请阅读 QAndroidJniObject 的文档来了解方法签名和参数类型的细节。
为了从 Java 回调 C++ 方法,你可以按下面的步骤来做:
让我们来稍稍改动一下之前的 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));
}
}
如果你尝试运行现在的代码,会失败。因为 sendFibonaciResult 还没注册, JVM 根本不知道它是神马玩意儿。
代码:
#include
#include
#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
使用这种方式来注册和声明 native 方法是很简单的,但是它有几个缺点:
为了使用 JNIEnv::RegisterNatives 来注册 native 函数,我们需要做下面四步:
代码如下:
// C++ code
#include
#include
// 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(&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;
}
这种方式用起来看着有那么一点点复杂,但是它有下列好处:
用哪种方式来注册 native 函数是个人偏好问题,但我还是推荐使用 JNIEnv::RegisterNatives 这种方式,因为它提供了额外的保护:当 JVM 检测到函数签名不匹配时会抛出一个异常。
这篇文章我们学习了 JNI 的基本知识,下一篇文章呢,我们将研究如何使用本文介绍的 JNI 知识来扩展我们的 Qt 应用。我们会更多的讨论 Qt on Android 应用的架构、如何扩展你应用的 Java 部分,我们还会提供一个实际的例子来说明如何在 Qt 的线程和 Java 的 UI 线程之间相互调用。
回顾一下我翻译的 Qt on Android Episode 系列文章: