本指南介绍了在 Unity 应用中运行 Android 可感知服务(前台服务或 FGS)所需的配置和实现。
1. 配置可感知服务支持
本部分介绍如何在项目的清单中设置所需权限并声明服务。
1.1 权限和服务声明
自定义 Assets/Plugins/Android/AndroidManifest.xml 必须声明启动器 activity、可感知服务权限、通知权限、网络权限和服务:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<activity
android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<service
android:name="com.sample.fgs.DownloadService"
android:process=":downloader"
android:exported="false"
android:stopWithTask="false"
android:foregroundServiceType="dataSync" />
</application>
</manifest>
foregroundServiceType必须与实际工作相匹配:dataSync 表示转移,mediaPlayback 表示播放,location 表示位置信息跟踪。它必须与相应的 FOREGROUND_SERVICE_<TYPE> 权限和 startForeground 调用保持一致,三者必须保持一致,否则服务启动会失败。
1.2 将服务进程 Java 添加到 Unity 项目
FGS 服务进程运行 Java。将服务实现 Java 代码打包到 jar 中,并将其放置在 Assets/Plugins/Android/ 下。Unity 会自动将相应目录中的 jar 文件纳入 Gradle 的 libs/输入中,并将其打包到 APK 中;服务进程会在运行时加载这些类。 如需详细了解如何使用 Java 或 Kotlin 实现该服务,请参阅 Java 实现。
2. 配置单独的流程
进程边界由一个清单属性启用:
android:process=":downloader"
开头的冒号会创建一个名为 your.package.name:downloader 的应用专用进程。它具有与主进程不同的 PID,并且可以在主进程终止后继续运行。
2.1 服务进程没有 Unity
为避免将 Unity 的 libunity.so、IL2CPP 运行时和类似内容引入服务进程,服务端 Java 不应使用任何 Unity 类(包括 UnityPlayer.currentActivity),并且应仅依赖于 Android Context、Intent extra 和平台 API。Unity 引擎运行时仅通过主进程的 UnityPlayerActivity 加载;私有 :downloader 进程不加载这些库,因此在服务进程中引用 Unity 类不起作用。
2.2 进程启动入口点
在主进程调用 startForegroundService 后,系统会派生 :downloader 服务进程:
- 实例化
Application并调用Application.onCreate- 这在每个进程中运行,因此服务进程所需的初始化必须在此处重新完成。如需了解详情,请参阅第 2.3 节。 - 创建
DownloadService并调用onCreate- 这是服务进程入口点。 - 使用在启动时构建的 Intent 回调
onStartCommand。服务在此处将自身提升到前台并启动工作器。如需了解详情,请参阅第 3.1 部分。
2.3 每个进程只存在一次状态
Dex 代码是共享只读的,但运行时状态不是:
Application.attachBaseContext和Application.onCreate在托管应用组件的每个进程中运行。- 静态初始化程序和静态字段在每个进程中独立存在。 在主进程中分配静态字段不会与服务通信。
- Unity、C# 和 activity 仍位于主进程中。
3. Java 实现
本部分介绍 FGS 模块的 Java 端:3.1 和 3.2 是服务进程中的 DownloadService;3.3 是主进程中的 FgsBridge(使用 JNI 的入口点 C# 调用)。
3.1 先升级到前台
public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
startForegroundCompat();
// Start the download thread; must come after promoting to foreground
// ...
} catch (Exception e) {
Log.e(TAG, "onStartCommand() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
stopSelf();
}
return START_NOT_STICKY;
}
private void startForegroundCompat() {
Notification notification = buildNotification(0L);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
...
3.2 添加通知
可感知服务需要在通知渠道上显示持续性通知来报告进度。该通知包含一个停止操作,该操作使用 PendingIntent.getService 向服务本身发送 ACTION_STOP 以停止该服务。
private Notification buildNotification(long progressBytes) {
int progressMib = (int) (progressBytes / MIB);
Intent launchIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
PendingIntent contentIntent = launchIntent != null
? PendingIntent.getActivity(this, 0, launchIntent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)
: null;
Intent stopIntent = new Intent(this, DownloadService.class)
.setAction(ACTION_STOP);
PendingIntent stopPendingIntent = PendingIntent.getService(this, 0,
stopIntent, PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(this,
CHANNEL_ID)
.setContentTitle("Download service")
.setContentText("Downloaded " + progressMib + " MB / " + TOTAL_MIB + " MB")
.setSmallIcon(android.R.drawable.stat_sys_download)
.setProgress(TOTAL_MIB, progressMib, false)
.setOngoing(true)
.setOnlyAlertOnce(true)
.addAction(new Notification.Action.Builder(null,
"Stop", stopPendingIntent).build());
if (contentIntent != null) {
builder.setContentIntent(contentIntent);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setForegroundServiceBehavior(
Notification.FOREGROUND_SERVICE_IMMEDIATE);
}
return builder.build();
}
3.3 Java 主机入口点
FgsBridge 是主进程用于控制 FGS 的 Java 入口点(它在主进程中运行,而不是在服务进程中运行)。C# 使用 JNI 调用这些静态方法来启动和停止服务并处理通知权限:
public class FgsBridge {
// Start the :downloader perceptible service and begin downloading
public static void startDownloadService(Context context) {
try {
context.startForegroundService(new Intent(context, DownloadService.class));
} catch (Exception e) {
Log.e(TAG, "startDownloadService() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
}
}
// Stop the service: sends a stop intent; the service removes its
// notification before exiting
public static void stopDownloadService(Context context) {
try {
context.startService(new Intent(context, DownloadService.class)
.setAction(ACTION_STOP));
} catch (Exception e) {
Log.e(TAG, "stopDownloadService() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
}
}
// Request notification permission (only needed on API 33+; if already
// granted, no dialog is shown and it returns immediately)
public static void requestNotificationPermission(Activity activity) {
if (Build.VERSION.SDK_INT < 33) {
return;
}
try {
activity.requestPermissions(
new String[] { POST_NOTIFICATIONS }, NOTIFICATION_PERMISSION_REQUEST);
} catch (Exception e) {
Log.e(TAG, "requestNotificationPermission() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
}
}
}
4. 从 Unity (C#) 启动和控制 FGS
AndroidBridge 是使用 JNI 调用 FgsBridge 的 C# 封装容器。这三种方法的实现:
public static void StartDownloadService()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("startDownloadService");
#else
Debug.Log("StartDownloadService() no-op outside Android");
#endif
}
public static void StopDownloadService()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("stopDownloadService");
#else
Debug.Log("StopDownloadService() no-op outside Android");
#endif
}
public static void RequestNotificationPermission()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("requestNotificationPermission");
#else
Debug.Log("RequestNotificationPermission() no-op outside Android");
#endif
}
CallStatic 是一种内部 JNI 帮助程序,用于解析 FgsBridge 类并调用相应的 Java 静态方法。应在应用启动时调用 RequestNotificationPermission,以便在服务进程启动时立即显示通知。