如要執行可能需要長時間的資料移轉作業,可以建立 JobScheduler 工作,並將其識別為使用者啟動的資料移轉 (UIDT) 工作。UIDT 工作適用於裝置使用者啟動的長時間資料轉移作業,例如從遠端伺服器下載檔案。Android 14 (API 級別 34) 導入了 UIDT 工作。
顧名思義,使用者啟動的資料移轉工作是由使用者所發起的。這類工作會要求傳送通知並且要立即開始執行,如果系統條件允許,還可能會長時間執行。您可以同時執行多項使用者啟動的資料移轉工作。
當使用者具備應用程式的瀏覽權限時 (或符合任一許可條件),則必須為使用者啟動的工作進行排程。滿足所有限制後,作業系統就能執行使用者啟動的工作,具體情況取決於系統健康狀態限制。此外,系統也能根據所提供的預估酬載大小,判斷執行工作的時間長度。
排定使用者啟動的資料移轉作業
To run a user initiated data-transfer job, do the following:
Make sure your app has declared the
JobServiceand associated permissions in its manifest:<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>Also, define a concrete subclass of
JobServicefor your data transfer:Kotlin
class CustomTransferService : JobService() { ... }
Java
class CustomTransferService extends JobService() { .... }
Declare the
RUN_USER_INITIATED_JOBSpermission in the manifest:<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>Call the
setUserInitiated()method when building aJobInfoobject. (This method is available beginning with Android 14.) We also recommend that you offer a payload size estimate by callingsetEstimatedNetworkBytes()while creating your job.Kotlin
val networkRequestBuilder = NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build() val jobInfo = JobInfo.Builder(jobId, ComponentName(mContext, CustomTransferService::class.java)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequestBuilder) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024, 1024 * 1024 * 1024) // ... .build()
Java
NetworkRequest networkRequest = new NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build(); JobInfo jobInfo = JobInfo.Builder(jobId, new ComponentName(mContext, CustomTransferService.class)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequest) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024, 1024 * 1024 * 1024) // ... .build();
While the job is being executed, call
setNotification()on theJobServiceobject. CallingsetNotification()makes the user aware that the job is running, both in the Task Manager and in the status bar notification area.When execution is complete, call
jobFinished()to signal to the system that the job is complete, or that the job should be rescheduled.Kotlin
class CustomTransferService: JobService() { private val scope = CoroutineScope(Dispatchers.IO) @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) override fun onStartJob(params: JobParameters): Boolean { val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build() setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. scope.launch { doDownload(params) } return true } private suspend fun doDownload(params: JobParameters) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false) } // Called when the system stops the job. override fun onStopJob(params: JobParameters?): Boolean { // Asynchronously record job-related data, such as the // stop reason. return true // or return false if job should end entirely } }
Java
class CustomTransferService extends JobService{ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Override public boolean onStartJob(JobParameters params) { Notification notification = Notification.Builder(getBaseContext(), NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build(); setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. new Thread(() -> doDownload(params)).start(); return true; } private void doDownload(JobParameters params) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false); } // Called when the system stops the job. @Override public boolean onStopJob(JobParameters params) { // Asynchronously record job-related data, such as the // stop reason. return true; // or return false if job should end entirely } }
Periodically update the notification to keep the user informed of the job's status and progress. If you cannot determine the transfer size ahead of scheduling the job, or need to update the estimated transfer size, use the new API,
updateEstimatedNetworkBytes()to update the transfer size after it becomes known.
Recommendations
To run UIDT jobs effectively, do the following:
Clearly define network constraints and job execution constraints to specify when the job should be executed.
Execute the task asynchronously in
onStartJob(); for example, you can do this by using a coroutine. If you don't run the task asynchronously, the work runs on the main thread and might block it, which can cause an ANR.To avoid running the job longer than necessary, call
jobFinished()when the transfer finishes, whether it succeeds or fails. That way, the job doesn't run longer than necessary. To discover why a job was stopped, implement theonStopJob()callback method and callJobParameters.getStopReason().
回溯相容性
目前沒有支援 UIDT 作業的 Jetpack 程式庫。因此,建議您使用程式碼控管變更,確認您是在 Android 14 以上版本上執行作業。在較舊的 Android 版本中,您可以使用 WorkManager 的前景服務實作做為備用方法。
以下是檢查適當系統版本的程式碼範例:
Kotlin
fun beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context) } else { scheduleDownloadUIDTJob(context) } } private fun scheduleDownloadUIDTJob(context: Context) { // build jobInfo val jobScheduler: JobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(jobInfo) } private fun scheduleDownloadFGSWorker(context: Context) { val myWorkRequest = OneTimeWorkRequest.from(DownloadWorker::class.java) WorkManager.getInstance(context).enqueue(myWorkRequest) }
Java
public void beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context); } else { scheduleDownloadUIDTJob(context); } } private void scheduleDownloadUIDTJob(Context context) { // build jobInfo JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(jobInfo); } private void scheduleDownloadFGSWorker(Context context) { OneTimeWorkRequest myWorkRequest = OneTimeWorkRequest.from(DownloadWorker.class); WorkManager.getInstance(context).enqueue(myWorkRequest) }
停止 UIDT 工作
Both the user and the system can stop user-initiated transfer jobs.
由工作管理員的使用者執行
The user can stop a user-initiated data transfer job that appears in the Task Manager.
At the moment that the user presses Stop, the system does the following:
- Terminates your app's process immediately, including all other jobs or foreground services running.
- Doesn't call
onStopJob()for any running jobs. - Prevents user-visible jobs from being rescheduled.
For these reasons, it's recommended to provide controls in the notification posted for the job to allow gracefully stopping and rescheduling the job.
Note that, under special circumstances, the Stop button doesn't appear next to the job in the Task Manager, or the job isn't shown in the Task Manager at all.
由系統執行
Unlike regular jobs, user-initiated data transfer jobs are unaffected by App Standby Buckets quotas. However, the system still stops the job if any of the following conditions occur:
- A developer-defined constraint is no longer met.
- The system determines that the job has run for longer than necessary to complete the data transfer task.
- The system needs to prioritize system health and stop jobs due to increased thermal state.
- The app process is killed due to low device memory.
When the job is stopped by the system for reasons other than low device
memory, the system calls onStopJob(), and the system retries the job at a time
that the system deems to be optimal. Make sure that your app can persist the
data transfer state even if onStopJob() isn't called, and that your app can
restore this state when onStartJob() is called again.
可對使用者啟動的資料移轉作業進行排程的情況
Apps can only start a user-initiated data transfer job if the app is in the visible window, or if certain conditions are met:
- If an app can launch activities from the background, it can also launch user-initiated data transfer jobs from the background.
- If an app has an activity in the back stack of an existing task on the Recents screen, that alone doesn't allow a user-initiated data transfer job to run.
If the job is scheduled to run at a time when the necessary conditions are not
met, the job fails and returns a RESULT_FAILURE error code.
使用者啟動的資料移轉作業許可的限制
為支援在最佳時間點執行的工作,Android 為各個工作類型提供指派限制的功能。這些限制自 Android 13 起已可供使用。
附註:下表僅就各種工作類型之間的限制差異提出比較。如要瞭解所有限制,請參閱 JobScheduler 開發人員頁面或工作限制條件。
下表說明支援特定工作限制的各種工作類型,並列出 WorkManager 支援的工作限制組合。使用表格前的搜尋列,即可按照工作限制方法的名稱篩選表格。
以下限制適用於使用者啟動的資料移轉作業:
setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)setClipData()setEstimatedNetworkBytes()setMinimumNetworkChunkBytes()setPersisted()setNamespace()setRequiredNetwork()setRequiredNetworkType()setRequiresBatteryNotLow()setRequiresCharging()setRequiresStorageNotLow()
測試
The following list shows some steps on how to test your app's jobs manually:
- To get the job ID, get the value that is defined upon the job being built.
To run a job immediately, or to retry a stopped job, run the following command in a terminal window:
adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
To simulate the system force-stopping a job (due to system health or out-of-quota conditions), run the following command in a terminal window:
adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID
另請參閱
其他資源
如要進一步瞭解使用者發起的資料轉移作業,請參閱下列其他資源:
- 使用者啟動的資料轉移整合案例研究:Google 地圖使用使用者啟動的資料轉移 API,將下載可靠性提升 10%