If you need to perform a data transfer that may take a long time, you can create a JobScheduler job and identify it as a user-initiated data transfer (UIDT) job. UIDT jobs are intended for longer-duration data transfers that are initiated by the device user, such as downloading a file from a remote server. UIDT jobs were introduced with Android 14 (API level 34).
User-initiated data transfer jobs are started by the user. These jobs require a notification, start immediately, and may be able to run for an extended period of time as system conditions allow. You can run several user-initiated data transfer jobs concurrently.
User initiated jobs must be scheduled while the application is visible to the user (or in one of the allowed conditions). After all constraints are met, user initiated jobs can be executed by the OS, subject to system health restrictions. The system may also use the provided estimated payload size to determine how long the job executes.
排定使用者啟動的資料移轉作業
如要執行使用者啟動的資料移轉作業,請按照下列步驟操作:
請確認應用程式已在資訊清單中宣告
JobService和相關聯的權限:<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>此外,請為資料移轉定義
JobService的具體子類別:Kotlin
class CustomTransferService : JobService() { ... }
Java
class CustomTransferService extends JobService() { .... }
在資訊清單中宣告
RUN_USER_INITIATED_JOBS權限:<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>建構
JobInfo物件時,呼叫setUserInitiated()方法。(這項方法適用於 Android 14 以上版本)。此外,建議您在建立工作時呼叫setEstimatedNetworkBytes(),以提供酬載大小預估值。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();
執行工作時,請在
JobService物件上呼叫setNotification()。呼叫setNotification()會告知使用者工作正在工作管理員和狀態列通知區域執行中。執行作業完成後,請呼叫
jobFinished()以告知系統工作已經完成,或應重新為工作排程。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 } }
定期更新通知,讓使用者掌握工作狀態與進度。如果您無法在工作排程前確定轉移大小,或需要更新預估轉移大小,請使用新的 API
updateEstimatedNetworkBytes()更新已知的轉移大小。
建議
如要有效執行 UIDT 作業,請採取下列做法:
明確定義網路限制和工作執行限制,指定工作執行時間。
在
onStartJob()中以非同步方式執行工作;舉例來說,您可以使用協同程式執行這項操作。如果沒有非同步執行工作,工作就會在主執行緒上執行,並可能封鎖主執行緒,進而導致 ANR。為避免工作執行時間過長,請在移轉完成時呼叫
jobFinished(),無論移轉成功或失敗都一樣。這樣一來,工作就不會執行過久。如要瞭解工作停止的原因,請導入onStopJob()回呼方法,並呼叫JobParameters.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%