Qt on Android Episode 7 (translation), androidepisode
Http://www.kdab.com/qt-android-episode-7/,May 27,2015 by BogDan Vatra.
The translator foruok. For more information, see the source.
In the last two articles on Qt on Android, I learned how to use the basic JNI and how to use external IDE to manage the Java part of the Qt application. In this chapter, we will continue to focus on how to expand the Java part of our Qt on Android Application and how to securely use JNI for interaction.
In this part, we are going to implement an SD card listener. This is a useful example for applications that want to use SD cards to store data, because if the application does not close the opened file immediately after receiving the notification, it will be killed by the Android system.
As we can see in Episode 5, calling Java methods in C/C ++ code or Calling C/C ++ methods in Java code is quite simple, but not all cases work. Why?
To understand why it doesn't work, first we need to understand the Qt on Android architecture.
Qt on Android Architecture
A few words about the architecture diagram:
- The blue rectangle on the left represents the Android UI thread
- The green rectangle on the right represents the main thread of Qt (where the Qt main event runs cyclically ). For more information about Android UI and Qt threads, see Episode 1.
- The black rectangle on the top is the Java section in your application. As you can see, most of them run in the Android UI thread. The only situation where Java runs in the Qt thread is that we call it from the C/C ++ code in the Qt thread (this is where most JNI calls occur ).
- The black rectangle at the bottom is the C/C ++ (Qt) part of your application. As you can see, most of it runs in the Qt thread. The only situation where the C/C ++ part runs on the Android UI thread is that the Java part of the Android UI thread calls it (most Java callbacks occur here ).
Okay ...... So what is the problem? Well, the problem is,Some Android APIs must be called in the Android UI thread.When we call the Java method in C/C ++ codeQt thread. That is to say, we need a way to run the code.In the Android UI thread rather than the Qt thread. To implement such a call from the C/C ++ Qt thread to the Java Android UI thread, we need three steps:
Similar problems exist when Java code calls C/C ++ functions, Because Java willAndroid UI threadSo we need a method inQt threadTo pass those notifications. There are also three steps:
Java Extension
Before you start, you 'd better read Episode 6, because you need it to easily manage Java files.
The first step is to create a custom Activity, inherit from QtActivity, and define a method for shipping our Runnable.
// src/com/kdab/training/MyActivity.javapackage 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)); }}
Next, we need to change the default Activity of AndroidManifest. xml from:
<activity ... android:name="org.qtproject.qt5.android.bindings.QtActivity" ... >
Changed to this:
<activity ... android:name="com.kdab.training.MyActivity" ... >
This is done to ensure that the application will be instantiated at startup.Custom Activity.
Step 3: define ourRegisterReceiverRunnableClass: The run method of this class willAndroid UI thread. InRunIn the method, we register ourSDCardReceiverListener.
// src/com/kdab/training/RegisterReceiverRunnable.javapackage 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); }}
Let's take a look at the class:
// src/com/kdab/training/SDCardReceiver.javapackage 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(); }}
SDCardReceiverThe onReceive method is rewritten, And then it uses the declared native method to send notifications to C/C ++ code.
The last step is to declareSDCardReceiverNative functions used in:
// src/com/kdab/training/NativeFunctions.javapackage 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 Architecture
Let's take a look at the call summary in the Java section by combining the structure diagram:
C/C ++ Extension
Now let's take a look at how to expand the C/C ++ section. To demonstrate how to do this, I use a simple QWidget-based application.
The first thing we need to do is callRegisterBroadcastReceiverMethod.
// 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 threadstatic 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 loadedJNIEXPORT 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;}
InNative. cppWe have registered Native functions. In our static native functions, we use QMetaObject: invokeMethod to deliver a slot call to the Qt thread.
// mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: 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 threadvoid MainWindow::onReceiveMounted(){ ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_MOUNTED"));}void MainWindow::onReceiveUnmounted(){ ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_UNMOUNTED"));}
MainWindowClass only adds some text to the plainText control when receiving the notification. Calling these functions in the Android thread can greatly damage the robustness of our application-it may cause crashes or unpredictable behavior, so they must be called in the Qt thread.
C/C ++ Architecture
In our architecture diagram, the calling of C/C ++ is as follows:
Structure of mutual calls between Java & C/C ++
The following figure shows the architecture of all calls between C/C ++ and Java:
Download the sample source code: Click Here.
Thank you for taking the time to read this article.
(Note: BogDan Vatra is really super nice. It provides so many diagrams that it is too clear to call each other between Java <-> C ++ .)
My articles on 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
I have activated the subscription number"Program horizon", Follow the instructions to see my original articles and the wonderful articles I recommend in the first place: