标签:jni android qt qt-android
原文链接: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卡来存储数据的应用来讲,这是很有用的一个例子,因为如果应用在收到通知后不立即关闭打开的文件,它就会被Android系统干掉。
正如我们在Episode 5中看到的那样,从C/C++代码里调用Java方法或者从Java代码里调用C/C++方法都是相当简单的,但不是所有情况都管用。为什么?
为了理解为什么不行,首先我们需要理解Qt on Android架构。
关于架构图的几句话:
好啦……那么,问题是什么?嗯,问题是,有一部分Android API必须在Android UI线程中调用,而当我们在C/C++代码里调用Java方法时实际上是在Qt线程里做这件事。这就是说,我们需要有一种方法让这些代码运行在Android UI线程中而不是Qt线程中。为了从C/C++ Qt线程到Java Android UI线程实现这样的调用,我们需要三个步骤:
当Java代码调用C/C++函数时也存在类似的问题,因为Java会在Android UI线程里调用我们的C/C++函数,因此我们需要一种方法在Qt线程里传递那些通知。也有三个步骤:
在你开始之前,最好读一读Episode 6,因为你需要它来方便地管理Java文件。
第一步是创建一个定制的Activity,继承自QtActivity,并且定义一个用来投递我们的Runnable的方法。
// src/com/kdab/training/MyActivity.java
package com.kdab.training;
import org.qtproject.qt5.android.bindings.QtActivity;
public class MyActivity extends QtActivity
{
// this method is called by C++ to register the BroadcastReceiver.
public void registerBroadcastReceiver() {
// Qt is running on a different thread than Android.
// In order to register the receiver we need to execute it in the Android UI thread
runOnUiThread(new RegisterReceiverRunnable(this));
}
}
接下来要改变AndroidManifest.xml的默认Activity,从这样:
<activity ...
android:name="org.qtproject.qt5.android.bindings.QtActivity"
... >
改成这样:
<activity ...
android:name="com.kdab.training.MyActivity"
... >
我们这么做是为了确保应用启动时会实例化我们定制的Activity。
第三步是定义我们的RegisterReceiverRunnable类:这个类的run方法会在Android UI线程里被调用。在run方法里我们注册我们的SDCardReceiver监听器。
// src/com/kdab/training/RegisterReceiverRunnable.java
package com.kdab.training;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
public class RegisterReceiverRunnable implements Runnable
{
private Activity m_activity;
public RegisterReceiverRunnable(Activity activity) {
m_activity = activity;
}
// this method is called on Android Ui Thread
@Override
public void run() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
filter.addDataScheme("file");
// this method must be called on Android Ui Thread
m_activity.registerReceiver(new SDCardReceiver(), filter);
}
}
让我们看看类的样子:
// src/com/kdab/training/SDCardReceiver.java
package com.kdab.training;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class SDCardReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// call the native method when it receives a new notificatio**SDCardReceiver**n
if (intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED))
NativeFunctions.onReceiveNativeMounted();
else if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED))
NativeFunctions.onReceiveNativeUnmounted();
}
}
SDCardReceiver重写了onReceive方法,然后它使用声明的原生方法向C/C++代码发送通知。
最后一步是声明在SDCardReceiver里用到的原生函数:
// src/com/kdab/training/NativeFunctions.java
package com.kdab.training;
public class NativeFunctions {
// define the native function
// these functions are called by the BroadcastReceiver object
// when it receives a new notification
public static native void onReceiveNativeMounted();
public static native void onReceiveNativeUnmounted();
}
让我们结合结构图看看Java部分的调用总结:
现在我们来看看怎么扩展C/C++部分。为了演示怎么做,我使用一个简单的基于QWidget的应用。
首先我们需要做的是调用registerBroadcastReceiver方法。
// main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QtAndroid>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// call registerBroadcastReceiver to register the broadcast receiver
QtAndroid::androidActivity().callMethod<void>("registerBroadcastReceiver", "()V");
MainWindow::instance().show();
return a.exec();
}
// native.cpp
#include <jni.h>
#include <QMetaObject>
#include "mainwindow.h"
// define our native static functions
// these are the functions that Java part will call directly from Android UI thread
static void onReceiveNativeMounted(JNIEnv * /*env*/, jobject /*obj*/)
{
// call MainWindow::onReceiveMounted from Qt thread
QMetaObject::invokeMethod(&MainWindow::instance(), "onReceiveMounted"
, Qt::QueuedConnection);
}
static void onReceiveNativeUnmounted(JNIEnv * /*env*/, jobject /*obj*/)
{
// call MainWindow::onReceiveUnmounted from Qt thread, we wait until the called function finishes
// in this function the application should close all its opened files, otherwise it will be killed
QMetaObject::invokeMethod(&MainWindow::instance(), "onReceiveUnmounted"
, Qt::BlockingQueuedConnection);
}
//create a vector with all our JNINativeMethod(s)
static JNINativeMethod methods[] = {
{"onReceiveNativeMounted", "()V", (void *)onReceiveNativeMounted},
{"onReceiveNativeUnmounted", "()V", (void *)onReceiveNativeUnmounted},
};
// this method is called automatically by Java 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;
// search for Java class which declares the native methods
jclass javaClass = env->FindClass("com/kdab/training/NativeFunctions");
if (!javaClass)
return JNI_ERR;
// register our native methods
if (env->RegisterNatives(javaClass, methods,
sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
在native.cpp中,我们注册了原生函数。在我们的静态原生函数里我们使用QMetaObject::invokeMethod来向Qt线程投递一个槽调用。
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
static MainWindow &instance(QWidget *parent = 0);
public slots:
void onReceiveMounted();
void onReceiveUnmounted();
private:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
MainWindow &MainWindow::instance(QWidget *parent)
{
static MainWindow mainWindow(parent);
return mainWindow;
}
// Step 6
// Callback in Qt thread
void MainWindow::onReceiveMounted()
{
ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_MOUNTED"));
}
void MainWindow::onReceiveUnmounted()
{
ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_UNMOUNTED"));
}
MainWindow类仅仅是在收到通知时给我们的plainText控件添加一些文字。在Android线程里调用这些函数可能大大损害我们应用的健壮性——它可能导致崩溃或不可预知的行为,因此它们必须在Qt线程里被调用。
在我们的架构图上,C/C++部分的调用概要如下:
下面是我们已经完成的C/C++和Java之间的所有调用的架构图:
示例源码下载:Click Here。
谢谢你肯花时间读这篇文章。
(译者注:BogDan Vatra真是超级nice,提供了这么多图,把Java <–> C++之间的相互调用解释得太清楚了。)
我翻译的Qt on Android Episode系列文章:
我开通了微信订阅号“程序视界”,关注即可第一时间看到我的原创文章以及我推荐的精彩文章:
标签:jni android qt qt-android
原文地址:http://blog.csdn.net/foruok/article/details/46323129