이 가이드에서는 Unity 애플리케이션에서 비공개 프로세스로 Android 인식 가능한 서비스 (포그라운드 서비스 또는 FGS)를 실행하는 데 필요한 구성 및 구현을 다룹니다.
1. 인식 가능한 서비스 지원 구성
이 섹션에서는 필요한 권한을 설정하고 프로젝트의 매니페스트에서 서비스를 선언하는 방법을 설명합니다.
1.1 권한 및 서비스 선언
커스텀
Assets/Plugins/Android/AndroidManifest.xml
은 런처 활동, 인식 가능한 서비스 권한,
알림 권한, 네트워크 권한, 서비스를 선언해야 합니다.
<?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
은 실제 작업과 일치해야 합니다. 전송의 경우 dataSync, 재생의 경우 mediaPlayback, 위치 추적의 경우 location입니다. 상응하는 FOREGROUND_SERVICE_<TYPE> 권한 및 startForeground 호출과 일치해야 합니다. 세 가지 모두 일관성이 있어야 하며, 그렇지 않으면 서비스 시작이 실패합니다.
1.2 서비스 프로세스 Java를 Unity 프로젝트에 추가
FGS 서비스 프로세스는 Java를 실행합니다. 서비스 구현 Java 코드를 jar로 패키징하고 Assets/Plugins/Android/ 아래에 배치합니다. Unity는 해당 디렉터리의 jar를 Gradle의 libs/ 입력에 자동으로 포함하고 이를 APK로 패키징합니다. 서비스 프로세스는 런타임에 이러한 클래스를 로드합니다. Java 또는 Kotlin에서 서비스를 구현하는 방법에 관한 자세한 내용은 Java 구현을 참고하세요.
2. 별도의 프로세스 구성
프로세스 경계는 하나의 매니페스트 속성으로 사용 설정됩니다.
android:process=":downloader"
선행 콜론은 your.package.name:downloader라는 애플리케이션 비공개 프로세스를 만듭니다. 기본 프로세스와 다른 PID를 가지며 기본 프로세스가 종료된 후에도 계속될 수 있습니다.
2.1 서비스 프로세스에 Unity가 없음
Unity의 libunity.so, IL2CPP 런타임, 유사한 항목을 서비스 프로세스에 가져오지 않으려면 서비스 측 Java에서 Unity 클래스(UnityPlayer.currentActivity 포함)를 사용하지 않아야 하며 Android 컨텍스트, 인텐트 추가 기능, 플랫폼 API에만 종속되어야 합니다. Unity 엔진 런타임은 기본 프로세스의 UnityPlayerActivity를 통해서만 로드됩니다. 비공개 :downloader 프로세스는 이러한 라이브러리를 로드하지 않으므로 서비스 프로세스에서 Unity 클래스를 참조해도 작동하지 않습니다.
2.2 프로세스 시작 진입점
기본 프로세스가 startForegroundService를 호출하면 시스템은 :downloader 서비스 프로세스를 포크합니다.
Application을 인스턴스화하고Application.onCreate을 호출합니다. 이는 모든 프로세스에서 실행되므로 서비스 프로세스 에 필요한 초기화를 여기서 다시 실행해야 합니다. 자세한 내용은 섹션 2.3을 참고하세요.DownloadService를 만들고onCreate를 호출합니다. 이는 서비스 프로세스 진입점입니다.- 시작 시 구성된 인텐트로
onStartCommand를 다시 호출합니다. 서비스는 여기서 포그라운드로 승격되고 작업자를 시작합니다. 자세한 내용은 섹션 3.1을 참고하세요.
2.3 프로세스당 한 번 상태가 존재함
Dex 코드는 읽기 전용으로 공유되지만 런타임 상태는 공유되지 않습니다.
Application.attachBaseContext및Application.onCreate는 애플리케이션 구성요소를 호스팅하는 모든 프로세스에서 실행됩니다.- 정적 초기화 프로그램과 정적 필드는 각 프로세스에 독립적으로 존재합니다. 기본 프로세스에서 정적 필드를 할당해도 서비스와 통신하지 않습니다.
- Unity, C#, 활동은 기본 프로세스에 남아 있습니다.
3. Java 구현
이 섹션에서는 FGS 모듈의 Java 측면을 다룹니다. 3.1 및 3.2는 서비스 프로세스의 DownloadService입니다. 3.3은 기본 프로세스의 FgsBridge (JNI를 사용하여 C# 호출을 시작하는 진입점)입니다.
3.1 먼저 포그라운드로 승격
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 알림 추가
인식 가능한 서비스는 진행 상황을 보고하기 위해 알림 채널에서 진행 중인 알림이 필요합니다. 알림에는 PendingIntent.getService를 사용하여 서비스 자체에 ACTION_STOP을 전송하여 서비스를 중지하는 중지 작업이 포함되어 있습니다.
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 호스트 진입점
FgsBridge 는 기본 프로세스에서 FGS를 제어하는 데 사용하는 Java 진입점입니다(서비스 프로세스가 아닌 기본 프로세스에서 실행됨). C# 은 JNI를 사용하여 이러한 정적 메서드를 호출하여 서비스를 시작 및 중지하고 알림 권한을 처리합니다.
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. Unity (C#)에서 FGS 시작 및 제어
AndroidBridge는 JNI를 사용하여 FgsBridge를 호출하는 C# 래퍼입니다. 세 가지 메서드 구현은 다음과 같습니다.
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 은 FgsBridge 클래스를 확인하고 상응하는 Java 정적 메서드를 호출하는 내부 JNI 도우미입니다. 서비스 프로세스가 시작될 때 알림이 즉시 표시되도록 애플리케이션 시작 시 RequestNotificationPermission을 호출해야 합니다.