คู่มือนี้ครอบคลุมการกำหนดค่าและการใช้งานที่จำเป็นสำหรับการเรียกใช้บริการที่รับรู้ได้ของ Android (บริการที่ทำงานอยู่เบื้องหน้า หรือ FGS) ในกระบวนการส่วนตัวจากแอปพลิเคชัน Unity
1. กำหนดค่าการรองรับบริการที่รับรู้ได้
ส่วนนี้จะอธิบายวิธีตั้งค่าสิทธิ์ที่จำเป็นและประกาศบริการในไฟล์ Manifest ของโปรเจ็กต์
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 ซึ่งทั้ง 3 อย่างต้องสอดคล้องกัน ไม่เช่นนั้นบริการจะเริ่มต้น
ไม่สำเร็จ
1.2 เพิ่ม Java ของกระบวนการบริการลงในโปรเจ็กต์ Unity
กระบวนการบริการ FGS จะเรียกใช้ Java แพ็กเกจโค้ด Java ของการใช้งานบริการลงในไฟล์ Jar แล้ววางไว้ใน Assets/Plugins/Android/ Unity จะรวมไฟล์ Jar ในไดเรกทอรีนั้นลงในอินพุต libs/ ของ Gradle และแพ็กเกจลงใน APK โดยอัตโนมัติ ซึ่งกระบวนการบริการจะโหลดคลาสเหล่านี้ในรันไทม์ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้งานบริการใน Java หรือ Kotlin ได้ที่ ดู การใช้งาน Java
2. กำหนดค่ากระบวนการแยกต่างหาก
ระบบจะเปิดใช้ขอบเขตกระบวนการด้วยแอตทริบิวต์ไฟล์ Manifest 1 รายการ ดังนี้
android:process=":downloader"
โคลอนนำหน้าจะสร้างกระบวนการส่วนตัวของแอปพลิเคชันชื่อ your.package.name:downloader โดยจะมี PID ที่แตกต่างจากกระบวนการหลักและสามารถทำงานต่อไปได้หลังจากกระบวนการหลักสิ้นสุดลง
2.1 กระบวนการบริการไม่มี Unity
เพื่อหลีกเลี่ยงการนำ libunity.so ของ Unity, รันไทม์ IL2CPP และสิ่งที่คล้ายกันมาไว้ในกระบวนการบริการ Java ฝั่งบริการจึงไม่ควรใช้คลาส Unity ใดๆ (รวมถึง UnityPlayer.currentActivity) และควรขึ้นอยู่กับบริบท Android, ส่วนเพิ่มเติมของ Intent และ API ของแพลตฟอร์มเท่านั้น ระบบจะโหลดรันไทม์ของเอนจิน Unity ผ่าน UnityPlayerActivity ของกระบวนการหลักเท่านั้น โดยกระบวนการ :downloader ส่วนตัวจะไม่โหลดไลบรารีเหล่านี้ ดังนั้นการอ้างอิงคลาส Unity จึงใช้ไม่ได้ในกระบวนการบริการ
2.2 จุดแรกเข้าของการเริ่มต้นกระบวนการ
หลังจากกระบวนการหลักเรียก startForegroundService แล้ว ระบบจะแยกกระบวนการบริการ :downloader ดังนี้
- สร้างอินสแตนซ์
ApplicationและเรียกApplication.onCreateซึ่ง จะทำงานในทุกกระบวนการ ดังนั้นจึงต้องทำการเริ่มต้นที่กระบวนการบริการจำเป็นต้องใช้ อีกครั้งที่นี่ ดูข้อมูลเพิ่มเติมได้ที่ส่วน 2.3 - สร้าง
DownloadServiceและเรียกonCreateซึ่งเป็นจุดแรกเข้าของกระบวนการบริการ - เรียกกลับ
onStartCommandด้วย Intent ที่สร้างขึ้นเมื่อเริ่มต้น บริการจะเลื่อนระดับตัวเองไปทำงานอยู่เบื้องหน้าที่นี่และเริ่ม Worker ดูข้อมูลเพิ่มเติมได้ที่ส่วน 3.1
2.3 สถานะมีอยู่ 1 ครั้งต่อกระบวนการ
โค้ด Dex จะแชร์แบบอ่านอย่างเดียว แต่สถานะรันไทม์จะไม่แชร์
Application.attachBaseContextและApplication.onCreateจะทำงานในทุกกระบวนการที่โฮสต์คอมโพเนนต์ของแอปพลิเคชัน- ตัวเริ่มต้นแบบคงที่และฟิลด์แบบคงที่จะมีอยู่แยกกันในแต่ละกระบวนการ การกำหนดฟิลด์แบบคงที่ในกระบวนการหลักจะไม่สื่อสารกับบริการ
- Unity, C# และกิจกรรมจะยังคงอยู่ในกระบวนการหลัก
3. การใช้งาน Java
ส่วนนี้ครอบคลุมโมดูล FGS ฝั่ง Java โดย 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 คือ Wrapper C# ที่เรียก FgsBridge โดยใช้ JNI การใช้งานเมธอด 3 รายการ
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 เมื่อเริ่มต้นแอปพลิเคชันเพื่อให้การแจ้งเตือนปรากฏขึ้นทันทีเมื่อกระบวนการบริการเริ่มต้น