Menjalankan layanan yang dapat dirasakan dalam proses terpisah dengan Unity

Panduan ini mencakup konfigurasi dan penerapan yang diperlukan untuk menjalankan layanan yang dapat dirasakan Android (Layanan Latar Depan, atau FGS) dalam proses pribadi dari aplikasi Unity.

1. Mengonfigurasi dukungan layanan yang dapat dirasakan

Bagian ini menjelaskan cara menyiapkan izin yang diperlukan dan mendeklarasikan layanan dalam manifes project Anda.

1.1 Izin dan pernyataan layanan

Assets/Plugins/Android/AndroidManifest.xml kustom harus mendeklarasikan Aktivitas peluncur, izin layanan yang dapat dirasakan, izin notifikasi, izin jaringan, dan layanan:

<?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 harus cocok dengan pekerjaan sebenarnya: dataSync untuk transfer, mediaPlayback untuk pemutaran, location untuk pelacakan lokasi. Harus sesuai dengan izin FOREGROUND_SERVICE_<TYPE> yang sesuai dan panggilan startForeground. Ketiganya harus konsisten, atau startup layanan akan gagal.

1.2 Menambahkan Java service-process ke project Unity

Proses layanan FGS menjalankan Java. Kemasi kode Java implementasi layanan ke dalam jar dan tempatkan di bawah Assets/Plugins/Android/. Unity secara otomatis menyertakan JAR di direktori tersebut ke dalam input libs/Gradle dan memaketkannya ke dalam APK; proses layanan memuat class ini saat runtime. Untuk mengetahui informasi selengkapnya tentang cara menerapkan layanan di Java atau Kotlin, lihat Penerapan Java.

2. Mengonfigurasi proses terpisah

Batas proses diaktifkan oleh satu atribut manifes:

android:process=":downloader"

Titik dua di awal membuat proses khusus aplikasi bernama your.package.name:downloader. Proses ini memiliki PID yang berbeda dari proses utama dan dapat bertahan setelah proses utama dihentikan.

2.1 Proses layanan tidak memiliki Unity

Untuk menghindari penggunaan libunity.so, runtime IL2CPP, dan sejenisnya dari Unity dalam proses layanan, Java sisi layanan tidak boleh menggunakan class Unity apa pun (termasuk UnityPlayer.currentActivity), dan hanya boleh bergantung pada Android Context, Intent ekstra, dan API platform. Runtime mesin Unity dimuat hanya melalui UnityPlayerActivity proses utama; proses :downloader pribadi tidak memuat library ini, sehingga mereferensikan class Unity tidak berfungsi dalam proses layanan.

2.2 Titik entri startup proses

Setelah proses utama memanggil startForegroundService, sistem membuat fork proses layanan :downloader:

  • Membuat instance Application dan memanggil Application.onCreate — ini berjalan di setiap proses, sehingga inisialisasi yang diperlukan oleh proses layanan harus dilakukan lagi di sini. Untuk mengetahui informasi selengkapnya, lihat bagian 2.3.
  • Membuat DownloadService dan memanggil onCreate — ini adalah titik entri service-process.
  • Memanggil kembali onStartCommand dengan Intent yang dibuat saat startup. Layanan membuat dirinya menjadi latar depan di sini dan memulai pekerja. Untuk mengetahui informasi selengkapnya, lihat bagian 3.1.

2.3 Status ada satu kali per proses

Kode Dex dibagikan hanya baca, tetapi status runtime tidak:

  • Application.attachBaseContext dan Application.onCreate berjalan di setiap proses yang menghosting komponen aplikasi.
  • Penginisialisasi statis dan kolom statis ada secara independen di setiap proses. Menetapkan kolom statis dalam proses utama tidak berkomunikasi dengan layanan.
  • Unity, C#, dan Aktivitas tetap berada di proses utama.

3. Penerapan Java

Bagian ini membahas sisi Java dari modul FGS: 3.1 dan 3.2 berada DownloadService dalam proses layanan; 3.3 berada FgsBridge dalam proses utama (panggilan C# titik entri menggunakan JNI).

3.1 Promosikan ke latar depan terlebih dahulu

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 Menambahkan notifikasi

Layanan yang dapat disimak memerlukan notifikasi berkelanjutan di saluran notifikasi untuk melaporkan progres. Notifikasi ini membawa tindakan Berhenti yang mengirimkan ACTION_STOP ke layanan itu sendiri menggunakan PendingIntent.getService untuk menghentikannya.

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 Titik entri host Java

FgsBridge adalah titik entri Java yang digunakan proses utama untuk mengontrol FGS (berjalan di proses utama, bukan proses layanan). C# memanggil metode statis ini menggunakan JNI untuk memulai dan menghentikan layanan serta menangani izin notifikasi:

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. Mulai dan kontrol FGS dari Unity (C#)

AndroidBridge adalah wrapper C# yang memanggil FgsBridge menggunakan JNI. Tiga implementasi metode:

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 adalah helper JNI internal yang menyelesaikan class FgsBridge dan memanggil metode statis Java yang sesuai. RequestNotificationPermission harus dipanggil saat aplikasi dimulai agar notifikasi muncul segera saat proses layanan dimulai.