Este guia aborda a configuração e a implementação necessárias para executar um serviço perceptível do Android (serviço em primeiro plano ou FGS, na sigla em inglês) em um processo particular de um aplicativo Unity.
1. Configurar o suporte a serviços perceptíveis
Esta seção explica como configurar as permissões necessárias e declarar o serviço no manifesto do projeto.
1.1 Permissões e declaração de serviço
O personalizado
Assets/Plugins/Android/AndroidManifest.xml
precisa declarar a atividade do inicializador, as permissões de serviço perceptível,
a permissão de notificação, a permissão de rede e o serviço:
<?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
precisa corresponder ao trabalho real: dataSync para transferências, mediaPlayback para reprodução, location para rastreamento de localização. Ele precisa concordar com a
permissão FOREGROUND_SERVICE_<TYPE> correspondente e a
startForeground chamada. Os três precisam ser consistentes, ou a inicialização do serviço
falha.
1.2 Adicionar o Java de processo de serviço ao projeto do Unity
O processo de serviço do FGS executa o Java. Empacote o código Java de implementação do serviço em um arquivo JAR e coloque-o em Assets/Plugins/Android/. O Unity inclui automaticamente arquivos JAR nesse diretório na entrada libs/ do Gradle e os empacota no APK. O processo de serviço carrega essas classes no ambiente de execução. Para mais informações sobre como implementar o serviço em Java ou Kotlin, consulte Implementação em Java.
2. Configurar um processo separado
O limite do processo é ativado por um atributo de manifesto:
android:process=":downloader"
Os dois pontos iniciais criam um processo particular do aplicativo chamado your.package.name:downloader. Ele tem um PID diferente do processo principal e pode sobreviver após o encerramento do processo principal.
2.1 O processo de serviço não tem Unity
Para evitar trazer libunity.so, o ambiente de execução do IL2CPP e semelhantes para o processo de serviço, o Java do lado do serviço não deve usar nenhuma classe do Unity (incluindo UnityPlayer.currentActivity) e só deve depender do contexto do Android, extras de intent e APIs da plataforma. O ambiente de execução do mecanismo do Unity é carregado apenas pela UnityPlayerActivity do processo principal. O processo :downloader particular não carrega essas bibliotecas. Portanto, a referência a classes do Unity não funciona no processo de serviço.
2.2 Pontos de entrada de inicialização do processo
Depois que o processo principal chama startForegroundService, o sistema ramifica o processo de serviço :downloader:
- Instancia
Applicatione chamaApplication.onCreate. Isso é executado em todos os processos. Portanto, a inicialização necessária pelo processo de serviço precisa ser feita novamente aqui. Para mais informações, consulte a seção 2.3. - Cria
DownloadServicee chamaonCreate. Esse é o ponto de entrada do processo de serviço. - Retorna a chamada
onStartCommandcom a intent construída na inicialização. O serviço se promove ao primeiro plano aqui e inicia o worker. Para mais informações, consulte a seção 3.1.
2.3 O estado existe uma vez por processo
O código Dex é compartilhado somente leitura, mas o estado do ambiente de execução não é:
Application.attachBaseContexteApplication.onCreatesão executados em todos os processos que hospedam componentes do aplicativo.- Os inicializadores estáticos e os campos estáticos existem de forma independente em cada processo. A atribuição de um campo estático no processo principal não se comunica com o serviço.
- O Unity, o C# e as atividades permanecem no processo principal.
3. Implementação em Java
Esta seção aborda o lado Java do módulo FGS: 3.1 e 3.2 são DownloadService no processo de serviço; 3.3 é FgsBridge no processo principal (as chamadas C# do ponto de entrada usando JNI).
3.1 Promover para o primeiro plano primeiro
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 Adicionar a notificação
Um serviço perceptível precisa de uma notificação em andamento em um canal de notificação para informar o progresso. A notificação tem uma ação de parada que envia ACTION_STOP para o próprio serviço usando PendingIntent.getService para interrompê-lo.
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 Ponto de entrada do host Java
FgsBridge é o ponto de entrada Java que o processo principal usa para controlar o FGS (ele é executado no processo principal, não no processo de serviço). O C# chama esses métodos estáticos usando JNI para iniciar e interromper o serviço e processar a permissão de notificação:
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. Iniciar e controlar o FGS no Unity (C#)
AndroidBridge é o wrapper C# que chama FgsBridge usando JNI. As três implementações de método:
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 é um auxiliar JNI interno que resolve a classe FgsBridge e chama o método estático Java correspondente. RequestNotificationPermission
precisa ser chamado na inicialização do aplicativo para que a notificação apareça imediatamente quando o processo de serviço for iniciado.