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

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

  1. Ihre App muss die JobService und die zugehörigen Berechtigungen in ihrem Manifest deklariert haben:

    <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 die 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 die Methode setUserInitiated() auf, wenn Sie ein JobInfo-Objekt erstellen. Diese Methode ist ab Android 14 verfügbar. Wir empfehlen außerdem, beim Erstellen des Jobs eine Schätzung der Nutzlastgröße durch Aufrufen von setEstimatedNetworkBytes() anzubieten.

    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 darüber informiert, dass der Job ausgeführt wird.

    Rufen Sie nach Abschluss der Ausführung 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 nicht vor dem Planen des Jobs ermitteln 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. Dazu können Sie beispielsweise eine Coroutine verwenden. Wenn Sie die Aufgabe nicht asynchron ausführen, wird sie im Hauptthread ausgeführt und kann ihn 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 fehlgeschlagen ist. 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

Derzeit gibt es keine Jetpack-Bibliothek, die UIDT-Jobs unterstützt. Aus diesem Grund empfehlen wir, die Änderung mit Code zu versehen, der prüft, ob Sie Android 14 oder höher verwenden. Bei älteren Android-Versionen können Sie die Implementierung des Diensts im Vordergrund von WorkManager als Fallback-Ansatz verwenden.

Hier ist ein Beispiel für Code, der die entsprechende Systemversion prüft:

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

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.

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 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.

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

Damit Jobs an optimalen Punkten ausgeführt werden können, 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 zwischen den einzelnen Jobtypen unterscheiden. Eine vollständige Liste der 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 Job-Einschränkung unterstützen, sowie die Job-Einschränkungen, die von WorkManager unterstützt werden. Verwenden Sie die Suchleiste vor der Tabelle, um die Tabelle nach dem Namen einer Methode für Jobbeschränkungen zu filtern.

Dies sind die Einschränkungen, die bei vom Nutzer initiierten Datenübertragungsvorgängen zulässig sind:

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