Wahrnehmbaren Dienst in einem separaten Prozess mit Unity ausführen

In dieser Anleitung wird die Konfiguration und Implementierung beschrieben, die erforderlich sind, um einen wahrnehmbaren Android-Dienst (Foreground Service, FGS) in einem privaten Prozess aus einer Unity-Anwendung heraus auszuführen.

1. Unterstützung für wahrnehmbare Dienste konfigurieren

In diesem Abschnitt wird erläutert, wie Sie die erforderlichen Berechtigungen einrichten und den Dienst im Manifest Ihres Projekts deklarieren.

1.1 Berechtigungen und Dienstdeklaration

Im benutzerdefinierten Assets/Plugins/Android/AndroidManifest.xml müssen die Launcher-Aktivität, die Berechtigungen für wahrnehmbare Dienste, die Berechtigung zum Senden von Benachrichtigungen, die Netzwerkberechtigung und der Dienst deklariert werden:

<?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 muss mit der tatsächlichen Arbeit übereinstimmen: dataSync für Übertragungen, mediaPlayback für die Wiedergabe und location für die Standortermittlung. Es muss mit der entsprechenden FOREGROUND_SERVICE_<TYPE> Berechtigung und dem startForeground Aufruf übereinstimmen. Alle drei müssen konsistent sein, andernfalls schlägt der Dienststart fehl.

1.2 Java für den Dienstprozess zum Unity-Projekt hinzufügen

Der FGS-Dienstprozess führt Java aus. Packen Sie den Java Code für die Dienstimplementierung in eine JAR-Datei und platzieren Sie sie unter Assets/Plugins/Android/. Unity fügt JAR-Dateien in diesem Verzeichnis automatisch in die Eingabe libs/ von Gradle ein und packt sie in die APK-Datei. Der Dienstprozess lädt diese Klassen zur Laufzeit. Weitere Informationen zum Implementieren des Dienstes in Java oder Kotlin, finden Sie unter Java-Implementierung.

2. Separaten Prozess konfigurieren

Die Prozessgrenze wird durch ein Manifestattribut aktiviert:

android:process=":downloader"

Der führende Doppelpunkt erstellt einen anwendungsprivaten Prozess namens your.package.name:downloader. Er hat eine andere PID als der Hauptprozess und kann auch nach Beendigung des Hauptprozesses weiterlaufen.

2.1 Der Dienstprozess hat kein Unity

Um zu vermeiden, dass libunity.so, die IL2CPP-Laufzeit und Ähnliches in den Dienstprozess aufgenommen werden, sollte Java auf Dienstseite keine Unity-Klassen verwenden (einschließlich UnityPlayer.currentActivity) und nur von Android-Kontext, Intent-Extras und Plattform-APIs abhängig sein. Die Unity-Engine-Laufzeit wird nur über UnityPlayerActivity des Hauptprozesses geladen. Der private Prozess :downloader lädt diese Bibliotheken nicht, daher funktioniert der Verweis auf Unity-Klassen im Dienstprozess nicht.

2.2 Einstiegspunkte für den Prozessstart

Nachdem der Hauptprozess startForegroundService aufgerufen hat, verzweigt das System den Dienstprozess :downloader:

  • Application wird instanziiert und Application.onCreate aufgerufen. Dies wird in jedem Prozess ausgeführt, daher muss die für den Dienstprozess erforderliche Initialisierung hier noch einmal durchgeführt werden. Weitere Informationen finden Sie im Abschnitt 2.3.
  • DownloadService wird erstellt und onCreate aufgerufen. Dies ist der Einstiegspunkt für den Dienstprozess.
  • onStartCommand wird mit dem beim Start erstellten Intent zurückgerufen. Der Dienst wird hier in den Vordergrund verschoben und der Worker gestartet. Weitere Informationen finden Sie im Abschnitt 3.1.

2.3 Status ist einmal pro Prozess vorhanden

Dex-Code wird schreibgeschützt freigegeben, der Laufzeitstatus jedoch nicht:

  • Application.attachBaseContext und Application.onCreate werden in jedem Prozess ausgeführt, der Anwendungskomponenten hostet.
  • Statische Initialisierer und statische Felder sind in jedem Prozess unabhängig voneinander vorhanden. Wenn Sie ein statisches Feld im Hauptprozess zuweisen, wird der Dienst nicht benachrichtigt.
  • Unity, C# und Aktivitäten bleiben im Hauptprozess.

3. Java-Implementierung

In diesem Abschnitt wird die Java-Seite des FGS-Moduls behandelt: 3.1 und 3.2 sind DownloadService im Dienstprozess, 3.3 ist FgsBridge im Hauptprozess (der Einstiegspunkt, den C# mit JNI aufruft).

3.1 Zuerst in den Vordergrund verschieben

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 Benachrichtigung hinzufügen

Ein wahrnehmbarer Dienst benötigt eine Benachrichtigung über laufende Aktivitäten in einem Benachrichtigungskanal, um den Fortschritt zu melden. Die Benachrichtigung enthält eine Aktion zum Beenden, die ACTION_STOP mit PendingIntent.getService an den Dienst selbst sendet, um ihn zu beenden.

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-Host-Einstiegspunkt

FgsBridge ist der Java-Einstiegspunkt, den der Hauptprozess verwendet, um den FGS zu steuern. Er wird im Hauptprozess und nicht im Dienstprozess ausgeführt. C# ruft diese statischen Methoden mit JNI auf, um den Dienst zu starten und zu beenden und die Benachrichtigungsberechtigung zu verarbeiten:

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. FGS aus Unity (C#) starten und steuern

AndroidBridge ist der C#-Wrapper, der FgsBridge mit JNI aufruft. Die drei Methodenimplementierungen:

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 ist ein interner JNI-Helfer, der die Klasse FgsBridge auflöst und die entsprechende statische Java-Methode aufruft. RequestNotificationPermission sollte beim Start der Anwendung aufgerufen werden, damit die Benachrichtigung sofort angezeigt wird, wenn der Dienstprozess gestartet wird.