Run a perceptible service in a separate process with Unity

This guide covers the configuration and implementation needed to run an Android perceptible service (Foreground Service, or FGS) in a private process from a Unity application.

1. Configure perceptible-service support

This section explains how to set up the required permissions and declare the service in your project's manifest.

1.1 Permissions and service declaration

The custom Assets/Plugins/Android/AndroidManifest.xml must declare the launcher Activity, perceptible-service permissions, notification permission, network permission, and the service:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android">
<uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permissionandroid:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permissionandroid:name="android.permission.INTERNET"/>
<application>
<activity
android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector"
android:exported="true">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-dataandroid: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 must match the real work: dataSync for transfers, mediaPlayback for playback, location for location tracking. It must agree with the corresponding FOREGROUND_SERVICE_<TYPE> permission and the startForeground call — all three must be consistent, or service startup fails.

1.2 Add the service-process Java to the Unity project

The FGS service process runs Java. Package the service implementation Java code into a jar and place it under Assets/Plugins/Android/. Unity automatically includes jars in that directory into Gradle's libs/ input and packages them into the APK; the service process loads these classes at runtime. For more information about how to implement the service in Java or Kotlin, see Java implementation.

2. Configure a separate process

The process boundary is enabled by one manifest attribute:

android:process=":downloader"

The leading colon creates an application-private process named your.package.name:downloader. It has a different PID from the main process and can survive after the main process terminates.

2.1 The service process has no Unity

To avoid bringing Unity's libunity.so, the IL2CPP runtime, and similar into the service process, service-side Java shouldn't use any Unity classes (including UnityPlayer.currentActivity), and should depend only on Android Context, Intent extras, and platform APIs. The Unity engine runtime is loaded only through the main process's UnityPlayerActivity; the private :downloader process doesn't load these libraries, so referencing Unity classes doesn't work in the service process.

2.2 Process startup entry points

After the main process calls startForegroundService, the system forks the :downloader service process:

  • Instantiates Application and calls Application.onCreate — this runs in every process, so initialization needed by the service process must be done again here. For more information, see section 2.3.
  • Creates DownloadService and calls onCreate — this is the service-process entry point.
  • Calls back onStartCommand with the Intent constructed at startup. The service promotes itself to the foreground here and starts the worker. For more information, see section 3.1.

2.3 State exists once per process

Dex code is shared read-only, but runtime state isn't:

  • Application.attachBaseContext and Application.onCreate run in every process that hosts application components.
  • Static initializers and static fields exist independently in each process. Assigning a static field in the main process doesn't communicate with the service.
  • Unity, C#, and Activities remain in the main process.

3. Java implementation

This section covers the Java side of the FGS module: 3.1 and 3.2 are DownloadService in the service process; 3.3 is FgsBridge in the main process (the entry point C# calls using JNI).

3.1 Promote to foreground first

publicclass DownloadServiceextendsService{
@Override
publicintonStartCommand(Intentintent,intflags,intstartId){
try{
startForegroundCompat();
// Start the download thread; must come after promoting to foreground
// ...
}catch(Exceptione){
Log.e(TAG,"onStartCommand() failed [errorType="
+e.getClass().getSimpleName()+"]: "+e.getMessage(),e);
stopSelf();
}
returnSTART_NOT_STICKY;
}
privatevoidstartForegroundCompat(){
Notificationnotification=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 Add the notification

A perceptible service needs an ongoing notification on a notification channel to report progress. The notification carries a Stop action that sends ACTION_STOP to the service itself using PendingIntent.getService to stop it.

privateNotificationbuildNotification(longprogressBytes){
intprogressMib=(int)(progressBytes/MIB);
IntentlaunchIntent=getPackageManager().getLaunchIntentForPackage(getPackageName());
PendingIntentcontentIntent=launchIntent!=null
?PendingIntent.getActivity(this,0,launchIntent,
PendingIntent.FLAG_IMMUTABLE|PendingIntent.FLAG_UPDATE_CURRENT)
:null;
IntentstopIntent=newIntent(this,DownloadService.class)
.setAction(ACTION_STOP);
PendingIntentstopPendingIntent=PendingIntent.getService(this,0,
stopIntent,PendingIntent.FLAG_IMMUTABLE
|PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builderbuilder=newNotification.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(newNotification.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);
}
returnbuilder.build();
}

3.3 Java host entry point

FgsBridge is the Java entry point the main process uses to control the FGS (it runs in the main process, not the service process). C# calls these static methods using JNI to start and stop the service and handle notification permission:

publicclass FgsBridge{
// Start the :downloader perceptible service and begin downloading
publicstaticvoidstartDownloadService(Contextcontext){
try{
context.startForegroundService(newIntent(context,DownloadService.class));
}catch(Exceptione){
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
publicstaticvoidstopDownloadService(Contextcontext){
try{
context.startService(newIntent(context,DownloadService.class)
.setAction(ACTION_STOP));
}catch(Exceptione){
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)
publicstaticvoidrequestNotificationPermission(Activityactivity){
if(Build.VERSION.SDK_INT < 33){
return;
}
try{
activity.requestPermissions(
newString[]{POST_NOTIFICATIONS},NOTIFICATION_PERMISSION_REQUEST);
}catch(Exceptione){
Log.e(TAG,"requestNotificationPermission() failed [errorType="
+e.getClass().getSimpleName()+"]: "+e.getMessage(),e);
}
}
}

4. Start and control the FGS from Unity (C#)

AndroidBridge is the C# wrapper that calls FgsBridge using JNI. The three method implementations:

publicstaticvoidStartDownloadService()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("startDownloadService");
#else
Debug.Log("StartDownloadService() no-op outside Android");
#endif
}
publicstaticvoidStopDownloadService()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("stopDownloadService");
#else
Debug.Log("StopDownloadService() no-op outside Android");
#endif
}
publicstaticvoidRequestNotificationPermission()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("requestNotificationPermission");
#else
Debug.Log("RequestNotificationPermission() no-op outside Android");
#endif
}

CallStatic is an internal JNI helper that resolves the FgsBridge class and calls the corresponding Java static method. RequestNotificationPermission should be called at application startup so that the notification appears immediately when the service process starts.

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026年08月20日 UTC.