Vom Nutzer initiierte Datenübertragung

Wenn Sie eine Datenübertragung durchführen müssen, die lange dauern kann, können Sie einen JobScheduler-Job erstellen und ihn als vom Nutzer initiierte Datenübertragung (UIDT)-Job kennzeichnen. UIDT-Jobs sind für länger dauernde Datenübertragungen vorgesehen, die vom Gerätenutzer initiiert werden, z. B. das Herunterladen einer Datei von einem Remote-Server. UIDT-Jobs wurden mit Android 14 (API‑Level 34) eingeführt.

Vom Nutzer initiierte Datenübertragungsvorgänge werden vom Nutzer gestartet. Für diese Jobs ist eine Benachrichtigung erforderlich, sie werden sofort gestartet und können unter Umständen über einen längeren Zeitraum ausgeführt werden, sofern die Systembedingungen dies zulassen. Sie können mehrere vom Nutzer initiierte Datenübermittlungsvorgänge gleichzeitig ausführen.

Vom Nutzer initiierte Jobs müssen geplant werden, während die Anwendung für den Nutzer sichtbar ist oder unter einer der zulässigen Bedingungen. Wenn alle Einschränkungen erfüllt sind, können vom Nutzer initiierte Jobs vom Betriebssystem ausgeführt werden, sofern keine Einschränkungen aufgrund des Systemzustands vorliegen. Das System kann die angegebene geschätzte Nutzlastgröße auch verwenden, um die Ausführungsdauer des Jobs zu bestimmen.

Vom Nutzer initiierte Datenübertragungsvorgänge planen

如需运行用户发起的数据传输作业,请执行以下操作:

  1. 确保您的应用已在其清单中声明 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() {
    
        ....
    
    }
  2. 在清单中声明 RUN_USER_INITIATED_JOBS 权限:

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. 构建 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();
  4. 在作业执行期间,对 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
        }
    }
  5. 定期更新通知,让用户了解作业的状态和进度。如果在安排作业之前无法确定传输大小,或者需要更新估计的传输大小,请在知道传输大小之后使用新的 API updateEstimatedNetworkBytes() 更新传输大小。

建议

如需有效运行 UIDT 作业,请执行以下操作:

  1. 明确定义网络限制和作业执行限制,以指定作业的执行时间。

  2. onStartJob() 中异步执行任务;例如,您可以使用协程来执行此操作。如果您不异步运行任务,工作将在主线程上运行,可能会阻塞主线程,从而导致 ANR。

  3. 为避免作业运行时间过长,请在转移完成后(无论成功还是失败)调用 jobFinished()。这样,作业就不会运行过长时间。如需了解作业停止的原因,请实现 onStopJob() 回调方法并调用 JobParameters.getStopReason()

Abwärtskompatibilität

目前还没有支持 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-Jobs beenden

Sowohl der Nutzer als auch das System können vom Nutzer initiierte Übertragungsjobs stoppen.

Vom Nutzer über den Task-Manager

用户可以停止显示在任务管理器中的用户发起的传输作业。

在用户按 Stop 时,系统会执行以下操作:

  • 立即终止应用的进程,包括正在运行的所有其他作业或前台服务。
  • 不针对任何正在运行的作业调用 onStopJob()
  • 阻止重新调度用户可见的作业。

因此,建议在发布的作业通知中提供控件,以便顺利停止和重新调度作业。

请注意,在特殊情况下,Stop 按钮不会显示在任务管理器中的作业旁边,或者该作业根本不会显示在任务管理器中。

Vom System

Im Gegensatz zu regulären Jobs sind vom Nutzer initiierte Datenübertragungsvorgänge nicht von den Kontingenten für App-Standby-Buckets betroffen. Das System stoppt den Job jedoch weiterhin, wenn eine der folgenden Bedingungen eintritt:

  • Eine vom Entwickler definierte Einschränkung wird nicht mehr erfüllt.
  • Das System stellt fest, dass der Job länger als für die Durchführung der Datenübertragung erforderlich ausgeführt wurde.
  • Das System muss der Systemintegrität Priorität einräumen und Jobs aufgrund eines erhöhten thermischen Zustands beenden.
  • Der App-Prozess wird aufgrund von zu wenig Gerätespeicher beendet.

Wenn der Job vom System aus anderen Gründen als geringem Gerätespeicher beendet wird, ruft das System onStopJob() auf und versucht es zu einem Zeitpunkt noch einmal, der vom System als optimal angesehen wird. Achte darauf, dass deine App den Status der Datenübertragung auch dann beibehalten kann, wenn onStopJob() nicht aufgerufen wird, und dass deine App diesen Status wiederherstellen kann, wenn onStartJob() noch einmal aufgerufen wird.

Bedingungen für das Planen vom Nutzer initiierter Datenübertragungsvorgänge

Apps können einen vom Nutzer initiierten Datenübertragungsvorgang nur starten, wenn sich die App im sichtbaren Fenster befindet oder bestimmte Bedingungen erfüllt sind:

  • Wenn eine App Aktivitäten aus dem Hintergrund starten kann, kann sie auch vom Nutzer initiierte Datenübertragungsjobs aus dem Hintergrund starten.
  • Wenn eine App eine Aktivität im Backstack einer vorhandenen Aufgabe auf dem Bildschirm Letzte hat, ist das allein noch kein Grund, einen vom Nutzer initiierten Datenübertragungsjob auszuführen.

Wenn der Job zu einem Zeitpunkt ausgeführt werden soll, zu dem die erforderlichen Bedingungen nicht erfüllt sind, schlägt er fehl und gibt den Fehlercode RESULT_FAILURE zurück.

Zulässige Einschränkungen für vom Nutzer initiierte Datenübertragungsvorgänge

为了支持在最佳时间点运行的作业,Android 提供了为每种作业类型分配约束条件的功能。这些约束条件从 Android 13 开始就已经可用。

注意:下表仅比较了因作业类型而异的约束条件。如需了解所有约束条件,请参阅 JobScheduler 开发者页面工作约束条件

下表显示了支持给定作业约束条件的不同作业类型,以及 WorkManager 支持的作业约束条件集。您可以使用表格前的搜索栏按作业约束方法的名称过滤表格。

以下是用户发起的数据传输作业允许使用的约束条件:

  • setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)
  • setClipData()
  • setEstimatedNetworkBytes()
  • setMinimumNetworkChunkBytes()
  • setPersisted()
  • setNamespace()
  • setRequiredNetwork()
  • setRequiredNetworkType()
  • setRequiresBatteryNotLow()
  • setRequiresCharging()
  • setRequiresStorageNotLow()

Testen

Die folgende Liste enthält einige Schritte zum manuellen Testen der Jobs Ihrer Anwendung:

  • Um die Job-ID abzurufen, rufen Sie den Wert ab, der für den zu erstellenden Job definiert ist.
  • Führen Sie den folgenden Befehl aus, um einen Job sofort auszuführen oder einen beendeten Job noch einmal auszuführen: -Befehl in einem Terminalfenster:

    adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
  • Um zu simulieren, dass das System das Beenden eines Jobs (aufgrund des Systemzustands oder Out-of-quota-Bedingungen), führen Sie den folgenden Befehl in einem Terminalfenster aus:

    adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID

Siehe auch

Zusätzliche Ressourcen

Weitere Informationen zu von Nutzern initiierten Datenübertragungen finden Sie in den folgenden zusätzlichen Ressourcen: