Unreal ile ayrı bir işlemde algılanabilir bir hizmet çalıştırma

Bu kılavuzda, Unreal uygulamasından özel bir süreçte Android'de algılanabilir bir hizmet (FGS) çalıştırmak için gereken yapılandırma ve uygulama açıklanmaktadır.

1. Algılanabilir hizmet desteğini yapılandırma

Bu bölümde, gerekli izinlerin nasıl ayarlanacağı ve hizmetin projenizin manifest dosyasında nasıl beyan edileceği açıklanmaktadır.

1.1 İzinler ve hizmet beyanı (UPL eklemeleri)

Unreal, motor manifestinin yerini almaz. UnrealBuildTool, AndroidManifest.xml dosyasını oluştururken GameActivity'yi zaten tanımladığından UPL'nin yalnızca izinleri ve hizmeti eklemesi gerekir. Başlatıcı etkinliğinin yeniden belirtilmesi gerekmez. Source/PSUnreal/PSUnreal_UPL.xml dosyasındaki <androidManifestUpdates> bölümüne aşağıdakileri ekleyin:

<androidManifestUpdates>
    <addPermission android:name="android.permission.FOREGROUND_SERVICE" />
    <addPermission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
    <addPermission android:name="android.permission.POST_NOTIFICATIONS" />

    <addElements tag="application">
        <service
            android:name="com.sample.fgs.DownloadService"
            android:process=":downloader"
            android:exported="false"
            android:foregroundServiceType="dataSync"
            android:stopWithTask="false" />
    </addElements>
</androidManifestUpdates>

foregroundServiceType gerçek işlevle eşleşmelidir: dataSync aktarım, mediaPlayback oynatma, location konum izleme. İlgili FOREGROUND_SERVICE_<TYPE> izni ve startForeground çağrısıyla eşleşmelidir. Üçü de tutarlı olmalıdır. Aksi takdirde hizmet başlatılamaz.

Hizmet, tam nitelikli adı com.sample.fgs.DownloadService kullanıyor. Başta nokta olması, com.sample.psunreal uygulama kimliğine göre çözümlenmesine neden olur ancak Java modülü com.sample.fgs paketinde bulunduğundan bulunamaz.

1.2 Hizmet süreci Java'sını Unreal projesine ekleme

Hizmet uygulaması Java kodunu bir JAR dosyası olarak derleyin ve UPL'nin <prebuildCopies> özelliğini kullanarak hazırlama libs dizinine kopyalayın. UEDeployAndroid, hazırlama libs/ dizinini Gradle projesinin app/libs/ dizinine kopyalar ve Gradle, oradaki her JAR dosyasını otomatik olarak dahil ederek APK'ya paketler. Hizmet süreci, bu sınıfları çalışma zamanında yükler:

<prebuildCopies>
    <copyFile src="$S(PluginDir)/fgs-android.jar"
              dst="$S(BuildDir)/libs/fgs-android.jar" />
</prebuildCopies>

$S(PluginDir), UPL dosyasının bulunduğu dizindir. Derlenmiş JAR dosyasını buraya yerleştirin. $S(BuildDir), hazırlama dizinidir.

Bu sınıflara yalnızca JNI ve manifest aracılığıyla erişilir. Java çağrı siteleri olmadığından bu sınıflar da <proguardAdditions> içinde tutulmalıdır. Aksi takdirde küçültücü, bu sınıfları kullanılmamış olarak değerlendirip kaldırır:

<proguardAdditions>
    <insert>
        -keep class com.sample.fgs.FgsBridge { public *; }
        -keep class com.sample.fgs.DownloadService { public *; }
        -keep class com.sample.fgs.ProgressFile { public *; }
        -keep class com.sample.fgs.FgsLogger { public *; }
    </insert>
</proguardAdditions>

2. Ayrı bir işlem yapılandırma

Süreç sınırı, bir manifest özelliğiyle etkinleştirilir:

android:process=":downloader"

Baştaki iki nokta üst üste işareti, com.sample.psunreal:downloader adlı uygulamaya özel bir işlem oluşturur. Ana işlemden farklı bir PID'ye sahiptir ve ana işlem sonlandırıldıktan sonra çalışmaya devam edebilir.

2.1 Hizmet sürecinde Unreal kullanılmıyor

libUE4.so veya libUE5.so gibi Unreal'ın yerel kitaplıklarının hizmet sürecine dahil edilmesini önlemek için hizmet tarafındaki Java, Unreal sınıflarını kullanmamalı ve yalnızca Android Context, Intent ekstraları ve platform API'lerine bağlı olmalıdır. Unreal Engine çalışma zamanı yalnızca ana işlemin GameActivity aracılığıyla yüklenir. Özel :downloader işlemi bu kitaplıkları yüklemez. Bu nedenle, Unreal sınıflarına referans verme işlemi hizmet sürecinde çalışmaz.

2.2 İşlem başlangıç giriş noktaları

Ana işlem startForegroundService'yı çağırdıktan sonra sistem, :downloader hizmet sürecini çatallandırır:

  • Application öğesini oluşturur ve Application.onCreate öğesini çağırır. Bu işlem her süreçte çalışır. Bu nedenle, hizmet süreci tarafından gereken başlatma işlemi burada tekrar yapılmalıdır (bkz. 2.3).
  • DownloadService oluşturur ve onCreate çağırır. Bu, hizmet süreci giriş noktasıdır.
  • Başlangıçta oluşturulan Intent ile onStartCommand geri aranır. Hizmet burada kendini ön plana çıkarır ve çalışanı başlatır (bkz. 3.1).

2.3 Durum, işlem başına bir kez bulunur

Dex kodu salt okunur olarak paylaşılır ancak çalışma zamanı durumu paylaşılmaz:

  • Application.attachBaseContext ve Application.onCreate, uygulama bileşenlerini barındıran her süreçte çalışır.
  • Statik başlatıcılar ve statik alanlar, her süreçte bağımsız olarak bulunur. Ana süreçte statik bir alan atandığında hizmetle iletişim kurulmaz.
  • Unreal, C++ ve Etkinlikler ana süreçte kalır.

3. Java uygulaması

Bu bölümde, FGS modülünün Java tarafı ele alınmaktadır: 3.1 ve 3.2, hizmet sürecinde DownloadService; 3.3 ise ana süreçte FgsBridge yer alır (JNI kullanılarak yapılan giriş noktası C++ çağrıları).

3.1 Önce ön plana çıkarma

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 Bildirimi ekleme

Algılanabilir bir hizmetin, ilerlemeyi bildirmek için bildirim kanalında devam eden bir bildirime ihtiyacı vardır. Bildirimde, PendingIntent.getService kullanılarak hizmetin kendisi durdurulması için ACTION_STOP gönderen bir Durdurma işlemi yer alır.

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 ana makine giriş noktası

FgsBridge, ana işlemin FGS'yi kontrol etmek için kullandığı Java giriş noktasıdır (hizmet işleminde değil, ana işlemde çalışır). C++, hizmeti başlatmak ve durdurmak, bildirim iznini işlemek için JNI kullanarak bu statik yöntemleri çağırır:

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. Unreal'dan (C++) FGS'yi başlatma ve kontrol etme

PSUnrealAndroidBridge, JNI kullanarak FgsBridge'yi çağıran C++ sarmalayıcısıdır. Üç yöntem uygulaması aşağıda verilmiştir:

void FPSUnrealAndroidBridge::StartDownloadService()
{
    CallActivityVoid(
            GBridgeInfo.StartDownloadService, TEXT("StartDownloadService"));
}

void FPSUnrealAndroidBridge::StopDownloadService()
{
    CallActivityVoid(
            GBridgeInfo.StopDownloadService, TEXT("StopDownloadService"));
}

void FPSUnrealAndroidBridge::RequestNotificationPermission()
{
    CallActivityVoid(
            GBridgeInfo.RequestNotificationPermission, TEXT("RequestNotificationPermission"));
}

CallActivityVoid, Context olarak FJavaWrapper::GameActivityThis'ı kullanarak ilgili Java statik yöntemini çağıran dahili bir JNI yardımcı programıdır. Bildirimin servis işlemi başladığında hemen görünmesini sağlamak için RequestNotificationPermission numaralı telefonu BeginPlay içinde arayın.