يغطّي هذا الدليل الإعداد والتنفيذ اللازمَين لتشغيل خدمة Android مرئية (Foreground Service أو FGS) في عملية خاصة من تطبيق Unity.
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 في هذا الدليل في الإدخال libs/ في Gradle وتجمّعها في حزمة APK، وتحمّل عملية الخدمة هذه الفئات في وقت التشغيل. لمزيد من المعلومات حول كيفية تنفيذ الخدمة في Java أو Kotlin، يُرجى الاطّلاع على مقالة تنفيذ Java.
2. ضبط عملية منفصلة
يتم تفعيل حدّ العملية من خلال أحد سمات البيان:
android:process=":downloader"
تنشئ النقطتان الرأسيتان في البداية عملية خاصة بالتطبيق باسم your.package.name:downloader. ويكون لها رقم تعريف عملية مختلف عن العملية الرئيسية ويمكن أن تظل نشطة بعد انتهاء العملية الرئيسية.
2.1 لا تتضمّن عملية الخدمة Unity
لتجنُّب إدخال libunity.so ووقت تشغيل IL2CPP وما شابه ذلك إلى عملية الخدمة، يجب ألا تستخدم Java من جهة الخدمة أي فئات Unity (بما في ذلك UnityPlayer.currentActivity)، ويجب أن تعتمد فقط على Android Context وIntent extras وواجهات برمجة التطبيقات للمنصة. لا يتم تحميل وقت تشغيل محرّك Unity إلا من خلال UnityPlayerActivity في العملية الرئيسية، ولا تحمّل العملية الخاصة :downloader هذه المكتبات، لذا لا يمكن الإشارة إلى فئات Unity في عملية الخدمة.
2.2 نقاط بدء عملية التشغيل
بعد أن تستدعي العملية الرئيسية startForegroundService، ينشئ النظام عملية خدمة :downloader:
- يتم إنشاء
ApplicationواستدعاءApplication.onCreate، ويتم تشغيل ذلك في كل عملية، لذا يجب إعادة تنفيذ عملية التهيئة التي تحتاجها عملية الخدمة هنا. لمزيد من المعلومات، يُرجى الاطّلاع على القسم 2.3. - يتم إنشاء
DownloadServiceواستدعاءonCreate، وهذه هي نقطة دخول عملية الخدمة. - يتم استدعاء
onStartCommandمرة أخرى باستخدام Intent الذي تم إنشاؤه عند بدء التشغيل. تتم ترقية الخدمة إلى المقدّمة هنا ويبدأ العامل. لمزيد من المعلومات، يُرجى الاطّلاع على القسم 3.1.
2.3 تتوفّر الحالة مرة واحدة لكل عملية
تتم مشاركة رمز Dex للقراءة فقط، ولكن لا تتم مشاركة حالة وقت التشغيل:
- يتم تشغيل
Application.attachBaseContextوApplication.onCreateفي كل عملية تستضيف مكوّنات التطبيق. - تتوفّر أدوات التهيئة الثابتة والحقول الثابتة بشكل مستقل في كل عملية. لا يؤدي تعيين حقل ثابت في العملية الرئيسية إلى التواصل مع الخدمة.
- تظل Unity وC# وActivities في العملية الرئيسية.
3. تنفيذ Java
يتناول هذا القسم جانب Java من وحدة FGS: 3.1 و3.2 هما DownloadService في عملية الخدمة، و3.3 هو FgsBridge في العملية الرئيسية (نقطة الدخول التي تستدعيها C# باستخدام JNI).
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 إضافة الإشعار
تحتاج الخدمة المرئية إلى إشعار بنشاط مستمر في الخلفية على قناة إشعارات للإبلاغ عن التقدّم. يتضمّن الإشعار إجراء "إيقاف" يرسل ACTION_STOP إلى الخدمة نفسها باستخدام PendingIntent.getService لإيقافها.
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 هي نقطة دخول Java التي تستخدمها العملية الرئيسية للتحكّم في FGS (يتم تشغيلها في العملية الرئيسية، وليس في عملية الخدمة). تستدعي 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. بدء FGS والتحكّم فيها من Unity (C#)
AndroidBridge هو برنامج C# الذي يستدعي FgsBridge باستخدام JNI. في ما يلي عمليات تنفيذ الطرق الثلاث:
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 هي أداة مساعدة داخلية في JNI تحلّ فئة FgsBridge وتستدعي طريقة Java الثابتة المقابلة. يجب استدعاء RequestNotificationPermission عند بدء تشغيل التطبيق حتى يظهر الإشعار فور بدء عملية الخدمة.