Starting with Qt 5.7, we added the ability to create Android services using Qt.
In this article we’re going to see how to get started and also how to communicate between the two.
Before we get started I want to add a big bold WARNING about the performance! Because the services are run in the background for a very long time, make sure your service doesn’t drain the device battery!
Getting started
Step I: Extend QtService
Every single Qt Android Service must have its own Service java class which extends QtService, so the first step is to create such a service:
// java file goes in android/src/com/kdab/training/MyService.java package com.kdab.training; import org.qtproject.qt5.android.bindings.QtService; public class MyService extends QtService { }
Step II: Add the service section(s) to your AndroidManifest.xml file
The next step is to add the service section(s) to your AndroidManifest.xml file. To do that you first need to copy & paste the template from https://wiki.qt.io/AndroidServices to your AndroidManifest.xml file, then set android:name attribute with your service class name, as shown in the following snippet:
<application ... > <!-- .... --> <service android:process=":qt" android:name=".MyService"> <!-- android:process=":qt" is needed to force the service to run on a separate process than the Activity --> <!-- .... --> <!-- Background running --> <meta-data android:name="android.app.background_running" android:value="true"/> <!-- Background running --> </service> <!-- .... --> </application>
BE AWARE: Every single Qt service/activity MUST run in it’s own process! Therefore for each service you must set a different android:process attribute value.
Step III: How to start the service ?
Now you need to decide how to start the service. There are two ways to do it:
- on demand
- at boot time
We’re going to check them both:
Start the service on demand
This is the most common way to start your service(s). To start the service you just need to call Context.startService(Intent intent) method.
The easiest way is to add a static method to your MyService:
// java file goes in android/src/com/kdab/training/MyService.java package com.kdab.training; import android.content.Context; import android.content.Intent; import org.qtproject.qt5.android.bindings.QtService; public class MyService extends QtService { public static void startMyService(Context ctx) { ctx.startService(new Intent(ctx, MyService.class)); } }
Then simply call it from Qt to start it:
QAndroidJniObject::callStaticMethod<void>("com/kdab/training/MyService", "startMyService", "(Landroid/content/Context;)V", QtAndroid::androidActivity().object());
Start the service at boot time
This method is used quite seldom and is useful ONLY when you really need to run the service at boot time, otherwise I do recommend you to start it on demand.
First you need to add android.permission.RECEIVE_BOOT_COMPLETED permission to your AndroidManifest.xml file:
<application ... > <!-- .... --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> </application>
Then you need to add a receiver element to your AndroidManifest.xml file:
<application ... > <!-- .... --> <receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> <!-- .... --> </application>
And finally, you need to implement MyBroadcastReceiver class, as shown in the following snippet:
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent startServiceIntent = new Intent(context, MyService.class); context.startService(startServiceIntent); } }
Step IV: Where to put your Qt Service code?
Next you need to decide where you’re going to put your service code. Qt (and qmake) has two options for you:
- in the same .so file with the application
- in a separate .so file
We’re going to check them both:
Same .so for app & service(s)
Because you’ll have one big .so file, you need a way to know when it will run as an activity or as a service. To do that you just need pass some arguments to your main function. AndroidManifest.xml allows you to easily do that:
<service ... > <!-- ... --> <!-- Application arguments --> <meta-data android:name="android.app.arguments" android:value="-service"/> <!-- Application arguments --> <!-- ... --> </service>
Then make sure you set the same android.app.lib_name metadata for both service(s) & activity elements:
<service ... > <!-- ... --> <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/> <!-- ... --> </service>
I recommend you to use this method only if your activity and your service(s) share a large piece of code.
Separate .so files for app & service(s)
The second option is to create separate .so files for your app & service(s).
First you need to create a separate server .pro file(s):
TEMPLATE = lib TARGET = server CONFIG += dll QT += core SOURCES += \ server.cpp
The server .so main entry is the main function:
#include <QDebug> int main(int argc, char *argv[]) { qDebug() << "Hello from service"; return 0 }
Last you need to load the server .so file:
<service ... > <!-- ... --> <meta-data android:name="android.app.lib_name" android:value="server"/> <!-- ... --> </service>
Use QtRemoteObject for communication
We’ve seen how to create and how to start a Qt on Android service, now let’s see how to do the communication between them.
There are lots of solutions out there, but for any Qt project, I do recommend you use QtRemoteObject, because it will make your life so easy!
QtRemoteObjects is a playground Qt module led by Ford, for object remoting between processes/devices:
- exports QObjects remotely (properties, signals & slots)
- exports QAbstractItemModels remotely
- creates a replicant on the client side you can interface with
- repc generates source & replica (server & client) source files from .rep files
- .rep file is the QtRemoteObjects IDL (interface description language)
As you can see it’s very Qt specific!
Let’s see how to add it to your projects and use it.
Get QtRemoteObjects
QtRemoteObjects project is located at http://code.qt.io/cgit/playground/qtremoteobjects.git/, to get it you need to run the following commands:
$ git clone git://code.qt.io/playground/qtremoteobjects.git $ cd qtremoteobjects $ ~/Qt/5.7/android_armv7/bin/qmake -r && make && make install
If needed, replace ~/Qt/5.7/android_armv7 with your Qt version and android ABI of choice.
Use QtRemoteObjects
Using QtRemoteObjects is pretty easy, you need to do a few easy steps:
– add QtRemoteObjects to your .pro files
# ... QT += remoteobjects # ...
– create .rep file(s)
class PingPong { SLOT(void ping(const QString &msg)); SIGNAL(pong(const QString &msg)); }
– add .rep file(s) to the server .pro file
# ... REPC_SOURCE += pingpong.rep # ...
– add .rep file(s) to the client .pro file
# ... REPC_REPLICA += pingpong.rep # ...
– QtRemoteObjects source(server) side implementation
#include <QCoreApplication>
#include "rep_pingpong_source.h"
class PingPong : public PingPongSource {
public slots:
// PingPongSource interface
void ping(const QString &msg) override {
emit pong(msg + " from server");
}
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));
PingPong pingPongServer;
srcNode.enableRemoting(&pingPongServer);
return app.exec();
}
#include <QCoreApplication> #include "rep_pingpong_source.h" class PingPong : public PingPongSource { public slots: // PingPongSource interface void ping(const QString &msg) override { emit pong(msg + " from server"); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica"))); PingPong pingPongServer; srcNode.enableRemoting(&pingPongServer); return app.exec(); }
Let’s check the code a little bit.
First you need to implement all .rep interfaces (PingPongSource), then export PingPong object using enableRemoting.
– QtRemoteObjects replica(client) side implementation
#include "rep_pingpong_replica.h"
// ....
QRemoteObjectNode repNode;
repNode.connectToNode(QUrl(QStringLiteral("local:replica")));
QSharedPointer<PingPongReplica> rep(repNode.acquire<PingPongReplica>());
bool res = rep->waitForSource();
Q_ASSERT(res);
QObject::connect(rep.data(), &PingPongReplica::pong, [](const QString &msg){
qDebug() << msg;
});
rep->ping("Hello");
// ....
#include "rep_pingpong_replica.h" // .... QRemoteObjectNode repNode; repNode.connectToNode(QUrl(QStringLiteral("local:replica"))); QSharedPointer<PingPongReplica> rep(repNode.acquire<PingPongReplica>()); bool res = rep->waitForSource(); Q_ASSERT(res); QObject::connect(rep.data(), &PingPongReplica::pong, [](const QString &msg){ qDebug() << msg; }); rep->ping("Hello"); // ....
Let’s check the code:
- use QRemoteObjectNode to connect to QRemoteObjectHost
- use QRemoteObjectNode:acquire to link the local object to the remote one
- use the acquired object as its local (call slots, connect to signals, etc.)
As you can see, using Qt + QtRemoteObject is (much?) easier and more straight forward than Android’s Java services + AIDL
Limitations
- the activities & service(s) must run on a different process.
- it is not possible (yet) to use QtCreator to easily add a service section to your AndroidManifest.xml file check QTCREATORBUG-16884
- it is not possible (yet) to use QtCreator to easily generate a service subproject for us, check QTCREATORBUG-16885
- it is not possible (yet) to see the services logs in QtCreator. You’ll need to use
$ adb logcat
to see it, check QTCREATORBUG-16887
- it is not possible (yet (hopefully)) to debug the services in QtCreator. This feature will take some time to implement it, therefore I’ll not hold my breath for it, check QTCREATORBUG-16886
Please use the above bug report links to vote for your favorite tasks, the ones that have more votes (usually) are implemented first!
You can find the full source code of this article here: https://github.com/KDAB/android
The post Qt on Android: How to create an Android service using Qt appeared first on KDAB.