Przeprowadź migrację usług działających na pierwszym planie do zadań transferu danych inicjowanych przez użytkownika

Android 14 stosuje rygorystyczne reguły, gdy aplikacje mogą korzystać z usług na pierwszym planie.

W Androidzie 14 wprowadzamy też nowy interfejs API, który określa, że zadanie musi być zadaniem transferu danych inicjowanym przez użytkownika. Ten interfejs API jest przydatny w przypadkach, które wymagają dłuższego czasu przesyłania danych inicjowanego przez użytkownika, np. pobierania pliku z serwera zdalnego. Do takich zadań należy wykonać zainicjowane przez użytkownika zadanie transferu danych.

Zadania transferu danych inicjowane przez użytkownika są uruchamiane przez użytkownika. Te zadania wymagają powiadomienia, są uruchamiane natychmiast i mogą być uruchamiane przez dłuższy czas, jeśli zezwalają na to warunki systemowe. Możesz jednocześnie uruchomić kilka zadań przenoszenia danych inicjowanych przez użytkownika.

Zadania inicjowane przez użytkownika muszą być zaplanowane, gdy aplikacja jest widoczna dla użytkownika (lub gdy znajduje się w jednym z dozwolonych warunków). Gdy zostaną spełnione wszystkie ograniczenia, system operacyjny może wykonywać zadania inicjowane przez użytkownika (z uwzględnieniem ograniczeń dotyczących stanu systemu). System może też korzystać z podanego szacowanego rozmiaru ładunku, aby określić czas wykonywania zadania.

Uprawnienia dla zadań transferu danych inicjowanych przez użytkownika

Zadania transferu danych inicjowane przez użytkownika wymagają nowych uprawnień do uruchomienia: RUN_USER_INITIATED_JOBS. System automatycznie przyznaje to uprawnienie. Jeśli nie zadeklarujesz uprawnień w manifeście aplikacji, system wygeneruje SecurityException.

Proces planowania zadań transferu danych inicjowanych przez użytkownika

To run a user initiated job, do the following:

  1. If this is your first time declaring an API with JobScheduler, declare the JobService and associated permissions in your manifest. Also, define a concrete subclass of JobService for your data transfer:

    <service android:name="com.example.app.CustomTransferService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false">
            ...
    </service>
    
    class CustomTransferService : JobService() {
      ...
    }
    
  2. Declare the RUN_USER_INITIATED_JOBS permission in your manifest:

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. Call the new setUserInitiated() method when building a JobInfo object. It is also recommended that you offer a payload size estimate by calling setEstimatedNetworkBytes() while creating your job:

    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. Schedule the job before the transfer starts, while the application is visible or in the allowed conditions list:

    val jobScheduler: JobScheduler =
        context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    jobScheduler.schedule(jobInfo)
    
  5. When the job is being executed, ensure you call setNotification() on the JobService object. This value is used to make the user aware that the job is running, both in the Task Manager and in the status bar notification area:

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

  7. When execution is complete, call jobFinished() to signal to the system that the job is complete, or that the job should be rescheduled.

Zadania transferu danych inicjowane przez użytkownika można zatrzymać

Zarówno użytkownik, jak i system mogą zatrzymać zadania transferu zainicjowane przez użytkownika.

Przez użytkownika w Menedżerze zadań

Użytkownik może zatrzymać zainicjowane przez użytkownika zadanie przenoszenia danych widoczne w Menedżerze zadań.

Gdy użytkownik kliknie Zatrzymaj, system wykona te czynności:

  • Natychmiast zatrzymuje proces aplikacji, w tym wszystkie uruchomione zadania i usługi na pierwszym planie.
  • Nie wywołuje onStopJob() w przypadku żadnego uruchomionego zadania.
  • Zapobiega zmianie harmonogramu zadań widocznych dla użytkowników.

Z tego powodu zalecamy dodanie elementów sterujących w przesłanym powiadomieniu o zadaniu, aby umożliwić płynne zatrzymanie i przełożenie zadania.

Pamiętaj, że w specjalnych okolicznościach przycisk Zatrzymaj nie wyświetla się obok zadania w Menedżerze zadań lub w ogóle nie jest w nim wyświetlane.

System

Unlike regular jobs, user-initiated data transfer jobs are unaffected by App Standby Buckets quotas. However, the system still stops the job if any of the following conditions occur:

  • A developer-defined constraint is no longer met.
  • The system determines that the job has run for longer than necessary to complete the data transfer task.
  • The system needs to prioritize system health and stop jobs due to increased thermal state.
  • The app process is killed due to low device memory.

When the job is stopped by the system (not by the low-memory case), the system calls onStopJob(), and the system retries the job at a time that the system deems to be optimal. Check that your app can persist data transfer state, even if onStopJob() isn't called, and that your app can restore this state when onStartJob() is called again.

Warunki dozwolone w przypadku planowania zadań transferu danych inicjowanych przez użytkownika

Aplikacje mogą uruchamiać zadanie transferu danych inicjowane przez użytkownika tylko wtedy, gdy jest ona w widocznym oknie lub gdy są spełnione określone warunki. Aby określić, kiedy można zaplanować zadanie przenoszenia danych inicjowane przez użytkownika, system stosuje tę samą listę warunków, które w szczególnych przypadkach pozwalają aplikacjom uruchamiać aktywność w tle. W szczególności ta lista warunków nie jest tym samym co zestaw wykluczeń w przypadku ograniczeń usług działających w tle uruchamianych w tle.

Wyjątki od poprzedniego oświadczenia:

  • Jeśli aplikacja może uruchamiać działania w tle, może też uruchamiać w tle inicjowane przez użytkownika zadania przenoszenia danych.
  • Jeśli w przypadku aplikacji, która znajduje się w tylnym stosie istniejącego zadania na ekranie Ostatnie, nie można uruchomić zadania przenoszenia danych inicjowanego przez użytkownika.

Jeśli zadanie zostanie zaplanowane w innym czasie, którego nie ma na liście dozwolonych warunków, zadanie nie powiedzie się i zwrócony zostanie kod błędu RESULT_FAILURE.

Ograniczenia dozwolone w przypadku zadań transferu danych inicjowanych przez użytkownika

Aby ułatwić wykonywanie zadań działających w optymalnych punktach, Android umożliwia przypisywanie ograniczeń do każdego typu zlecenia. Te ograniczenia są już dostępne od wersji Androida 13.

Uwaga: w tabeli poniżej porównano ograniczenia, które różnią się w zależności od typu zadania. Wszystkie ograniczenia znajdziesz na stronie dewelopera JobScheduler lub na stronie o ograniczeniach związanych z pracą.

W tabeli poniżej znajdziesz różne typy zadań, które obsługują określone ograniczenie zadań, a także zestaw ograniczeń zadań obsługiwanych przez WorkManager. Użyj paska wyszukiwania przed tabelą, aby przefiltrować tabelę według nazwy metody ograniczania zadania.

Oto ograniczenia dozwolone w zadaniach przenoszenia danych inicjowanych przez użytkownika:

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

Testowanie

The following list shows some steps on how to test your app's jobs manually:

  • To get the job ID, get the value that is defined upon the job being built.
  • To run a job immediately, or to retry a stopped job, run the following command in a terminal window:

    adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
    
  • To simulate the system force-stopping a job (due to system health or out-of-quota conditions), run the following command in a terminal window:

    adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID