如果您需要执行可能需要很长时间的数据传输,可以创建 一个 JobScheduler 作业,并将其标识为用户发起的数据 传输 (UIDT) 作业。UIDT 作业适用于由设备用户发起的持续时间较长的数据传输,例如从远程服务器下载文件。UIDT 作业是在 Android 14(API 级别 34)中引入的。
由用户发起的数据传输作业由用户启动。这些作业需要一个通知,会立即启动,并且可能在系统条件允许的情况下长时间运行。您可以同时运行多个由用户发起的数据传输作业。
必须在应用对用户可见的情况下(或在某个允许的条件下)安排由用户发起的作业。满足所有限制条件后,操作系统可以执行由用户发起的作业,具体取决于系统运行状况限制。系统还可以根据提供的估算载荷大小来确定作业的执行时长。
사용자가 시작한 데이터 전송 작업 예약
사용자가 시작한 데이터 전송 작업을 실행하려면 다음을 실행하세요.
앱이 매니페스트에서
JobService및 관련 권한을 선언했는지 확인합니다.<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>데이터 전송을 위해
JobService의 구체적인 하위 클래스도 정의합니다.Kotlin
class CustomTransferService : JobService() { ... }
자바
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()
자바
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 } }
자바
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.
작업 관리자에서 사용자에 의해
사용자는 작업 관리자에 표시되는 사용자 시작 데이터 전송 작업을 중지할 수 있습니다.
사용자가 중지를 누르면 시스템은 다음 작업을 실행합니다.
- 실행 중인 다른 모든 작업 또는 포그라운드 서비스를 포함하여 앱의 프로세스를 즉시 종료합니다.
- 실행 중인 작업의
onStopJob()을 호출하지 않습니다. - 사용자에게 표시되는 작업의 일정이 변경되는 것을 방지합니다.
따라서 작업을 단계적으로 중지하고 일정을 변경할 수 있도록 작업에 관해 게시된 알림에 컨트롤을 제공하는 것이 좋습니다.
특수한 상황에서는 중지 버튼이 작업 관리자의 작업 옆에 표시되지 않거나 작업이 작업 관리자에 아예 표시되지 않을 수 있습니다.
시스템에 의해
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.
사용자가 시작한 데이터 전송 작업을 예약할 수 있는 조건
앱은 표시되는 창에 있거나 특정 조건이 충족되는 경우에만 사용자가 시작한 데이터 전송 작업을 시작할 수 있습니다.
- 앱이 백그라운드에서 활동을 실행할 수 있는 경우 사용자가 시작한 데이터 전송 작업도 백그라운드에서 실행할 수 있습니다.
- 앱의 최근 화면에 있는 기존 작업의 백 스택에 활동이 있어도 이것만으로는 사용자가 시작한 데이터 전송 작업을 실행할 수 없습니다.
필요한 조건이 충족되지 않는 시간에 작업이 실행되도록 예약되면 작업이 실패하고 RESULT_FAILURE 오류 코드가 반환됩니다.
사용자가 시작한 데이터 전송 작업에 허용되는 제약 조건
최적의 지점에서 실행되는 작업을 지원하기 위해 Android는 각 작업 유형에 제약 조건을 할당하는 기능을 제공합니다. 이러한 제약 조건은 Android 13부터 사용할 수 있습니다.
참고: 다음 표에서는 각 작업 유형에 따라 달라지는 제약 조건만 비교합니다. 모든 제약 조건은 JobScheduler 개발자 페이지 또는 작업 제약 조건을 참고하세요.
다음 표에서는 특정 작업 제약 조건을 지원하는 다양한 작업 유형과 WorkManager에서 지원하는 작업 제약 조건 집합을 보여줍니다. 표 앞에 있는 검색창을 사용하여 작업 제약 조건 메서드의 이름으로 표를 필터링합니다.
사용자가 시작한 데이터 전송 작업에서 허용되는 제약 조건은 다음과 같습니다.
setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)setClipData()setEstimatedNetworkBytes()setMinimumNetworkChunkBytes()setPersisted()setNamespace()setRequiredNetwork()setRequiredNetworkType()setRequiresBatteryNotLow()setRequiresCharging()setRequiresStorageNotLow()
테스트
下面列出了有关如何手动测试应用作业的一些步骤:
- 如需获取作业 ID,请获取在构建作业时定义的值。
如需立即运行作业或重试已停止的作业,请在终端窗口中运行以下命令:
adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
如需模拟系统强行停止作业(由于系统运行状况或超出配额条件),请在终端窗口中运行以下命令:
adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID
참고 항목
추가 리소스
사용자 시작 데이터 전송에 관한 자세한 내용은 다음 추가 리소스를 참고하세요.
- UIDT 통합 사례 연구: Google 지도에서 사용자 시작 데이터 전송 API를 사용하여 다운로드 안정성 10% 개선