Android服务

从Qt 5.7版本开始,您可以使用Qt创建Android服务。服务是一个在后台运行组件,因此没有用户界面。它可以执行长时间操作,如记录GPS、等待社交媒体通知等。即使启动它的应用程序退出,服务也将继续运行。

组装服务

要开始使用,按照在Qt Creator:将应用程序部署到Android设备中的说明创建一个Android包目录。此目录包含AndroidManifest.xml文件。在包目录内,创建一个src目录,您的所有Java包和类都将在该目录下创建。

创建服务类

您可以通过扩展类QtServiceAndroid: Service到您的Java类中来创建服务。根据您是否想在服务中使用Qt功能或从Java调用原生C++函数,您必须扩展QtServiceService。让我们从一个简单的服务开始,如下所示

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

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int ret = super.onStartCommand(intent, flags, startId);

        // Do some work

        return ret;
    }
}

启动服务

Android允许在需要时或启动时启动服务。您也可以使用Qt做这两项。

按需启动服务

您可以通过以下方式启动服务

  • 直接使用QAndroidIntentQJniObject从C++启动,通过创建服务Intent并调用应用程序主活动方法startService()
    auto activity = QJniObject(QNativeInterface::QAndroidApplication::context());
    QAndroidIntent serviceIntent(activity.object(),
                                 "org/qtproject/example/qtandroidservice/QtAndroidService");
    QJniObject result = activity.callObjectMethod(
                "startService",
                "(Landroid/content/Intent;)Landroid/content/ComponentName;",
                serviceIntent.handle().object());
  • 通过调用Java方法启动服务。最简单的方法是在您的服务类中创建一个静态方法
    public static void startQtAndroidService(Context context) {
            context.startService(new Intent(context, QtAndroidService.class));
    }

    然后您可以使用以下JNI调用从C++调用它

    QJniObject::callStaticMethod<void>(
        "org/qtproject/example/qtandroidservice/QtAndroidService",
        "startQtAndroidService",
        "(Landroid/content/Context;)V",
        QtAndroid::androidActivity().object());

在启动时启动服务

要启动时运行服务,您需要一个BroadcastReceiver

创建自定义Java类

public class QtBootServiceBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent startServiceIntent = new Intent(context, QtAndroidService.class);
        context.startService(startServiceIntent);
    }
}

AndroidManifest.xml文件的<manifest>部分正文中添加以下uses-permission

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

同时,在<application>部分的正文内添加receiver定义

<receiver android:name=".QtBootServiceBroadcastReceiver" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

注意:Android 8.0引入了对运行后台服务的某些限制,这意味着使用普通的Service类可能不起作用。有关更多信息,请参阅Android建议使用Foreground servicesJobIntentService

在AndroidManifest.xml中管理服务

要在Android应用中使用此服务,您必须在AndroidManifest.xml文件中声明它。让我们从添加服务部分开始

  • 在扩展Service时,只需将服务部分声明为标准的Android服务。在<application>部分内添加以下内容
    <service android:name=".QtAndroidService" android:exported="true">
        <!-- Background running -->
        <meta-data android:name="android.app.background_running" android:value="true"/>
        <!-- Background running -->
    </service>

    这样,服务将以和QtActivity相同的进程启动,这允许您从Java代码中使用原生C++调用。您可以将它运行在独立进程中,但那样您无法使用任何原生调用进行通信,因为对于该进程Qt库没有被加载。要在独立进程中运行,将以下内容添加到服务标签中

    android:process=":qt_service"
  • 在扩展QtService时,您需要声明其他用于加载Qt所需的所有库的条目,主要是与<activity>部分中相同的内容,用于QtActivity。添加以下内容
    <service android:process=":qt_service" android:name=".QtAndroidService" android:exported="true">
        <meta-data android:name="android.app.lib_name" android:value="service"/>
        <meta-data android:name="android.app.background_running" android:value="true"/>
    </service>

注意: 请确保以下定义,以在后台运行服务

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

声明服务的方式有几种。其中一些已在先前的清单片段中使用了。根据您的用例,可以将服务运行在与QtActivity相同的进程中,或在独立进程中。

与QtActivity相同进程中的服务

要在与QtActivity相同的进程中运行服务,按照以下方式声明服务头

<service android:name=".QtAndroidService" android:exported="true">

在独立进程中的服务

要在专用进程中运行服务,按照以下方式声明服务头

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

Qt会加载在android.app.lib_name meta-data中定义的.so文件,并通过所有在android.app.arguments meta-data中设置的参数调用main()函数。如果在独立进程中运行,您可以使用主要活动的相同lib文件或单独的lib文件来启动服务。

使用相同的.so库文件

使用主要活动相同的.so库文件意味着服务将使用相同的入口点,并通过额外的参数来区分与主要活动。您可以根据提供的参数处理您的应用程序执行。将以下参数声明添加到服务主体中

<!-- Application arguments -->
<meta-data android:name="android.app.arguments" android:value="-service"/>
<!-- Application arguments -->

然后确保服务的android.app.lib_name与主要活动相同,添加以下内容

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

当使用相同的.so库文件时,您的应用程序的main()函数会执行两次,一次启动主要活动,第二次启动服务。因此,您必须根据提供的参数处理每次执行。实现这一点的一种方法如下

if (argc <= 1) {
    // code to handle main activity execution
} else if (argc > 1 && strcmp(argv[1], "-service") == 0) {
    qDebug() << "Service starting with from the same .so file";
    QAndroidService app(argc, argv);
    return app.exec();
} else {
    qWarning() << "Unrecognized command line argument";
    return -1;
}
使用单独的.so库文件

在这种情况下,您需要一个有lib模板的子项目,该模板为服务提供不同的可执行文件。一个示例项目.pro如下

TEMPLATE = lib
TARGET = service
CONFIG += dll
QT += core core-private

SOURCES += \
    service_main.cpp

HEADERS += servicemessenger.h

service_main.cpp中,您可能会有以下内容

#include <QDebug>
#include <QAndroidService>

int main(int argc, char *argv[])
{
    qWarning() << "Service starting from a separate .so file";
    QAndroidService app(argc, argv);

    return app.exec();
}

AndroidManifest.xml中为服务定义android.app.lib_name

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

与服务通信

Qt for Android提供各种进程间通信(IPC)方法来与Android服务通信。根据您的项目结构,您可以使用从Java服务执行的原生C++调用或Android BroadcastReceiver。

从Java服务中调用原生C++

这可以与运行在与QtActivity相同的进程中的服务以及扩展Service的服务一起工作。

获取更多信息,请参阅Qt Android Notifier 示例

使用Android BroadcastReceiver

Android BroadcastReceiver 允许在Android系统、应用程序、活动和服务之间交换消息。类似于其他Android功能,Qt可以使用广播接收器来在 QtActivity 和你的服务之间交换消息。让我们从你的服务中发送消息的逻辑开始。在你的服务实现中添加以下内容,这将调用 sendBroadcast()

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    int ret = super.onStartCommand(intent, flags, startId);

    Intent sendToUiIntent = new Intent();
    sendToUiIntent.setAction(ActivityUtils.BROADCAST_CUSTOM_ACTION);
    sendToUiIntent.putExtra("message", "simple_string");

    Log.i(TAG, "Service sending broadcast");
    sendBroadcast(sendToUiIntent);

    return ret;
}

然后,您需要从Qt的主要活动创建并注册广播接收器。最简单的方法是创建一个具有方法的自定义类,并在Java中实现所有这些逻辑。在以下示例中,服务通过调用本地方法 sendToQt() 向Qt发送消息 "simple_string"

public class ServiceBroadcastUtils {

    private static native void sendToQt(String message);

    private static final String TAG = "ActivityUtils";
    public static final String BROADCAST_CUSTOM_ACTION = "org.qtproject.example.qtandroidservice.broadcast.custom";

    public void registerServiceBroadcastReceiver(Context context) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BROADCAST_CUSTOM_ACTION);
        context.registerReceiver(serviceMessageReceiver, intentFilter);
        Log.i(TAG, "Registered broadcast receiver");
    }

    private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "In OnReceive()");
            if (BROADCAST_CUSTOM_ACTION.equals(intent.getAction())) {
                String message = intent.getStringExtra("message");
                sendToQt(data);
                Log.i(TAG, "Service sent back message to C++: " + message);
            }
        }
    };
}

要使用所有这些功能,请按启动服务中所示启动您的服务,然后通过调用方法 registerServiceBroadcastReceiver() 注册广播接收器

QJniEnvironment env;
jclass javaClass = env.findClass("org/qtproject/example/qtandroidservice/ActivityUtils");
QJniObject classObject(javaClass);

classObject.callMethod<void>("registerServiceBroadcastReceiver",
                             "(Landroid/content/Context;)V",
                             QtAndroid::androidContext().object());

使用Qt远程对象

Qt远程对象 提供了一种轻松的方法来在Qt进程之间共享API。主要概念是在服务进程中设置服务器,并在Qt应用程序中有一个副本,然后这两个部分能够通过信号和槽相互交换数据。

准备副本

让我们考虑一个具有单独 .so lib 文件的服务示例。定义一个 .rep 文件,它定义了我们的通信类

class ServiceMessenger {
    SLOT(void ping(const QString &message));
    SIGNAL(pong(const QString &message));
}

然后,在服务子项目中以 servicemessenger.h 的形式定义这个类

#include "rep_servicemessenger_source.h"

class ServiceMessenger : public ServiceMessengerSource {
public slots:
    void ping(const QString &name) override {
        emit pong("Hello " + name);
    }
};

然后,将 .rep 文件添加到主应用程序和服务的 .pro 文件中,在主应用程序中

QT += remoteobjects
REPC_REPLICA += servicemessenger.rep

并在服务子项目中

QT += remoteobjects
REPC_SOURCE += servicemessenger.rep

连接源和副本

在服务子项目的 main() 函数中定义 Qt 远程对象源节点

#include "servicemessenger.h"

#include <QDebug>
#include <QAndroidService>

int main(int argc, char *argv[])
{
    qWarning() << "QtAndroidService starting from separate .so";
    QAndroidService app(argc, argv);

    QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));
    ServiceMessenger serviceMessenger;
    srcNode.enableRemoting(&serviceMessenger);

    return app.exec();
}

然后,在应用程序的 main() 函数中连接到源节点

QRemoteObjectNode repNode;
repNode.connectToNode(QUrl(QStringLiteral("local:replica")));
QSharedPointer<ServiceMessengerReplica> rep(repNode.acquire<ServiceMessengerReplica>());
bool res = rep->waitForSource();
Q_ASSERT(res);

QObject::connect(rep.data(), &ServiceMessengerReplica::pong, [](const QString &message){
    qDebug() << "Service sent: " << message;
});
rep->ping("Qt and Android are friends!");

此示例将来自主应用程序进程的消息发送到服务。服务以相同的信息回复,并在调试日志中打印出来。

注意: 当在一个进程中运行多个服务时,Qt for Android 具有限制,只允许执行一个服务。因此,建议每个服务运行在其自己的进程中。有关更多信息,请参阅 QTBUG-78009

使用 QAndroidBinder

QAndroidBinder 是一个便利类,通过实现Android: Binder 中最重要的方法来启用进程间通信。它允许在进程之间发送 QByteArrayQVariant 对象。

注意: Qt for Android 在运行时强制单个进程只能执行一个服务,当在一个进程中运行多个服务时。因此,建议每个服务运行在其自己的进程中。有关更多信息,请参阅 QTBUG-78009

© 2024 Qt公司有限有限公司。本文档中包含的贡献文档的版权属于各自的拥有者。本文档受自由软件基金会发布的GNU自由文档许可证1.3版的条款所许可。Qt及其相关标志是芬兰及全球其他国家Qt公司的商标。所有其他商标属于其各自所有者。