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
Applicatione chiamaApplication.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
DownloadServicee chiamaonCreate. Questo è il punto di ingresso del processo di servizio. - Richiama
onStartCommandcon 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.attachBaseContexteApplication.onCreatevengono 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.