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

Aby uruchomić zadanie zainicjowane przez użytkownika, wykonaj te czynności:

  1. Jeśli po raz pierwszy deklarujesz interfejs API za pomocą JobScheduler, zadeklaruj uprawnienie JobService i powiązane z nim uprawnienia w pliku manifestu. Zdefiniuj też konkretną podklasę typu JobService na potrzeby przesyłania danych:

    <service android:name="com.example.app.CustomTransferService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false">
            ...
    </service>
    
    class CustomTransferService : JobService() {
      ...
    }
    
  2. Zadeklaruj w pliku manifestu uprawnienie RUN_USER_INITIATED_JOBS:

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. Wywołaj nową metodę setUserInitiated() podczas tworzenia obiektu JobInfo. Zalecamy też podanie szacowanego rozmiaru ładunku, wywołując setEstimatedNetworkBytes() podczas tworzenia zadania:

    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. Zaplanuj zadanie przed rozpoczęciem przenoszenia, gdy aplikacja jest widoczna lub znajduje się na liście dozwolonych warunków:

    val jobScheduler: JobScheduler =
        context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    jobScheduler.schedule(jobInfo)
    
  5. Pamiętaj, by podczas wykonywania zadania wywołać setNotification() w obiekcie JobService. Dzięki tej wartości użytkownik wie, że zadanie jest uruchomione – zarówno w Menedżerze zadań, jak i w obszarze powiadomień na pasku stanu:

    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. Okresowo aktualizuj powiadomienie, aby informować użytkownika o stanie i postępach zadania. Jeśli przed zaplanowaniem zadania nie możesz określić rozmiaru transferu lub musisz zaktualizować szacowany rozmiar transferu, użyj nowego interfejsu API (updateEstimatedNetworkBytes()), aby zaktualizować rozmiar transferu, gdy stanie się on znany.

  7. Po zakończeniu wykonywania wywołaj polecenie jobFinished(), aby zasygnalizować systemowi, że zadanie zostało ukończone lub że należy je przełożyć.

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

W przeciwieństwie do zwykłych zadań limity zasobników gotowości aplikacji nie mają wpływu na zadania transferu danych inicjowane przez użytkownika. System zatrzyma jednak zadanie, jeśli wystąpi dowolny z tych warunków:

  • Ograniczenie zdefiniowane przez dewelopera nie jest już spełnione.
  • System ustala, że zadanie działało dłużej niż jest to konieczne do ukończenia zadania transferu danych.
  • System musi traktować priorytetowo stan systemu i zatrzymywać zadania z powodu zwiększonej temperatury.
  • Proces aplikacji jest przerywany z powodu zbyt małej ilości pamięci urządzenia.

Gdy zadanie zostanie zatrzymane przez system (a nie w przypadku małej ilości pamięci), system wywoła onStopJob(), a system ponawia próbę w momencie, gdy system uzna to za optymalne. Sprawdź, czy aplikacja może utrzymywać stan transferu danych, nawet jeśli funkcja onStopJob() nie jest wywoływana, oraz czy aplikacja może przywrócić ten stan po ponownym wywołaniu funkcji onStartJob().

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

Na tej liście znajdziesz kilka kroków, które pozwolą Ci ręcznie przetestować zadania aplikacji:

  • Aby uzyskać identyfikator zadania, pobierz wartość zdefiniowaną podczas tworzenia zadania.
  • Aby natychmiast uruchomić zadanie lub ponowić próbę zatrzymanego zadania, uruchom to polecenie w oknie terminala:

    adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
    
  • Aby symulować wymuszanie przez system zadania (ze względu na sprawność systemu lub przekroczenie limitu), uruchom w oknie terminala to polecenie:

    adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID