Android服务
从Qt 5.7版本开始,您可以使用Qt创建Android服务。服务是一个在后台运行组件,因此没有用户界面。它可以执行长时间操作,如记录GPS、等待社交媒体通知等。即使启动它的应用程序退出,服务也将继续运行。
组装服务
要开始使用,按照在Qt Creator:将应用程序部署到Android设备中的说明创建一个Android包目录。此目录包含AndroidManifest.xml
文件。在包目录内,创建一个src
目录,您的所有Java包和类都将在该目录下创建。
创建服务类
您可以通过扩展类QtService
或Android: Service
到您的Java类中来创建服务。根据您是否想在服务中使用Qt功能或从Java调用原生C++函数,您必须扩展QtService
或Service
。让我们从一个简单的服务开始,如下所示
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做这两项。
按需启动服务
您可以通过以下方式启动服务
- 直接使用
QAndroidIntent
和QJniObject
从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 services
或JobIntentService
。
在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 中最重要的方法来启用进程间通信。它允许在进程之间发送 QByteArray 或 QVariant 对象。
注意: Qt for Android 在运行时强制单个进程只能执行一个服务,当在一个进程中运行多个服务时。因此,建议每个服务运行在其自己的进程中。有关更多信息,请参阅 QTBUG-78009。
© 2024 Qt公司有限有限公司。本文档中包含的贡献文档的版权属于各自的拥有者。本文档受自由软件基金会发布的GNU自由文档许可证1.3版的条款所许可。Qt及其相关标志是芬兰及全球其他国家Qt公司的商标。所有其他商标属于其各自所有者。