Vom Nutzer initiierte Datenübertragung

Wenn Sie eine Datenübertragung durchführen müssen, die möglicherweise lange dauert, können Sie einen JobScheduler-Job erstellen und ihn als vom Nutzer initiierte Datenübertragung (UIDT) kennzeichnen. UIDT-Jobs sind für länger andauernde Datenübertragungen gedacht, 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. Diese Jobs erfordern eine Benachrichtigung, starten sofort und können je nach Systembedingungen über einen längeren Zeitraum ausgeführt werden. Sie können mehrere vom Nutzer initiierte Datenübertragungsvorgänge gleichzeitig ausführen.

Vom Nutzer initiierte Jobs müssen geplant werden, während die App für den Nutzer sichtbar ist (oder unter einer der zulässigen Bedingungen). Nachdem alle Einschränkungen erfüllt sind, können vom Nutzer initiierte Jobs vom Betriebssystem ausgeführt werden, vorbehaltlich der Einschränkungen des Systemzustands. Das System kann die angegebene geschätzte Nutzlastgröße auch verwenden, um zu bestimmen, wie lange der Job ausgeführt wird.

Vom Nutzer initiierte Datenübertragungsvorgänge planen

So führen Sie einen vom Nutzer initiierten Datenübertragungsjob aus:

  1. Achten Sie darauf, dass in Ihrer App die JobService und die zugehörigen Berechtigungen im Manifest deklariert sind:

    <service android:name="com.example.app.CustomTransferService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false">
            ...
    </service>
    

    Definieren Sie außerdem eine konkrete Unterklasse von JobService für Ihre Datenübertragung:

    Kotlin

    class CustomTransferService : JobService() {
      ...
    }

    Java

    class CustomTransferService extends JobService() {
    
        ....
    
    }
  2. Deklarieren Sie die Berechtigung RUN_USER_INITIATED_JOBS im Manifest:

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. Rufen Sie beim Erstellen eines JobInfo-Objekts die Methode setUserInitiated() auf. (Diese Methode ist ab Android 14 verfügbar.) Wir empfehlen außerdem, eine Schätzung der Nutzlastgröße anzugeben, indem Sie beim Erstellen des Jobs setEstimatedNetworkBytes() aufrufen.

    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. Rufen Sie während der Ausführung des Jobs setNotification() für das JobService-Objekt auf. Durch den Aufruf von setNotification() wird der Nutzer sowohl im Task-Manager als auch im Benachrichtigungsbereich der Statusleiste darauf aufmerksam gemacht, dass der Job ausgeführt wird.

    Wenn die Ausführung abgeschlossen ist, rufen Sie jobFinished() auf, um dem System zu signalisieren, dass der Job abgeschlossen ist oder neu geplant werden soll.

    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. Aktualisieren Sie die Benachrichtigung regelmäßig, um den Nutzer über den Status und Fortschritt des Jobs auf dem Laufenden zu halten. Wenn Sie die Übertragungsgröße vor dem Planen des Jobs nicht bestimmen können oder die geschätzte Übertragungsgröße aktualisieren müssen, verwenden Sie die neue API updateEstimatedNetworkBytes(), um die Übertragungsgröße zu aktualisieren, sobald sie bekannt ist.

Empfehlungen

So führen Sie UIDT-Jobs effektiv aus:

  1. Definieren Sie Netzwerk- und Jobausführungseinschränkungen, um festzulegen, wann der Job ausgeführt werden soll.

  2. Führen Sie die Aufgabe asynchron in onStartJob() aus, z. B. mit einer Koroutine. Wenn Sie die Aufgabe nicht asynchron ausführen, wird die Arbeit im Hauptthread ausgeführt und kann diesen blockieren, was zu einem ANR-Fehler führen kann.

  3. Damit der Job nicht länger als nötig ausgeführt wird, rufen Sie jobFinished() auf, wenn die Übertragung abgeschlossen ist, unabhängig davon, ob sie erfolgreich war oder nicht. So wird der Job nicht länger als nötig ausgeführt. Wenn Sie herausfinden möchten, warum ein Job beendet wurde, implementieren Sie die Callback-Methode onStopJob() und rufen Sie JobParameters.getStopReason() auf.

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

Der Nutzer kann einen vom Nutzer initiierten Datenübertragungsvorgang beenden, der im Task-Manager angezeigt wird.

Wenn der Nutzer auf Stopp drückt, geschieht Folgendes:

  • Der Prozess Ihrer App wird sofort beendet, einschließlich aller anderen Jobs oder Dienste im Vordergrund.
  • onStopJob() wird für keine laufenden Jobs aufgerufen.
  • Verhindert, dass nutzersichtbare Jobs neu geplant werden.

Aus diesen Gründen wird empfohlen, in der Benachrichtigung für den Job Steuerelemente bereitzustellen, mit denen der Job ordnungsgemäß beendet und neu geplant werden kann.

Unter bestimmten Umständen wird die Schaltfläche Beenden nicht neben dem Job im Task-Manager angezeigt oder der Job wird im Task-Manager gar nicht angezeigt.

Vom System

与常规作业不同,用户发起的数据传输作业不受应用待机模式存储分区配额的影响。但是,如果出现以下任一情况,系统仍会停止作业:

  • 不再满足开发者定义的约束条件。
  • 系统确定该作业的运行时间超出了完成数据传输任务所需的时间。
  • 系统需要优先考虑系统运行状况,并因发热程度上升而停止作业。
  • 应用进程因设备内存不足而被终止。

如果系统因设备内存不足以外的原因停止作业,系统会调用 onStopJob(),并在系统认为最佳的时间重试作业。确保您的应用可以保留数据传输状态(即使未调用 onStopJob()),并且您的应用可以在再次调用 onStartJob() 时恢复此状态。

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

Damit Jobs zu optimalen Zeitpunkten ausgeführt werden, bietet Android die Möglichkeit, jedem Jobtyp Einschränkungen zuzuweisen. Diese Einschränkungen sind ab Android 13 verfügbar.

Hinweis: In der folgenden Tabelle werden nur die Einschränkungen verglichen, die sich je nach Jobtyp unterscheiden. Alle Einschränkungen finden Sie auf der JobScheduler-Entwicklerseite oder unter Arbeitseinschränkungen.

In der folgenden Tabelle sind die verschiedenen Jobtypen aufgeführt, die eine bestimmte Jobbeschränkung unterstützen, sowie die von WorkManager unterstützten Jobbeschränkungen. Verwenden Sie die Suchleiste vor der Tabelle, um die Tabelle nach dem Namen einer Methode für Jobbeschränkungen zu filtern.

Folgende Einschränkungen sind bei vom Nutzer initiierten Datenübertragungen zulässig:

  • 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: