Android Service with QAndroidBinder

Demonstrates how to run an Android service in a separate process, and how to communicate between the service process and the main process using QAndroidBinder.

This example demonstrates how to create and run an Android service in a separate process and using a separate .so lib file, and then exchange data between the two processes using QAndroidBinder.

When clicking the Send to Service button, the name entered in the QML view, Qt, in this case, is sent to the Android service. Then, the service replies back with the message Hello Qt which is printed in the QML view.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.

Create the Service

To start a service in its own process, extend the QtService class for your service. Extending QtService allows the service to load the necessary Qt libraries used for Qt.

Start by creating the Java service class. The following class extends QtService and acts as your service entry point:

package org.qtproject.example.qtandroidservice;

import android.content.Context;
import android.content.Intent;
import org.qtproject.qt5.android.bindings.QtService;
import android.util.Log;

public class QtAndroidService extends QtService {

    private static final String TAG = "QtAndroidService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Creating Service");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Destroying Service");
    }
}

This class can have any logic you want using Java code. However, you don't need any logic to communicate with Qt as that will be done using QAndroidBinder.

Manage the AndroidManifest.xml File

To use the service, it must be declared in the AndroidManifest.xml file as follows:

<service android:process=":qt_service" android:name=".QtAndroidService">

        <meta-data android:name="android.app.lib_name" android:value="service"/>
        <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
        <meta-data android:name="android.app.repository" android:value="default"/>
        <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
        <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
        <!-- Deploy Qt libs as part of package -->
        <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>

        <!-- Run with local libs -->
        <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
        <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
        <meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
        <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
        <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
        <!-- Run with local libs -->

        <!-- Background running -->
        <meta-data android:name="android.app.background_running" android:value="true"/>
        <!-- Background running -->
    </service>

The important part of this service declaration is the lib_name part. It will ensure that the service is run by the service's own lib file:

        <meta-data android:name="android.app.lib_name" android:value="service"/>

Handle the Service Start

Create a sub-project for the service, as follows:

TEMPLATE = lib
TARGET = service
CONFIG += dll
QT += core androidextras

HEADERS += androidbinder.h

SOURCES += \
    service_main.cpp \
    androidbinder.cpp

In androidbinder.cpp, implement a class that inherits QAndroidBinder. This is the binder that the main application will use to connect to the service by binding to it. QAndroidBinder::onTransact() uses a code integer to differentiate between actions. Use a switch case or if conditions to handle all expected actions that the binder could expect:

bool AndroidBinder::onTransact(int code,
                               const QAndroidParcel &data,
                               const QAndroidParcel &reply,
                               QAndroidBinder::CallType flags)
{
    qDebug() << TAG << ": onTransact(), code " << code << ", flags " << int(flags);

    switch (code) {
    case 1: {
        QAndroidBinder binder = data.readBinder();

        const QString name(data.readData());
        qDebug() << TAG << ": onTransact() received name " << name;

        reply.writeVariant("Hello " + name);
    } break;
    default:
        QAndroidBinder binder = data.readBinder();

        qDebug() << TAG << ": onTransact() received non-name data" << data.readVariant();
        reply.writeVariant(QVariant("Cannot process this!"));

        // send back message
        QAndroidParcel sendData, replyData;
        sendData.writeVariant(QVariant("Send me only names!"));
        binder.transact(0, sendData, &replyData);
        qDebug() << TAG << ": onTransact() received " << replyData.readData();

        break;
    }
    return true;
}

In the service's main(), start the QAndroidBinder along with QAndroidService:

int main(int argc, char *argv[])
{
    qDebug() << "Starting service process from C++";
    QAndroidService app(argc, argv, [](const QAndroidIntent &) {
        qDebug() << "Android service onBind()";
        return new AndroidBinder{};
    });

    return app.exec();
}

Handle the Application Start

In the main application side, a QAndroidServiceConnection implementation is required to bind to the service and exchange data with it. Implement the functions QAndroidServiceConnection::onServiceConnected() and QAndroidServiceConnection::onServiceDisconnected():

void AndroidServiceConnection::onServiceConnected(const QString &name,
                                                  const QAndroidBinder &serviceBinder)
{
    qDebug() << TAG << ": onServiceConnected() " << name;
    m_servieBinder = serviceBinder;
}

void AndroidServiceConnection::onServiceDisconnected(const QString &name)
{
    qDebug() << TAG << ": onServiceDisconnected() " << name;
}

Then, create a function to explicitly send messages to the service:

void AndroidServiceConnection::sendToService(const QString &name)
{
    // send name
    QAndroidParcel sendData, replyData;
    sendData.writeBinder(m_servieBinder);
    sendData.writeData(name.toUtf8());
    m_servieBinder.transact(1, sendData, &replyData);
    const QVariant received(replyData.readVariant());
    qDebug() << received;
    emit QtAndroidService::instance()->messageFromService(received.toString());
}

Once you have all that ready, it's time to start the service and bind to it as follows:

QtAndroidService::QtAndroidService(QObject *parent) : QObject(parent)
{
    m_instance = this;

    QAndroidIntent serviceIntent(QtAndroid::androidActivity(),
                                 "org.qtproject.example.qtandroidservice.QtAndroidService");

    const bool bindResult = QtAndroid::bindService(serviceIntent,
                                                   m_serviceConnection,
                                                   QtAndroid::BindFlag::AutoCreate);
    qDebug() << "Binding to the service..." << bindResult;
}

The QtAndroid::bindService() is called using QtAndroid::AutoCreate which starts the service if it's not already running.

Note: To receive data explicitly sent from the service (i.e. not just a reply), implement QAndroidBinder in the main application the same way it's done on the service. Once you have that, the service could initially send a message.

Then, create an instance for the custom QAndroidServiceConnection class and connect it to QML. Add the following in main.cpp:

    QtAndroidService *qtAndroidService = new QtAndroidService(&app);
    engine.rootContext()->setContextProperty(QLatin1String("qtAndroidService"), qtAndroidService);

Then, add a Connections element to watch for the incoming messages from the service in main.qml:

    Connections {
        target: qtAndroidService
        function onMessageFromService(message) {
            pongText.text = message
        }
    }

And set the onClicked for the sending button to:

        onClicked: qtAndroidService.sendToService(pingText.text)

Example project @ code.qt.io

See also Android Services, Qt for Android, and Qt Android Extras.

© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.