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 veApplication.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).DownloadServiceoluşturur veonCreateçağırır. Bu, hizmet süreci giriş noktasıdır.- Başlangıçta oluşturulan
IntentileonStartCommandgeri 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.attachBaseContextveApplication.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.