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
To run a user initiated data-transfer job, do the following:
Make sure your app has declared the
JobServiceand associated permissions in its manifest:<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>Also, define a concrete subclass of
JobServicefor your data transfer:Kotlin
class CustomTransferService : JobService() { ... }
Java
class CustomTransferService extends JobService() { .... }
Declare the
RUN_USER_INITIATED_JOBSpermission in the manifest:<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>Call the
setUserInitiated()method when building aJobInfoobject. (This method is available beginning with Android 14.) We also recommend that you offer a payload size estimate by callingsetEstimatedNetworkBytes()while creating your job.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();
While the job is being executed, call
setNotification()on theJobServiceobject. CallingsetNotification()makes the user aware that the job is running, both in the Task Manager and in the status bar notification area.When execution is complete, call
jobFinished()to signal to the system that the job is complete, or that the job should be rescheduled.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 } }
Periodically update the notification to keep the user informed of the job's status and progress. If you cannot determine the transfer size ahead of scheduling the job, or need to update the estimated transfer size, use the new API,
updateEstimatedNetworkBytes()to update the transfer size after it becomes known.
Recommendations
To run UIDT jobs effectively, do the following:
Clearly define network constraints and job execution constraints to specify when the job should be executed.
Execute the task asynchronously in
onStartJob(); for example, you can do this by using a coroutine. If you don't run the task asynchronously, the work runs on the main thread and might block it, which can cause an ANR.To avoid running the job longer than necessary, call
jobFinished()when the transfer finishes, whether it succeeds or fails. That way, the job doesn't run longer than necessary. To discover why a job was stopped, implement theonStopJob()callback method and callJobParameters.getStopReason().
Abwärtskompatibilität
There is currently no Jetpack library that supports UIDT jobs. For this reason, we recommend that you gate your change with code that verifies that you're running on Android 14 or higher. On lower Android versions, you can use WorkManager's foreground service implementation as a fallback approach.
Here's an example of code that checks for the appropriate system version:
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
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
To support jobs running at optimal points, Android offers the ability to assign constraints to each job type. These constraints are available as of Android 13.
Note: The following table only compares the constraints that vary between each job type. See JobScheduler developer page or work constraints for all constraints.
The following table shows the different job types that support a given job constraint, as well as the set of job constraints that WorkManager supports. Use the search bar before the table to filter the table by the name of a job constraint method.
These are the constraints allowed with user-initiated data transfer jobs:
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:
- Fallstudie zur UIDT-Integration: Google Maps improved download reliability by 10% using user initiated data transfer API (Google Maps hat die Zuverlässigkeit von Downloads durch die Verwendung der User Initiated Data Transfer API um 10 % verbessert)