Qt on Android Episode 7(翻译)
2015-06-02 07:40
645 查看
原文链接: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 UI线程
右边的绿色矩形代表Qt的主线程(Qt主事件循环运行在其中)。如果你想了解Android UI和Qt线程的更多信息请阅读Episode 1。
顶部的黑色矩形是你应用中的Java部分。如你所见,它大部分运行在Android UI线程中。 Java部分运行在Qt线程中的唯一情况,是我们在Qt线程里从C/C++代码里调用它(这也是大部分JNI调用发生的地方)。
底部的黑色矩形是你应用的C/C++(Qt)部分。如你所见,它大部分运行在Qt线程中。C/C++部分运行在Android UI线程的唯一情况,是我们在Android UI线程里的Java部分调用它(大部分的Java回调发生在这里)。
好啦……那么,问题是什么?嗯,问题是,有一部分Android API必须在Android UI线程中调用,而当我们在C/C++代码里调用Java方法时实际上是在Qt线程里做这件事。这就是说,我们需要有一种方法让这些代码运行在Android UI线程中而不是Qt线程中。为了从C/C++ Qt线程到Java Android UI线程实现这样的调用,我们需要三个步骤:
从C/C++ Qt线程里调用一个Java方法。这个方法会在Qt线程里执行,因此我们需要一种方法,能在Android UI线程里访问Android API。
我们的Java方法使用Activity.runOnUiThread来投递一个Runnable到Android UI线程里。Android事件循环会在Android UI线程里执行这个Runnable。
Runnable对象在Android UI线程里访问Android API。
当Java代码调用C/C++函数时也存在类似的问题,因为Java会在Android UI线程里调用我们的C/C++函数,因此我们需要一种方法在Qt线程里传递那些通知。也有三个步骤:
在 Android UI线程里调用一个C/C++函数.
使用QMetaObject::invokeMethod向Qt事件循环投递一个方法调用。
Qt时间循环在Qt线程里执行那个函数。
第一步是创建一个定制的Activity,继承自QtActivity,并且定义一个用来投递我们的Runnable的方法。
接下来要改变AndroidManifest.xml的默认Activity,从这样:
改成这样:
我们这么做是为了确保应用启动时会实例化我们定制的Activity。
第三步是定义我们的RegisterReceiverRunnable类:这个类的run方法会在Android UI线程里被调用。在run方法里我们注册我们的SDCardReceiver监听器。
让我们看看类的样子:
SDCardReceiver重写了onReceive方法,然后它使用声明的原生方法向C/C++代码发送通知。
最后一步是声明在SDCardReceiver里用到的原生函数:
首先我们需要做的是调用registerBroadcastReceiver方法。
!
在native.cpp中,我们注册了原生函数。在我们的静态原生函数里我们使用QMetaObject::invokeMethod来向Qt线程投递一个槽调用。
MainWindow类仅仅是在收到通知时给我们的plainText控件添加一些文字。在Android线程里调用这些函数可能大大损害我们应用的健壮性——它可能导致崩溃或不可预知的行为,因此它们必须在Qt线程里被调用。
示例源码下载:Click Here。
谢谢你肯花时间读这篇文章。
(译者注:BogDan Vatra真是超级nice,提供了这么多图,把Java <–> C++之间的相互调用解释得太清楚了。)
我翻译的Qt on Android Episode系列文章:
Qt on Android Episode 1
Qt on Android Episode 2
Qt on Android Episode 3
Qt on Android Episode 4
Qt on Android Episode 5
Qt on Android Episode 6
我开通了微信订阅号“程序视界”,关注即可第一时间看到我的原创文章以及我推荐的精彩文章:
译者 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架构。
Qt on Android架构
关于架构图的几句话:
左边的蓝色矩形代表Android UI线程
右边的绿色矩形代表Qt的主线程(Qt主事件循环运行在其中)。如果你想了解Android UI和Qt线程的更多信息请阅读Episode 1。
顶部的黑色矩形是你应用中的Java部分。如你所见,它大部分运行在Android UI线程中。 Java部分运行在Qt线程中的唯一情况,是我们在Qt线程里从C/C++代码里调用它(这也是大部分JNI调用发生的地方)。
底部的黑色矩形是你应用的C/C++(Qt)部分。如你所见,它大部分运行在Qt线程中。C/C++部分运行在Android UI线程的唯一情况,是我们在Android UI线程里的Java部分调用它(大部分的Java回调发生在这里)。
好啦……那么,问题是什么?嗯,问题是,有一部分Android API必须在Android UI线程中调用,而当我们在C/C++代码里调用Java方法时实际上是在Qt线程里做这件事。这就是说,我们需要有一种方法让这些代码运行在Android UI线程中而不是Qt线程中。为了从C/C++ Qt线程到Java Android UI线程实现这样的调用,我们需要三个步骤:
从C/C++ Qt线程里调用一个Java方法。这个方法会在Qt线程里执行,因此我们需要一种方法,能在Android UI线程里访问Android API。
我们的Java方法使用Activity.runOnUiThread来投递一个Runnable到Android UI线程里。Android事件循环会在Android UI线程里执行这个Runnable。
Runnable对象在Android UI线程里访问Android API。
当Java代码调用C/C++函数时也存在类似的问题,因为Java会在Android UI线程里调用我们的C/C++函数,因此我们需要一种方法在Qt线程里传递那些通知。也有三个步骤:
在 Android UI线程里调用一个C/C++函数.
使用QMetaObject::invokeMethod向Qt事件循环投递一个方法调用。
Qt时间循环在Qt线程里执行那个函数。
扩展Java部分
在你开始之前,最好读一读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部分的架构
让我们结合结构图看看Java部分的调用总结:扩展C/C++部分
现在我们来看看怎么扩展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 & C/C++相互调用的结构
下面是我们已经完成的C/C++和Java之间的所有调用的架构图:示例源码下载:Click Here。
谢谢你肯花时间读这篇文章。
(译者注:BogDan Vatra真是超级nice,提供了这么多图,把Java <–> C++之间的相互调用解释得太清楚了。)
我翻译的Qt on Android Episode系列文章:
Qt on Android Episode 1
Qt on Android Episode 2
Qt on Android Episode 3
Qt on Android Episode 4
Qt on Android Episode 5
Qt on Android Episode 6
我开通了微信订阅号“程序视界”,关注即可第一时间看到我的原创文章以及我推荐的精彩文章:
相关文章推荐
- Android-Service的生命周期
- Android-Service
- Android Canvas转Bitmap转ImageView 指定尺寸大小
- Mac 平台搭建 Android 集成开发环境
- Android Studio 使用技巧(6)
- Android Studio 使用技巧(5)
- Android Studio 使用技巧(4)
- Android Studio 使用技巧(3)
- Android Studio 使用技巧(2)
- Android Studio 使用技巧(1)
- 关注Android安全 手机锁屏勒索国内首现身
- android天气1
- android天气2
- Android项目的目录结构
- ADT中添加libs下的jar包,增加了reference library,而没有增加Android private library的
- android 新浪微博登录获取access_token值的问题
- android studio引入第三方jar包
- marki-制作.9格式的素材图片
- Android Resources$NotFoundException: String resource ID #0x1
- Android动画之translate(位移动画)