Esegui un servizio percettibile in un processo separato con Unity

Questa guida illustra la configurazione e l'implementazione necessarie per eseguire un servizio percepibile Android (servizio in primo piano o FGS) in un processo privato da un'applicazione Unity.

1. Configurare il supporto per i servizi percepibili

Questa sezione spiega come configurare le autorizzazioni richieste e dichiarare il servizio nel manifest del progetto.

1.1 Autorizzazioni e dichiarazione del servizio

Il file personalizzato Assets/Plugins/Android/AndroidManifest.xml deve dichiarare l'attività di avvio, le autorizzazioni del servizio percepibile, l'autorizzazione per le notifiche, l'autorizzazione di rete e il servizio:

<?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 deve corrispondere al lavoro reale: dataSync per i trasferimenti, mediaPlayback per la riproduzione, location per il monitoraggio della posizione. Deve corrispondere all' autorizzazione FOREGROUND_SERVICE_<TYPE> e alla startForeground chiamata corrispondenti. Tutti e tre devono essere coerenti, altrimenti l'avvio del servizio non riesce.

1.2 Aggiungere il codice Java del processo di servizio al progetto Unity

Il processo di servizio FGS esegue Java. Inserisci il codice Java di implementazione del servizio in un file JAR e inseriscilo in Assets/Plugins/Android/. Unity include automaticamente i file JAR in questa directory nell'input libs/ di Gradle e li inserisce nel pacchetto APK. Il processo di servizio carica queste classi in fase di runtime. Per saperne di più su come implementare il servizio in Java o Kotlin, consulta Implementazione Java.

2. Configurare un processo separato

Il limite del processo è abilitato da un attributo del manifest:

android:process=":downloader"

Il punto iniziale crea un processo privato dell'applicazione denominato your.package.name:downloader. Ha un PID diverso dal processo principale e può sopravvivere dopo la terminazione del processo principale.

2.1 Il processo di servizio non ha Unity

Per evitare di inserire libunity.so, il runtime IL2CPP e simili nel processo di servizio, il codice Java lato servizio non deve utilizzare classi Unity (inclusa UnityPlayer.currentActivity) e deve dipendere solo dal contesto Android, dagli extra di Intent e dalle API della piattaforma. Il runtime del motore Unity viene caricato solo tramite UnityPlayerActivity del processo principale. Il processo privato :downloader non carica queste librerie, quindi il riferimento alle classi Unity non funziona nel processo di servizio.

2.2 Punti di ingresso per l'avvio del processo

Dopo che il processo principale chiama startForegroundService, il sistema esegue il fork del processo di servizio :downloader:

  • Istanzia Application e chiama Application.onCreate. Questa operazione viene eseguita in ogni processo, quindi l'inizializzazione necessaria per il processo di servizio deve essere eseguita di nuovo qui. Per saperne di più, consulta la sezione 2.3.
  • Crea DownloadService e chiama onCreate. Questo è il punto di ingresso del processo di servizio.
  • Richiama onStartCommand con l'Intent creato all'avvio. Il servizio si promuove in primo piano e avvia il worker. Per saperne di più, consulta la sezione 3.1.

2.3 Lo stato esiste una volta per processo

Il codice Dex è condiviso in sola lettura, ma lo stato di runtime no:

  • Application.attachBaseContext e Application.onCreate vengono eseguiti in ogni processo che ospita i componenti dell'applicazione.
  • Gli inizializzatori statici e i campi statici esistono indipendentemente in ogni processo. L'assegnazione di un campo statico nel processo principale non comunica con il servizio.
  • Unity, C# e le attività rimangono nel processo principale.

3. Implementazione Java

Questa sezione illustra il lato Java del modulo FGS: 3.1 e 3.2 sono DownloadService nel processo di servizio; 3.3 è FgsBridge nel processo principale (il punto di ingresso C# chiama utilizzando JNI).

3.1 Promuovere prima in primo piano

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 Aggiungere la notifica

Un servizio percepibile ha bisogno di una notifica continua su un canale di notifica per segnalare l'avanzamento. La notifica include un'azione di arresto che invia ACTION_STOP al servizio stesso utilizzando PendingIntent.getService per arrestarlo.

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 Punto di ingresso dell'host Java

FgsBridge è il punto di ingresso Java utilizzato dal processo principale per controllare l'FGS (viene eseguito nel processo principale, non nel processo di servizio). C# chiama questi metodi statici utilizzando JNI per avviare e arrestare il servizio e gestire l'autorizzazione per le notifiche:

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. Avviare e controllare l'FGS da Unity (C#)

AndroidBridge è il wrapper C# che chiama FgsBridge utilizzando JNI. Le tre implementazioni dei metodi:

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 è un helper JNI interno che risolve la classe FgsBridge e chiama il metodo statico Java corrispondente. RequestNotificationPermission deve essere chiamato all'avvio dell'applicazione in modo che la notifica venga visualizzata immediatamente all'avvio del processo di servizio.