Skip to content

Qt on Android Episode 7 How to access and use Android Java API using JNI in a safe way.

Update: Here you have also the Chinese version, thanks goes to Foruok.

In the last two Qt on Android episodes we learned how to use basic JNI on Android and how to use an external IDE to easily manage the Java part. In this episode, it is time to move forward and focus on extending our Qt on Android Java part and also how to interact with it using JNI in a “safe way”.

In this part we are going to implement an SD-Card listener. This is quite a useful example for applications that are using SD-Cards to store their data, because if the application doesn’t close all the opened files immediately when it gets the notification, it will be killed by the Android O.S.

As we’ve seen in Episode 5 it’s quite easy to call a Java method from C/C++ and a C/C++ function from Java, but it doesn’t work on all cases. But why not?

To understand why not, we need first to understand the Qt on Android architecture.

Architecture diagram:

Java-Qt

A few words about the architecture diagram.

  • the left blue rectangle represents the Android UI thread
  • the right green rectangle represents the main Qt thread (where the main QEventLoop is running). Read Episode 1 if you want to learn more about Android UI & Qt threads)
  • the top (black) rectangle is the Java part of your application. As you can see the biggest part of it runs on the Android UI thread. The only case when the Java part runs on the Qt thread is when we call it from C/C++ from Qt thread (as most of the JNI calls will come from there).
  • the bottom (black) rectangle is the C/C++ (Qt) part of your application. As you can see the biggest part of it runs on the Qt thread. The only case when the C/C++ part runs on the Android UI thread is when it’s called from the Java part from Android UI (as most of the Java callbacks will be from there).

Ok … so what’s the problem? Well, the problem is that there are SOME Android APIs that MUST be called from Android UI thread, and when we call a Java method from C/C++ we do it from Qt thread. It means that we need a way to run that code on Android UI not on Qt thread. To do such a call, from C/C++ Qt thread to Java Android UI thread, we need to do 3 steps:

  1. call a Java method from C/C++ Qt thread. The Java method will be executed in Qt thread, so we we need a way to access Android APIs in Android UI thread.
  2. our Java method uses Activity.runOnUiThread to post a runnable on Android UI thread. This runnable will be executed by the Android event loop on Android UI thread.
  3. the runnable accesses the Android APIs from Android UI thread.

The same problem occurs when Java calls a C/C++ function, because Java will call our C/C++ functions from Android UI and we need a way to pass that notification on Qt thread. Again there are 3 steps involved:

  1. call a C/C++ function from Android UI thread.
  2. use QMetaObject::invokeMethod to post a method call on Qt event loop.
  3. Qt event loop will execute that function on Qt thread.

Extending the Java part:

Before you start, make sure you read Episode 6 one more time because you’ll need it to easily manage the Java files. First step is to create a custom Activity by extending QtActivity and defining a method which will post our 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));
    }
}

Java-Qt_1-2

Next step is to change the default activity to AndroidManifest.xml, from:

<activity ...
        android:name="org.qtproject.qt5.android.bindings.QtActivity"
        ... >

to:

<activity ...
        android:name="com.kdab.training.MyActivity"
        ... >

We need to do this to make sure that our custom Activity will be instantiated when the application starts.

Next step is to define our RegisterReceiverRunnable class: The run method of this class will be called on Android UI thread. In run method we register our SDCardReceiver listener.

// 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);
    }
}

Java-Qt_3

Let’s check what SDCardReceiver class looks like:

// 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 notification
        if (intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED))
            NativeFunctions.onReceiveNativeMounted();
        else if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED))
            NativeFunctions.onReceiveNativeUnmounted();
    }
}

Java-Qt_4SDCardReceiver overrides onReceive method, then it uses the declared native functions to send the notification to C/C++.

Last step is to declare our native functions that we used in 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();
}

Architecture diagram Java:

Let’s see the summary of the Java part calls on our architecture diagram:

Java-Qt_Java_final

Extending C/C++ part:

Now let’s see how we extend the C/C++ part. To illustrate how to do it, I’m using a simple widget application.

First thing we need to do, is to call the registerBroadcastReceiver method.

// 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();
}

Java-Qt_1

 

// 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;
}

Java-Qt_4-5

In native.cpp we are registering the native functions. From our static native functions we are using QMetaObject::invokeMethod to post the slots call to Qt thread.

 

// 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"));
}

Java-Qt_6

MainWindow class is used just to add some text to our plainText control when it gets a notification. Calling these functions from Android thread might be very harmful to our application health – it might lead to crashes or unexpected behavior, so they MUST be called from Qt thread.

Architecture diagram C/C++:

This is the summary of C/C++ calls on our architecture diagram:

Java-Qt_Qt_final

Architecture diagram Java & C/C++:

This is the summary of all the calls that we’ve done in C/C++ and in Java.

Java-Qt_final

Here you can download the example source code.

Thank you for your time!

36 thoughts on “Qt on Android Episode 7”

    1. BogDan Vatra

      disclaimer: I read your mail very briefly.

      So, you want to create an application (viewer) that handles some file extensions? I’m pretty sure it is already possible, you just need to tweak a little bit your android manifest file, set APPLICATION_PARAMETERS [0] correctly in your custom activity, make sure your let the application to quit and that it. If you’ll control the intent then you can pass the params via *applicationArguments* stringExtras key [1].

      [0] http://code.qt.io/cgit/qt/qtbase.git/tree/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java#n137
      [1] http://code.qt.io/cgit/qt/qtbase.git/tree/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java#n652

      1. Invoking it for the first time is not a problem, only minor QtActivity code modification is needed. But then when you switch to file manager and try to open another file – android will not quit the first instance of viewer, it will just restore it, with the first opened file. I was not able to open second file when the first one is already opened.

          1. Sorry, I meant QtNative, not QtActivity. I would contribute the fix upstream if I will be able to make it work as I wanted.

            I see your point about quitting, but it will affect the startup time on every new intent.

          2. BogDan Vatra

            If your fix doesn’t break anything else than you can upstream it.

            If you want to keep your application running than you need to send the params using JNI.

          3. I’ve solved this problem in another way. Instead of restarting the application I have set the activity with property android:launchMode=”singleTask”; this way the same, opened, instance is called when an intent is launched. I’ve also extended the QtActivity with the method “onNewIntent(Intent i)” in which I call “setIntent(i): this way the subsequent call to the extras() method will return the parameters contained in the new Intent, instead of the original intent which started the app. I’ve only ONE last issue: notify Qt of the new Intent. Guess this episode is what I’m looking for. Thanks BogDan! 🙂

  1. Hi, BogDan Vatra. In my practice, I tend to register natives by using my custom functions and let it run before QQmlApplicationEngine gets started rather than “JNI_OnLoad( )” function, and it also works.
    In fact I’m going to encapsulate some open authentication platforms into my plugin and expose some informations to QML environments, registering methods within “QQmlExtensionPlugin::registerTypes( )” function seems a good practice.

    1. BogDan Vatra

      It doesn’t matter where you register them as long as you’ll register them *before* the java part calls them.

      *JNI_OnLoad( )* gives us everything we need to register them (JavaVM pointer), that’s why I’m using it and that is the reason why I’m telling people to use it.

    1. BogDan Vatra

      HUH ?!?! The idea is to run some code on Android UI thread.
      AsyncTask is used for something else. It’s usefull when you want to do some heavy work in background and publish the results on Android UI thread. I don’t see how AsyncTask is better than the humble Runnable and Activity.runOnUiThread for what we need…
      Of course AsyncTask might be useful for other examples, but for the example in the article is useless.

  2. What about the solution on creating instance in C++ by JNI whereas it affiances to Android UI thread. subclass Activity and write a new clause is a solution, but I don’t want to disturb QtActivity.

    1. BogDan Vatra

      I think I fail to follow you 🙂 .

      You want to create a new Activity from JNI? If the activity is not declared in your manifest file, you can’t create a new one at runtime.

      Also the main Activity and Qt needs separate threads because they have their own event loops and they can’t leave togheter into a single thread (check http://www.kdab.com/qt-on-android-episode-1 for more info on this matter).

  3. Hello ,I have a “already working ” Qt application ,an instant messaging one ,it uses QXmpp and I am porting it to android.

    The problem I am facing is that when the application is inactive for some time it is killed by the system .I tried to remedy on this by writting an android service using (asmack) and handling received messages and opening my application again and pass in the received messages .

    This is starting to look like a bad design as I have to log off from the server and let the service take control every time the user minimises the app or when the screen goes black.

    I was wondering if there is a better way of hanling “long running operations on android (Qt) or should I just offload the Instant messaging details to the background service.

    Thank you.

    1. BogDan Vatra

      Using a service is the Android way to do long term background operations.
      So, IMHO you should create a service which processes the data, post notifications and start the Qt UI using an Intent if needed.

  4. Hello Bodgan, thank you for your job (yes, I’m really impressed!).

    I have a question on how the Android UI thread and the Qt thread relate to the process running the Android app, in particular when it comes to acquire a WakeLock.

    While developing a GPS app, I ended up writing an example that shows how, if holding a PARTIAL_WAKE_LOCK, the QtActivity keeps working also in the background, as expected, while the Qt thread goes to sleep.

    How is that possible? Do Android WakeLocks, as it seems, get effect at a thread level?

    Here is an excerpt from my sample:

    *** java ***

    public class UtyActivity extends QtActivity
    {
    private PowerManager.WakeLock m_wakelock;
    Timer m_timer;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);

    // starts Android UI task
    m_timer = new Timer();
    DebugTask task = new DebugTask();
    m_timer.schedule(task, 1, 10000);

    // acquire a partial WakeLock here (not working using JNI from Qt)
    // NB: We are in the Android UI thread here, aren’t we?
    // (anyway I also tryed using runOnUiThread)
    Context context = getApplicationContext();
    PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    m_wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, “com.utillyty.android.wakelock”);
    m_wakelock.acquire();
    }

    // —> THIS KEEPS WORKING ALSO IN BACKGROUND
    class DebugTask extends TimerTask
    {
    public void run()
    {
    // http get on test server: the request is always delivered

    ========== C++ ==========

    // —> THIS BLOCKS WHILE APP IN THE BACKGROUND
    void MainPanel::timerEvent(QTimerEvent *)
    {
    // same http get as java: no request is delivered while in the background

    1. BogDan Vatra

      By default the main QEventLoop is freezed when it goes in background. We need to do it because the surface that Qt needs to paint to is not available anymore and if you try to paint something your application might crash.

      There are two ways to keep going on Qt world after it goes in background:
      – create a new thread , call QThread::exec() then create all the timers and other QObjects that you need the in that thread.
      – don’t freeze the Qt main loop. You need to edit AndroidManifest.xml and set “android.app.background_running” to “true”, but you must make sure you don’t draw anything when the application is backgrounded.

      1. It works great!
        If I need to draw anything when the app may be running in the background, I just check if “isVisible()” is “false” and if so, store the data in memory and do the job in the “showEvent()” function.
        Thanks a lot!

  5. Hello, I need to control device orientation, both iOS and Android from qt5.6 and c++. What’s the best way

  6. Hi, it is the best articles about qt on android that i’ve found. But it’s didn’t answer on my questions 🙁
    Here is my problem:
    1) i’m trying to write soft keyboard using qt.
    2) android use android.view.View to draw keyboard
    3) so i need to use View to draw qt interface, but standard way to draw qt on android is QtActivity.
    4) I’ve found a way to do that, by extendinng View, with custom onDraw() that will get bitmap of qt interface by grabWindow() and put it into view.

    5) Do you think it’s normal solution? Does anyone have any better ideas?

    Thanks anyway, you did a great job.

    1. BogDan Vatra

      I think you’re wrong, “standard” way to draw qt is by using a [Qt]SurfaceView not the QtActivity.
      QtActivity is used just to forward all the events to Qt world.
      I never tried to write an android keyboard so, I don’t know all the details, but using grabWindow() doesn’t seem the best solution…
      If you can add a single SurfaceView to android.view.View that (apparently) is needed to draw the keyboard, then Qt can use it to draw its stuff too.

  7. Hi,

    I have a little question about this article and signals.
    Shoud I always use the QMetaObject::InvokeMethod or can I call the Q_EMIT directly?

    static void notifyXXXX(JNIEnv *env, jobject, jlong id)
    {
    {
    JData *const mp = mDataObjects[id]; jData*const mp = mDataObjects[id];
    if (mp) {
    Q_EMIT mp->modelChanged(mp->model());
    }
    }

    OR

    static void notifyXXXX(JNIEnv *env, jobject, jlong id)
    {
    {
    JData *const mp = mDataObjects[id]; jData*const mp = mDataObjects[id];
    if (mp) {
    QMetaObject::invokeMethod(mp, “handleDataChanged”);
    }
    }

    void JData::handleDataChanged(){
    Q_EMIT modelChanged(model());
    }

    1. BogDan Vatra

      I don’t think Q_EMIT (which is the same thing as emit) will delegate the call to callee thread.


      static void notifyXXXX(JNIEnv *env, jobject, jlong id)
      {
      JData *const mp = mDataObjects[id]; jData*const mp = mDataObjects[id];
      if (mp)
      QMetaObject::invokeMethod(mp, “callTheSignalMethodDirectly”);
      }

  8. Hi BogDan,

    thanks very much to sharing your knowledge about Qt/Android with us.
    I have read your Android/Qt article and watched some of your presentation at Qt Con.
    I have developed a quit simple Qt/Android application, which is using the Camera and some other features of my Smartphone.
    The application is working well when I use “Bundle Qt libraries in APK” but when I try “Use Ministro service to install Qt”. Nothing works!

    My Application uses some JNI functions, so I have create JNI_OnLoad() function as your show in your presentation at Qt Con 2016. But when I start it I’ve got following error:
    E/art ( 8451): No implementation found for void com.geocept.android.NativeFunctions.onWifiStateChanged(boolean) (tried Java_com_geocept_android_NativeFunctions_onWifiStateChanged and Java_com_geocept_android_NativeFunctions_onWifiStateChanged__Z)

    This only happens when I generate the APK with “Use Ministro service to install Qt”.
    Why?

    Can you help me?

      1. Here my configuration:
        – Qt 5.7.1 / Android armeabi-v7
        – Android NDK r13b
        – Qt Creator v4.2.0
        – Workstation: Windows 7 pro/64 bit
        – Android Lollipop
        – Android API level used: 21

          1. Thanks for your answer,
            Do you have a deadline for this update?
            Before end of this week (before Xmas) or next year?

            I have to inform customer about software availability.

          2. Hi BogDan,

            in the meantime, I found a quick&dirty solution which “WorksForMe(tm)”.
            To save data space on APK transfer to the device, I have create a full APK.
            On this APK I have extract all Qt specific stuff.
            Those files are transferred into device internal storage (backup copy).
            For every next APK release, I remove Qt stuff from APK.
            Send this small APK (less than 2 MB) to the device, on device side a insert remove Qt stuff to complete the APK then start a “normal” APK install on Android.

            It is very dirty but it works for my needs.

            Perhaps, you can provide an “extended” version of Ministro, with a kind of “Off-Line” or “Static” mode in which it is possible to setup a device with specified Qt version, for example downloaded from Qt repository. The transfer this version on device.
            So all APK generated for this device do not need any Qt dll/qml/whatever. This can be useful for a business solution.
            I don’t think this will be so much changes on Ministro side.
            What do you think about this?

            Regards

            Fabrice

  9. just wandering isn’t possible to make the “static void onReceiveNativeMounted” inside Mainwindow’s class so it will be “static void Mainwindow::onReceiveNativeMounted” and then call ui->plainTextEdit…. directly from it ? this could be more elegant i guess ?…
    imagine if we have many many functions to pass with it data from java to C++, making statics functions along side with the same number of slots will rise the ability of maintaining the code …

    1. BogDan Vatra

      Calling a static method (from your class) or a static function it doesn’t change the thread from it’s called.
      So “Mainwindow::onReceiveNativeMounted” is called from Android UI thread no matter what and trying to call “ui->plainTextEdit” directly (though you can’t use it *directly* because “ui” is not accessible from a static method) will not automatically switch the threads and it will lead to a crash (sooner or later).

Leave a Reply

Your email address will not be published. Required fields are marked *