Dienste im Vordergrund zu vom Nutzer initiierten Datenübertragungsjobs migrieren

Android 14 applies strict rules on when apps are allowed to use foreground services.

Also in Android 14 we are introducing a new API to specify that a job must be a user-initiated data transfer job. This API is helpful for use cases that require longer-duration, user-initiated transferring of data, such as downloading a file from a remote server. These types of tasks should use a user-initiated data transfer job.

User-initiated data transfer jobs are started by the user. These jobs require a notification, start immediately, and may be able to run for an extended period of time as system conditions allow. You can run several user-initiated data transfer jobs concurrently.

User initiated jobs must be scheduled while the application is visible to the user (or in one of the allowed conditions). After all constraints are met, user initiated jobs can be executed by the OS, subject to system health restrictions. The system may also use the provided estimated payload size to determine how long the job executes.

Berechtigung für vom Nutzer initiierte Datenübertragungsjobs

Für vom Nutzer initiierte Datenübertragungsjobs ist eine neue Berechtigung zum Ausführen erforderlich: RUN_USER_INITIATED_JOBS. Das System erteilt diese Berechtigung automatisch. Das System gibt einen SecurityException aus, wenn du die Berechtigung nicht in deinem App-Manifest deklariert hast.

Prozess zum Planen von vom Nutzer initiierten Datenübertragungsjobs

So führen Sie einen vom Nutzer initiierten Job aus:

  1. Wenn Sie zum ersten Mal eine API mit JobScheduler deklarieren, deklarieren Sie JobService und die zugehörigen Berechtigungen in Ihrem Manifest. Definieren Sie außerdem eine konkrete abgeleitete Klasse von JobService für die Datenübertragung:

    <service android:name="com.example.app.CustomTransferService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false">
            ...
    </service>
    
    class CustomTransferService : JobService() {
      ...
    }
    
  2. Deklariere die Berechtigung „RUN_USER_INITIATED_JOBS“ in deinem Manifest:

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. Rufen Sie die neue setUserInitiated()-Methode auf, wenn Sie ein JobInfo-Objekt erstellen. Es wird auch empfohlen, eine Schätzung der Nutzlastgröße anzubieten. Dazu rufen Sie beim Erstellen des Jobs setEstimatedNetworkBytes() auf:

    val networkRequestBuilder = NetworkRequest.Builder()
            .addCapability(NET_CAPABILITY_INTERNET)
            .addCapability(NET_CAPABILITY_NOT_METERED)
            // Add or remove capabilities based on your requirements
            .build()
    
    val jobInfo = JobInfo.Builder()
            // ...
            .setUserInitiated(true)
            .setRequiredNetwork(networkRequestBuilder.build())
            .setEstimatedNetworkBytes(1024 * 1024 * 1024)
            // ...
            .build()
    
  4. Planen Sie den Job vor dem Start der Übertragung, während die Anwendung sichtbar ist oder in der Liste der zulässigen Bedingungen aufgeführt ist:

    val jobScheduler: JobScheduler =
        context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    jobScheduler.schedule(jobInfo)
    
  5. Wenn der Job ausgeführt wird, müssen Sie setNotification() für das JobService-Objekt aufrufen. Mit diesem Wert wird sowohl im Task-Manager als auch im Benachrichtigungsbereich der Statusleiste darauf hingewiesen, dass der Job ausgeführt wird:

    class CustomTransferService : JobService() {
      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)
          // Do the job execution.
      }
    }
    
  6. Aktualisieren Sie die Benachrichtigung regelmäßig, um den Nutzer über den Status und den Fortschritt des Jobs zu informieren. Wenn Sie die Übertragungsgröße vor dem Planen des Jobs nicht ermitteln können oder die geschätzte Übertragungsgröße aktualisieren müssen, verwenden Sie die neue API updateEstimatedNetworkBytes(), um die Übertragungsgröße zu aktualisieren, nachdem sie bekannt ist.

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

Vom Nutzer initiierte Datenübertragungsjobs können angehalten werden

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

Vom Nutzer aus dem Task-Manager

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

Wenn der Nutzer auf Stopp drückt, geht das System so vor:

  • Beendet den Prozess der Anwendung sofort, einschließlich aller anderen Jobs oder Dienste im Vordergrund, die ausgeführt werden.
  • Ruft onStopJob() für laufende Jobs nicht auf.
  • Verhindert, dass für Nutzer sichtbare Jobs verschoben werden.

Aus diesen Gründen wird empfohlen, in der Benachrichtigung, die für den Job veröffentlicht wird, Steuerelemente bereitzustellen, damit der Job ordnungsgemäß beendet und neu geplant werden kann.

Beachten Sie, dass unter besonderen Bedingungen die Schaltfläche Beenden neben dem Job im Task-Manager nicht angezeigt wird oder der Job im Task-Manager überhaupt nicht angezeigt wird.

Durch das System

Im Gegensatz zu normalen Jobs sind vom Nutzer initiierte Datenübertragungsjobs von Kontingenten für App-Standby-Buckets nicht betroffen. Das System beendet 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 nötig ausgeführt wurde, um die Datenübertragungsaufgabe abzuschließen.
  • Das System muss den Systemzustand priorisieren und Jobs aufgrund eines erhöhten Thermalzustands beenden.
  • Der App-Prozess wird aufgrund des geringen Gerätespeichers abgebrochen.

Wenn der Job vom System (nicht aufgrund des geringen Arbeitsspeichers) beendet wird, ruft das System onStopJob() auf und wiederholt den Job zu einem Zeitpunkt, den das System für optimal hält. Prüfen Sie, ob die Anwendung den Datenübertragungsstatus beibehalten kann, auch wenn onStopJob() nicht aufgerufen wird, und ob die Anwendung diesen Status wiederherstellen kann, wenn onStartJob() noch einmal aufgerufen wird.

Zulässige Bedingungen für die Planung von vom Nutzer initiierten Datenübertragungsjobs

Anwendungen können einen vom Nutzer initiierten Datenübertragungsjob nur starten, wenn sich die Anwendung im sichtbaren Fenster befindet oder bestimmte Bedingungen erfüllt sind. Das System wendet dieselbe Liste von Bedingungen an, die es Apps ermöglichen, in Sonderfällen eine Aktivität aus dem Hintergrund zu starten, um festzustellen, wann ein vom Nutzer initiierter Datenübertragungsjob geplant werden kann. Diese Liste von Bedingungen ist nicht mit den Ausnahmen für Einschränkungen im Vordergrund identisch, die im Hintergrund gestartet wurden.

Es gelten folgende Ausnahmen:

  • Wenn eine App Aktivitäten im Hintergrund starten kann, kann sie auch vom Nutzer initiierte Datenübertragungsjobs im Hintergrund starten.
  • Wenn eine Anwendung im Back-Stack einer vorhandenen Aufgabe auf dem Bildschirm Recents (Zuletzt verwendet) eine Aktivität enthält, kann damit kein vom Nutzer initiierter Datenübertragungsjob ausgeführt werden.

Wenn der Job zu einem Zeitpunkt geplant wurde, der nicht in der Liste der zulässigen Bedingungen aufgeführt ist, schlägt er fehl und gibt den Fehlercode RESULT_FAILURE zurück.

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

Damit Jobs an optimalen Punkten ausgeführt werden können, bietet Android die Möglichkeit, jedem Jobtyp Einschränkungen zuzuweisen. Diese Einschränkungen sind bereits ab Android 13 verfügbar.

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

In der folgenden Tabelle finden Sie die verschiedenen Arten von Dienstleistungen, die eine bestimmte Jobeinschränkung unterstützen, sowie die von WorkManager unterstützten Jobeinschränkungen. Mit der Suchleiste vor der Tabelle können Sie die Tabelle nach dem Namen einer Jobeinschränkungsmethode filtern.

Folgende Einschränkungen sind für vom Nutzer initiierte Datenübertragungsjobs 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 zu erhalten, rufen Sie den Wert ab, der beim Erstellen des Jobs definiert wurde.
  • Führen Sie den folgenden Befehl in einem Terminalfenster aus, um einen Job sofort auszuführen oder einen angehaltenen Job noch einmal auszuführen:

    adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
    
  • Führen Sie den folgenden Befehl in einem Terminalfenster aus, um das Beenden eines Jobs durch das System (aufgrund des Systemzustands oder aufgrund von Bedingungen außerhalb des Kontingents) zu simulieren:

    adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID