Hướng dẫn này trình bày cấu hình và quá trình triển khai cần thiết để chạy một dịch vụ có thể nhận biết (Dịch vụ trên nền trước hoặc FGS) trong một quy trình riêng tư từ ứng dụng Unity.
1. Định cấu hình chế độ hỗ trợ perceptible-service
Phần này giải thích cách thiết lập các quyền bắt buộc và khai báo dịch vụ trong tệp kê khai của dự án.
1.1 Khai báo quyền và dịch vụ
Assets/Plugins/Android/AndroidManifest.xml tuỳ chỉnh phải khai báo Hoạt động của Trình chạy, các quyền đối với dịch vụ có thể nhận biết, quyền gửi thông báo, quyền truy cập mạng và dịch vụ:
<?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
phải khớp với công việc thực tế: dataSync cho việc chuyển, mediaPlayback cho việc phát, location cho việc theo dõi vị trí. Nó phải đồng ý với quyền FOREGROUND_SERVICE_<TYPE> tương ứng và lệnh gọi startForeground – cả ba phải nhất quán, nếu không quá trình khởi động dịch vụ sẽ thất bại.
1.2 Thêm Java quy trình dịch vụ vào dự án Unity
Quy trình dịch vụ FGS chạy Java. Đóng gói mã Java triển khai dịch vụ vào một tệp jar và đặt tệp đó trong Assets/Plugins/Android/. Unity tự động đưa các tệp jar trong thư mục đó vào libs/input của Gradle và đóng gói các tệp đó vào APK; quy trình dịch vụ sẽ tải các lớp này trong thời gian chạy. Để biết thêm thông tin về cách triển khai dịch vụ này bằng Java hoặc Kotlin, hãy xem phần Triển khai Java.
2. Định cấu hình một quy trình riêng biệt
Ranh giới quy trình được bật bằng một thuộc tính tệp kê khai:
android:process=":downloader"
Dấu hai chấm ở đầu sẽ tạo một quy trình dành riêng cho ứng dụng có tên là your.package.name:downloader. Tiến trình này có PID khác với tiến trình chính và có thể tồn tại sau khi tiến trình chính kết thúc.
2.1 Quy trình dịch vụ không có Unity
Để tránh đưa libunity.so, thời gian chạy IL2CPP và các thành phần tương tự của Unity vào quy trình dịch vụ, Java phía dịch vụ không được dùng bất kỳ lớp Unity nào (kể cả UnityPlayer.currentActivity) và chỉ được phụ thuộc vào Android Context, Intent extras và các API nền tảng. Thời gian chạy của công cụ Unity chỉ được tải thông qua UnityPlayerActivity của quy trình chính; quy trình :downloader riêng tư không tải các thư viện này, nên việc tham chiếu các lớp Unity không hoạt động trong quy trình dịch vụ.
2.2 Điểm truy cập khởi động quy trình
Sau khi quy trình chính gọi startForegroundService, hệ thống sẽ phân nhánh quy trình dịch vụ :downloader:
- Tạo thực thể
Applicationvà gọiApplication.onCreate– thao tác này chạy trong mọi quy trình, vì vậy, quá trình khởi tạo mà quy trình dịch vụ cần phải thực hiện lại tại đây. Để biết thêm thông tin, hãy xem phần 2.3. - Tạo
DownloadServicevà gọionCreate– đây là điểm truy cập vào quy trình dịch vụ. - Gọi lại
onStartCommandbằng Intent được tạo khi khởi động. Dịch vụ này tự quảng bá lên nền trước tại đây và khởi động worker. Để biết thêm thông tin, hãy xem phần 3.1.
2.3 Trạng thái chỉ tồn tại một lần cho mỗi quy trình
Mã Dex được chia sẻ ở chế độ chỉ đọc, nhưng trạng thái thời gian chạy thì không:
Application.attachBaseContextvàApplication.onCreatechạy trong mọi quy trình lưu trữ các thành phần ứng dụng.- Trình khởi tạo tĩnh và trường tĩnh tồn tại độc lập trong mỗi quy trình. Việc chỉ định một trường tĩnh trong quy trình chính không giao tiếp với dịch vụ.
- Unity, C# và Hoạt động vẫn nằm trong quy trình chính.
3. Triển khai Java
Phần này trình bày phía Java của mô-đun FGS: 3.1 và 3.2 là DownloadService trong quy trình dịch vụ; 3.3 là FgsBridge trong quy trình chính (các lệnh gọi C# tại điểm truy cập bằng JNI).
3.1 Quảng bá lên nền trước trước
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 Thêm thông báo
Một dịch vụ mà người dùng có thể nhận biết cần có một thông báo hiển thị liên tục trên một kênh thông báo để báo cáo tiến trình. Thông báo này có một thao tác Dừng, thao tác này sẽ gửi ACTION_STOP đến chính dịch vụ bằng cách dùng PendingIntent.getService để dừng dịch vụ.
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 Điểm truy cập vào máy chủ Java
FgsBridge là điểm truy cập Java mà quy trình chính dùng để kiểm soát FGS (chạy trong quy trình chính, chứ không phải quy trình dịch vụ). C# gọi các phương thức tĩnh này bằng JNI để bắt đầu và dừng dịch vụ, đồng thời xử lý quyền thông báo:
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. Bắt đầu và kiểm soát FGS từ Unity (C#)
AndroidBridge là trình bao bọc C# gọi FgsBridge bằng JNI. 3 cách triển khai phương thức:
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 là một trình trợ giúp JNI nội bộ giúp phân giải lớp FgsBridge và gọi phương thức tĩnh Java tương ứng. Bạn nên gọi RequestNotificationPermission khi khởi động ứng dụng để thông báo xuất hiện ngay khi quy trình dịch vụ bắt đầu.