このガイドでは、Unity アプリケーションからプライベート プロセスで Android の知覚可能サービス(フォアグラウンド サービス、FGS)を実行するために必要な構成と実装について説明します。
1. 知覚可能サービスのサポートを構成する
このセクションでは、必要な権限を設定し、プロジェクトのマニフェストでサービスを宣言する方法について説明します。
1.1 権限とサービスの宣言
カスタム
Assets/Plugins/Android/AndroidManifest.xml
は、ランチャー アクティビティ、知覚可能サービスの権限、
通知権限、ネットワーク権限、サービスを宣言する必要があります。
<?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呼び出しと一致する必要があります。これら 3 つは一貫している必要があります。そうでない場合、サービスの起動は
失敗します。
1.2 サービス プロセス Java を Unity プロジェクトに追加する
FGS サービス プロセスは Java を実行します。サービス実装の Java コードを jar にパッケージ化し、 Assets/Plugins/Android/ に配置します。 Unity は、そのディレクトリ内の jar を Gradle の libs/ 入力に自動的に含め、APK にパッケージ化します。サービス プロセスは、これらのクラスを実行時に読み込みます。 Java または Kotlin でサービスを実装する方法について詳しくは、 Java の実装をご覧ください。
2. 別のプロセスを構成する
プロセス境界は、1 つのマニフェスト属性で有効になります。
android:process=":downloader"
先頭のコロンは、your.package.name:downloader という名前のアプリケーション専用プロセスを作成します。メインプロセスとは異なる PID を持ち、メインプロセスが終了した後も存続できます。
2.1 サービス プロセスに Unity がない
Unity の libunity.so、IL2CPP ランタイムなどをサービス プロセスに含めないようにするには、サービス側の Java で Unity クラス(UnityPlayer.currentActivity を含む)を使用せず、Android Context、Intent エクストラ、プラットフォーム API のみに依存する必要があります。Unity エンジン ランタイムは、メインプロセスの UnityPlayerActivity を介してのみ読み込まれます。プライベート :downloader プロセスはこれらのライブラリを読み込まないため、Unity クラスを参照してもサービス プロセスでは機能しません。
2.2 プロセスの起動エントリ ポイント
メインプロセスが startForegroundService を呼び出すと、システムは :downloader サービス プロセスをフォークします。
Applicationをインスタンス化してApplication.onCreateを呼び出します。これは すべてのプロセスで実行されるため、サービス プロセス に必要な初期化をここで行う必要があります。詳細については、2.3 をご覧ください。DownloadServiceを作成してonCreateを呼び出します。これがサービス プロセスのエントリ ポイントです。- 起動時に作成された Intent で
onStartCommandをコールバックします。サービスはここでフォアグラウンドに昇格し、ワーカーを開始します。 詳細については、3.1 をご覧ください。
2.3 プロセスごとに 1 つの状態が存在する
Dex コードは読み取り専用で共有されますが、ランタイム状態は共有されません。
Application.attachBaseContextとApplication.onCreateは、アプリケーション コンポーネントをホストするすべてのプロセスで実行されます。- 静的イニシャライザと静的フィールドは、各プロセスに個別に存在します。 メインプロセスで静的フィールドを割り当てても、サービスと通信しません。
- Unity、C#、アクティビティはメインプロセスに残ります。
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# ラッパーです。3 つのメソッドの実装は次のとおりです。
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 は、FgsBridge クラスを解決して対応する Java 静的メソッドを呼び出す内部 JNI ヘルパーです。RequestNotificationPermission
は、サービス プロセスの開始時に通知がすぐに表示されるように、アプリケーションの起動時に呼び出す必要があります。