Ten przewodnik opisuje konfigurację i implementację potrzebną do uruchomienia w aplikacji Unity usługi widocznej dla użytkownika (Foreground Service lub FGS) w procesie prywatnym.
1. Konfigurowanie obsługi usługi widocznej dla użytkownika
W tej sekcji dowiesz się, jak skonfigurować wymagane uprawnienia i zadeklarować usługę w pliku manifestu projektu.
1.1 Deklaracja uprawnień i usługi
Niestandardowy
Assets/Plugins/Android/AndroidManifest.xml
musi deklarować aktywność uruchamiającą, uprawnienia usługi widocznej dla użytkownika,
zgodę na wyświetlanie powiadomień, uprawnienia do sieci i usługę:
<?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
musi odpowiadać rzeczywistemu działaniu: dataSync w przypadku przesyłania, mediaPlayback w przypadku odtwarzania, location w przypadku śledzenia lokalizacji. Musi być zgodny z
odpowiednim uprawnieniem FOREGROUND_SERVICE_<TYPE> i wywołaniem
startForeground – wszystkie 3 muszą być spójne, w przeciwnym razie uruchomienie usługi
się nie powiedzie.
1.2 Dodawanie kodu Java procesu usługi do projektu Unity
Proces usługi FGS jest uruchamiany w Javie. Spakuj kod Java implementacji usługi do pliku JAR i umieść go w folderze Assets/Plugins/Android/. Unity automatycznie uwzględnia pliki JAR w tym katalogu w danych wejściowych Gradle libs/ i pakuje je do pliku APK. Proces usługi wczytuje te klasy w czasie działania. Więcej informacji o implementowaniu usługi w Javie lub Kotlinie, zobacz Implementacja w Javie.
2. Konfigurowanie osobnego procesu
Granica procesu jest włączana przez jeden atrybut manifestu:
android:process=":downloader"
Początkowy dwukropek tworzy proces prywatny aplikacji o nazwie your.package.name:downloader. Ma on inny identyfikator PID niż proces główny i może działać po zakończeniu procesu głównego.
2.1 Proces usługi nie ma Unity
Aby uniknąć przenoszenia do procesu usługi biblioteki libunity.so, środowiska wykonawczego IL2CPP i podobnych, kod Java po stronie usługi nie powinien używać żadnych klas Unity (w tym UnityPlayer.currentActivity) i powinien zależeć tylko od kontekstu Androida, dodatków Intent i interfejsów API platformy. Środowisko wykonawcze silnika Unity jest wczytywane tylko przez UnityPlayerActivity procesu głównego. Proces prywatny :downloader nie wczytuje tych bibliotek, więc odwoływanie się do klas Unity nie działa w procesie usługi.
2.2 Punkty wejścia uruchamiania procesu
Gdy proces główny wywoła startForegroundService, system rozwidla proces usługi :downloader:
- Tworzy instancję
Applicationi wywołujeApplication.onCreate– to jest uruchamiane w każdym procesie, więc inicjowanie potrzebne procesowi usługi musi zostać wykonane ponownie. Więcej informacji znajdziesz w sekcji 2.3. - Tworzy
DownloadServicei wywołujeonCreate– jest to punkt wejścia procesu usługi. - Wywołuje zwrotnie
onStartCommandz intencją utworzoną podczas uruchamiania. Usługa promuje się tutaj na pierwszy plan i uruchamia proces roboczy. Więcej informacji znajdziesz w sekcji 3.1.
2.3 Stan istnieje raz na proces
Kod Dex jest udostępniany tylko do odczytu, ale stan środowiska wykonawczego nie jest:
Application.attachBaseContextiApplication.onCreatesą uruchamiane w każdym procesie, który hostuje komponenty aplikacji.- Inicjatory statyczne i pola statyczne istnieją niezależnie w każdym procesie. Przypisanie pola statycznego w procesie głównym nie komunikuje się z usługą.
- Unity, C# i aktywności pozostają w procesie głównym.
3. Implementacja w Javie
W tej sekcji omówimy stronę Java modułu FGS: 3.1 i 3.2 to DownloadService w procesie usługi, a 3.3 to FgsBridge w procesie głównym (punkt wejścia wywołań C# za pomocą JNI).
3.1 Najpierw promuj na pierwszy plan
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 Dodawanie powiadomienia
Usługa widoczna dla użytkownika potrzebuje powiadomienia o trwającej aktywności w kanale powiadomień, aby informować o postępach. Powiadomienie zawiera działanie Stop, które wysyła ACTION_STOP do samej usługi za pomocą PendingIntent.getService, aby ją zatrzymać.
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 Punkt wejścia hosta Java
FgsBridge to punkt wejścia Java, którego proces główny używa do sterowania FGS (jest on uruchamiany w procesie głównym, a nie w procesie usługi). C# wywołuje te metody statyczne za pomocą JNI, aby uruchamiać i zatrzymywać usługę oraz obsługiwać uprawnienia do powiadomień:
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. Uruchamianie FGS i sterowanie nim z poziomu Unity (C#)
AndroidBridge to otoka C#, która wywołuje FgsBridge za pomocą JNI. Implementacje 3 metod:
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 to wewnętrzna pomocnicza funkcja JNI, która rozwiązuje klasę FgsBridge i wywołuje odpowiednią statyczną metodę Java. RequestNotificationPermission
należy wywołać podczas uruchamiania aplikacji, aby powiadomienie pojawiło się natychmiast po uruchomieniu procesu usługi.