Uwaga: zalecamy WorkManager jako zalecane rozwiązanie w większości przypadków użycia związanych z przetwarzaniem w tle. Zapoznaj się z przewodnikiem dotyczącym przetwarzania w tle, aby sprawdzić, które rozwiązanie jest dla Ciebie najlepsze.
Komponent adaptera synchronizacji w aplikacji zawiera kod dla zadań przesyłających dane między urządzeniem a serwerem. Na podstawie harmonogramu i aktywatorów podanych w aplikacji platforma adaptera synchronizacji uruchamia kod w komponencie adaptera synchronizacji. Aby dodać do aplikacji komponent adaptera synchronizacji, musisz dodać te elementy:
- Zsynchronizuj klasę adaptera.
- Klasa, która pakuje kod transferu danych w interfejs zgodny ze platformą adaptera synchronizacji.
-
Powiązano
Service
. - Komponent, który umożliwia platformie adaptera synchronizacji uruchamianie kodu w klasie adaptera synchronizacji.
- Plik XML metadanych adaptera synchronizacji.
- Plik zawierający informacje o adapterze synchronizacji. Platforma odczytuje ten plik, aby dowiedzieć się, jak wczytać i zaplanować transfer danych.
- Deklaracje w manifeście aplikacji
- Plik XML deklarujący powiązaną usługę i wskazujący synchronizację metadanych konkretnego adaptera.
Z tej lekcji dowiesz się, jak zdefiniować te elementy.
Tworzenie klasy adaptera synchronizacji
W tej części lekcji dowiesz się, jak utworzyć klasę adaptera synchronizacji, która zawiera kod transferu danych. Utworzenie klasy obejmuje rozszerzenie klasy podstawowej adaptera synchronizacji, definiowanie jej konstruktorów i wdrożenie metody, w której definiuje się zadania przenoszenia danych.
Rozszerz klasę podstawowej adaptera synchronizacji
Aby utworzyć komponent adaptera synchronizacji, zacznij od rozszerzenia AbstractThreadedSyncAdapter
i napisania jego konstruktorów. Używaj konstruktorów do uruchamiania zadań konfiguracji za każdym razem, gdy komponent adaptera synchronizacji jest tworzony od podstaw – tak samo jak robisz to przy użyciu Activity.onCreate()
do konfigurowania działania. Jeśli na przykład Twoja aplikacja korzysta z usług dostawcy treści do przechowywania danych, użyj konstruktorów, aby uzyskać wystąpienie ContentResolver
. Na platformie Androida w wersji 3.0 dodano drugą formę konstruktora w celu obsługi argumentu parallelSyncs
, więc aby zachować zgodność, musisz utworzyć 2 formy konstruktora.
Uwaga: platforma adaptera synchronizacji została zaprojektowana do współpracy z komponentami adaptera synchronizacji, które są instancjami pojedynczymi. Tworzenie instancji komponentu adaptera synchronizacji zostało szczegółowo omówione w sekcji Przypisywanie adaptera synchronizacji z platformą.
Z przykładu poniżej dowiesz się, jak zaimplementować funkcję AbstractThreadedSyncAdapter
i jej konstruktory:
Kotlin
/** * Handle the transfer of data between a server and an * app, using the Android sync adapter framework. */ class SyncAdapter @JvmOverloads constructor( context: Context, autoInitialize: Boolean, /** * Using a default argument along with @JvmOverloads * generates constructor for both method signatures to maintain compatibility * with Android 3.0 and later platform versions */ allowParallelSyncs: Boolean = false, /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ val mContentResolver: ContentResolver = context.contentResolver ) : AbstractThreadedSyncAdapter(context, autoInitialize, allowParallelSyncs) { ... }
Java
/** * Handle the transfer of data between a server and an * app, using the Android sync adapter framework. */ public class SyncAdapter extends AbstractThreadedSyncAdapter { ... // Global variables // Define a variable to contain a content resolver instance ContentResolver contentResolver; /** * Set up the sync adapter */ public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ contentResolver = context.getContentResolver(); } ... /** * Set up the sync adapter. This form of the * constructor maintains compatibility with Android 3.0 * and later platform versions */ public SyncAdapter( Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ contentResolver = context.getContentResolver(); ... }
Dodawanie kodu przenoszenia danych
Komponent adaptera synchronizacji nie przenosi danych automatycznie. Zamiast tego zawiera on kod transferu danych, dzięki czemu platforma adaptera synchronizacji może uruchamiać przesyłanie danych w tle, bez angażowania Twojej aplikacji. Gdy platforma będzie gotowa do synchronizacji danych aplikacji, wywoła implementację metody onPerformSync()
.
Aby ułatwić przesyłanie danych z głównego kodu aplikacji do komponentu adaptera synchronizacji, platforma adaptera synchronizacji wywołuje onPerformSync()
przy użyciu tych argumentów:
- Konto
- Obiekt
Account
powiązany ze zdarzeniem, które uruchomiło adapter synchronizacji. Jeśli serwer nie używa kont, nie musisz używać informacji z tego obiektu. - Dodatkowe treści
-
Element
Bundle
zawierający flagi wysłane przez zdarzenie, które uruchomiło adapter synchronizacji. - Urząd
- Uprawnienia dostawcy treści w systemie. Twoja aplikacja musi mieć dostęp do tego dostawcy. Zwykle urząd odpowiada dostawcy treści w Twojej aplikacji.
- Klient dostawcy treści
-
Wartość
ContentProviderClient
dla dostawcy treści, na którą wskazuje argument autoryzacyjny.ContentProviderClient
to prosty interfejs publiczny dla dostawcy treści. Ma te same podstawowe funkcje coContentResolver
. Jeśli korzystasz z usług dostawcy treści do przechowywania danych o aplikacji, możesz połączyć się z nim za pomocą tego obiektu. W przeciwnym razie możesz go zignorować. - Wynik synchronizacji
- Obiekt
SyncResult
używany do wysyłania informacji do platformy adaptera synchronizacji.
Ten fragment kodu pokazuje ogólną strukturę strony onPerformSync()
:
Kotlin
/* * Specify the code you want to run in the sync adapter. The entire * sync adapter runs in a background thread, so you don't have to set * up your own background processing. */ override fun onPerformSync( account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult ) { /* * Put the data transfer code here. */ }
Java
/* * Specify the code you want to run in the sync adapter. The entire * sync adapter runs in a background thread, so you don't have to set * up your own background processing. */ @Override public void onPerformSync( Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { /* * Put the data transfer code here. */ }
Rzeczywista implementacja onPerformSync()
zależy od wymagań aplikacji w zakresie synchronizacji danych i protokołów połączenia z serwerem, jednak musisz wykonać kilka ogólnych zadań:
- Łączenie z serwerem
- Możesz założyć, że sieć jest dostępna w momencie rozpoczęcia przenoszenia danych, ale platforma adaptera synchronizacji nie łączy się automatycznie z serwerem.
- Pobieranie i przesyłanie danych
- Adapter synchronizacji nie automatyzuje żadnych zadań związanych z przesyłaniem danych. Jeśli chcesz pobrać dane z serwera i przechowywać je u dostawcy treści, musisz podać kod, który wysyła żądanie danych, pobiera je i wstawia u dostawcy. Analogicznie, jeśli chcesz wysłać dane na serwer, musisz odczytać je z pliku, bazy danych lub dostawcy i wysłać niezbędne żądanie przesłania. Musisz też obsługiwać błędy sieci, które pojawiają się podczas przenoszenia danych.
- radzenie sobie z konfliktami danych i określanie ich aktualności,
- Adapter synchronizacji nie obsługuje automatycznie konfliktów między danymi na serwerze a danymi na urządzeniu. Nie wykrywa też automatycznie, czy dane na serwerze są nowsze niż dane na urządzeniu, i odwrotnie. Zamiast tego musisz udostępnić własne algorytmy, które będą postępować w takiej sytuacji.
- Wyczyść dane.
- Zawsze zamykaj połączenia z serwerem oraz czyść pliki tymczasowe i pamięć podręczną na końcu przesyłania danych.
Uwaga: platforma adaptera synchronizacji uruchamia onPerformSync()
w wątku w tle, więc nie musisz konfigurować własnego przetwarzania w tle.
Oprócz zadań związanych z synchronizacją warto też połączyć zwykłe zadania związane z siecią i dodać je do zadania onPerformSync()
.
Koncentrując wszystkie zadania sieciowe na tej metodzie, oszczędzasz baterię potrzebną do uruchamiania i zatrzymywania interfejsów sieciowych. Więcej informacji o zwiększaniu wydajności dostępu do sieci znajdziesz w klasie szkoleniowej Przenoszenie danych bez wyczerpywania baterii. Opisano w niej kilka zadań związanych z dostępem do sieci, które możesz uwzględnić w kodzie transferu danych.
Powiąż adapter synchronizacji z platformą
Twój kod transferu danych jest teraz zawarty w komponencie adaptera synchronizacji, ale musisz zapewnić platformie dostęp do kodu. Aby to zrobić, musisz utworzyć powiązany obiekt Service
, który przekazuje do platformy specjalny obiekt powiązania Androida z komponentu adaptera synchronizacji. Dzięki temu obiektowi powiązania platforma może wywołać metodę onPerformSync()
i przekazać do niej dane.
Utwórz wystąpienie komponentu adaptera synchronizacji jako single w metodzie onCreate()
usługi. Tworząc instancję komponentu w onCreate()
, odkładasz jego utworzenie do momentu uruchomienia usługi, co ma miejsce, gdy platforma po raz pierwszy spróbuje uruchomić przesyłanie danych. Musisz utworzyć instancję komponentu w sposób bezpieczny w wątku, na wypadek gdyby platforma adaptera synchronizacji umieszczała w kolejce wiele uruchomień adaptera synchronizacji w odpowiedzi na aktywatory lub harmonogramy.
Ten fragment kodu pokazuje na przykład, jak utworzyć klasę, która implementuje powiązany Service
, tworzy instancję komponentu adaptera synchronizacji i pobiera obiekt powiązania Androida:
Kotlin
package com.example.android.syncadapter /** * Define a Service that returns an [android.os.IBinder] for the * sync adapter class, allowing the sync adapter framework to call * onPerformSync(). */ class SyncService : Service() { /* * Instantiate the sync adapter object. */ override fun onCreate() { /* * Create the sync adapter as a singleton. * Set the sync adapter as syncable * Disallow parallel syncs */ synchronized(sSyncAdapterLock) { sSyncAdapter = sSyncAdapter ?: SyncAdapter(applicationContext, true) } } /** * Return an object that allows the system to invoke * the sync adapter. * */ override fun onBind(intent: Intent): IBinder { /* * Get the object that allows external processes * to call onPerformSync(). The object is created * in the base class code when the SyncAdapter * constructors call super() * * We should never be in a position where this is called before * onCreate() so the exception should never be thrown */ return sSyncAdapter?.syncAdapterBinder ?: throw IllegalStateException() } companion object { // Storage for an instance of the sync adapter private var sSyncAdapter: SyncAdapter? = null // Object to use as a thread-safe lock private val sSyncAdapterLock = Any() } }
Java
package com.example.android.syncadapter; /** * Define a Service that returns an <code><a href="/reference/android/os/IBinder.html">IBinder</a></code> for the * sync adapter class, allowing the sync adapter framework to call * onPerformSync(). */ public class SyncService extends Service { // Storage for an instance of the sync adapter private static SyncAdapter sSyncAdapter = null; // Object to use as a thread-safe lock private static final Object sSyncAdapterLock = new Object(); /* * Instantiate the sync adapter object. */ @Override public void onCreate() { /* * Create the sync adapter as a singleton. * Set the sync adapter as syncable * Disallow parallel syncs */ synchronized (sSyncAdapterLock) { if (sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext(), true); } } } /** * Return an object that allows the system to invoke * the sync adapter. * */ @Override public IBinder onBind(Intent intent) { /* * Get the object that allows external processes * to call onPerformSync(). The object is created * in the base class code when the SyncAdapter * constructors call super() */ return sSyncAdapter.getSyncAdapterBinder(); } }
Uwaga: bardziej szczegółowy przykład usługi powiązanej z adapterem synchronizacji znajdziesz w przykładowej aplikacji.
Dodaj konto wymagane przez zasady.
Platforma adaptera synchronizacji wymaga, aby każdy adapter synchronizacji miał określony typ konta. Wartość rodzaju konta zadeklarowano w sekcji Dodawanie pliku metadanych Authenticator. Teraz musisz skonfigurować ten rodzaj konta w systemie Android. Aby skonfigurować rodzaj konta, dodaj konto zastępcze korzystające z tego typu konta, wywołując addAccountExplicitly()
.
Najlepszym miejscem do jej wywołania jest metoda onCreate()
związana z otwieraniem aplikacji. Poniższy fragment kodu pokazuje, jak to zrobić:
Kotlin
... // Constants // The authority for the sync adapter's content provider const val AUTHORITY = "com.example.android.datasync.provider" // An account type, in the form of a domain name const val ACCOUNT_TYPE = "example.com" // The account name const val ACCOUNT = "placeholderaccount" ... class MainActivity : FragmentActivity() { // Instance fields private lateinit var mAccount: Account ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Create the placeholder account mAccount = createSyncAccount() ... } ... /** * Create a new placeholder account for the sync adapter */ private fun createSyncAccount(): Account { val accountManager = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager return Account(ACCOUNT, ACCOUNT_TYPE).also { newAccount -> /* * Add the account and account type, no password or user data * If successful, return the Account object, otherwise report an error. */ if (accountManager.addAccountExplicitly(newAccount, null, null)) { /* * If you don't set android:syncable="true" in * in your <provider> element in the manifest, * then call context.setIsSyncable(account, AUTHORITY, 1) * here. */ } else { /* * The account exists or some other error occurred. Log this, report it, * or handle it internally. */ } } } ... }
Java
public class MainActivity extends FragmentActivity { ... ... // Constants // The authority for the sync adapter's content provider public static final String AUTHORITY = "com.example.android.datasync.provider"; // An account type, in the form of a domain name public static final String ACCOUNT_TYPE = "example.com"; // The account name public static final String ACCOUNT = "placeholderaccount"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Create the placeholder account mAccount = CreateSyncAccount(this); ... } ... /** * Create a new placeholder account for the sync adapter * * @param context The application context */ public static Account CreateSyncAccount(Context context) { // Create the account type and default account Account newAccount = new Account( ACCOUNT, ACCOUNT_TYPE); // Get an instance of the Android account manager AccountManager accountManager = (AccountManager) context.getSystemService( ACCOUNT_SERVICE); /* * Add the account and account type, no password or user data * If successful, return the Account object, otherwise report an error. */ if (accountManager.addAccountExplicitly(newAccount, null, null)) { /* * If you don't set android:syncable="true" in * in your <provider> element in the manifest, * then call context.setIsSyncable(account, AUTHORITY, 1) * here. */ } else { /* * The account exists or some other error occurred. Log this, report it, * or handle it internally. */ } } ... }
Dodaj plik metadanych adaptera synchronizacji
Aby podłączyć komponent adaptera synchronizacji do platformy, musisz udostępnić platformę z metadanymi opisującymi ten komponent i dodatkowymi flagami. Metadane określają typ konta utworzony dla adaptera synchronizacji, deklarują urząd dostawcy treści powiązany z Twoją aplikacją, kontrolują część interfejsu użytkownika systemu związaną z adapterami synchronizacji oraz deklarują inne flagi związane z synchronizacją. Zadeklaruj te metadane w specjalnym pliku XML przechowywanym w katalogu /res/xml/
w projekcie aplikacji. Plikowi możesz nadać dowolną nazwę. Zazwyczaj ma on nazwę syncadapter.xml
.
Ten plik XML zawiera pojedynczy element XML <sync-adapter>
o następujących atrybutach:
android:contentAuthority
-
Urządzenie URI dla dostawcy treści. Jeśli w poprzedniej lekcji Tworzenie dostawcy treści Stub został przez Ciebie utworzony dostawcę treści jego aplikacji, użyj wartości określonej dla atrybutu
android:authorities
w elemencie<provider>
dodanym do pliku manifestu aplikacji. Ten atrybut został szczegółowo opisany w sekcji Deklarowanie dostawcy w pliku manifestu.
Jeśli przenosisz dane od dostawcy treści na serwer za pomocą adaptera synchronizacji, ta wartość powinna być taka sama jak wartość identyfikatora URI treści używanego w przypadku tych danych. Ta wartość jest również jednym z organów urzędowych podanych w atrybucieandroid:authorities
elementu<provider>
, który deklaruje dostawcę w manifeście aplikacji. android:accountType
- Rodzaj konta wymagany przez platformę adaptera synchronizacji. Wartość musi być taka sama jak wartość rodzaju konta podana podczas tworzenia pliku metadanych uwierzytelniania zgodnie z opisem w sekcji Dodawanie pliku metadanych Authenticator. Jest to również wartość podana dla stałej
ACCOUNT_TYPE
we fragmencie kodu w sekcji Add the Account required by the Framework. - Atrybuty ustawień
-
-
android:userVisible
- Ustawia widoczność typu konta adaptera synchronizacji. Domyślnie ikona i etykieta konta powiązane z typem konta są widoczne w sekcji Konta w systemowej aplikacji Ustawienia. Dlatego adapter synchronizacji musisz ustawić jako niewidoczny, chyba że masz konto lub domenę, które można łatwo powiązać z aplikacją. Jeśli ustawisz typ konta jako niewidoczny, nadal możesz zezwolić użytkownikom na sterowanie adapterem synchronizacji za pomocą interfejsu użytkownika w ramach jednej z czynności wykonywanych w aplikacji.
-
android:supportsUploading
- Umożliwia przesyłanie danych do chmury. Ustaw go na
false
, jeśli aplikacja tylko pobiera dane. -
android:allowParallelSyncs
- Umożliwia jednoczesne uruchamianie wielu instancji komponentu adaptera synchronizacji. Użyj tej opcji, jeśli Twoja aplikacja obsługuje wiele kont użytkowników i chcesz umożliwić wielu użytkownikom równoległe przenoszenie danych. Ta flaga nie będzie działać, jeśli nigdy nie uruchamiasz wielu transferów danych.
-
android:isAlwaysSyncable
- Wskazuje platformę adaptera synchronizacji, że może ona w dowolnym momencie uruchomić adapter synchronizacji. Jeśli chcesz automatycznie kontrolować, kiedy adapter synchronizacji ma działać, ustaw tę flagę na
false
, a następnie wywołajrequestSync()
, aby uruchomić adapter synchronizacji. Więcej informacji o używaniu adaptera synchronizacji znajdziesz w lekcji Uruchamianie adaptera synchronizacji.
-
Poniższy przykład przedstawia kod XML adaptera synchronizacji, który korzysta z jednego konta zastępczego i tylko do pobierania plików.
<?xml version="1.0" encoding="utf-8"?> <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:contentAuthority="com.example.android.datasync.provider" android:accountType="com.android.example.datasync" android:userVisible="false" android:supportsUploading="false" android:allowParallelSyncs="false" android:isAlwaysSyncable="true"/>
Zadeklaruj adapter synchronizacji w pliku manifestu
Po dodaniu do aplikacji komponentu adaptera synchronizacji musisz poprosić o uprawnienia związane z jego używaniem oraz zadeklarować dodany komponent Service
.
Komponent adaptera synchronizacji uruchamia kod, który przesyła dane między siecią a urządzeniem, więc musisz poprosić o dostęp do internetu. Aplikacja musi też prosić o uprawnienia do odczytu i zapisywania ustawień adaptera synchronizacji, aby można było automatycznie sterować adapterem synchronizacji z innych komponentów aplikacji. Musisz też poprosić o specjalne uprawnienie, które pozwoli aplikacji na użycie komponentu uwierzytelniającego utworzonego przez Ciebie w ramach lekcji Tworzenie Stub Authenticator.
Aby poprosić o te uprawnienia, dodaj do manifestu aplikacji jako elementy podrzędne <manifest>
:
-
android.permission.INTERNET
- Zezwala kodowi adaptera synchronizacji na dostęp do internetu, aby mógł pobrać dane z urządzenia lub przesłać je na serwer. Nie musisz ponownie dodawać tego uprawnienia, jeśli prosiłeś o nie wcześniej.
-
android.permission.READ_SYNC_SETTINGS
- Umożliwia aplikacji odczyt bieżących ustawień adaptera synchronizacji. Potrzebujesz tego uprawnienia na przykład, aby wywołać funkcję
getIsSyncable()
. -
android.permission.WRITE_SYNC_SETTINGS
- Umożliwia aplikacji kontrolowanie ustawień adaptera synchronizacji. Potrzebujesz tych uprawnień, aby ustawić okresowe uruchamianie adaptera synchronizacji za pomocą
addPeriodicSync()
. To uprawnienie nie jest wymagane do wywoływania funkcjirequestSync()
. Więcej informacji o używaniu adaptera synchronizacji znajdziesz w artykule Uruchamianie adaptera synchronizacji.
Ten fragment kodu pokazuje, jak dodać uprawnienia:
<manifest> ... <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> ... </manifest>
Aby na koniec zadeklarować granicę Service
, której platforma używa do interakcji z adapterem synchronizacji, dodaj ten kod XML do pliku manifestu aplikacji jako element podrzędny <application>
:
<service android:name="com.example.android.datasync.SyncService" android:exported="false" android:process=":sync"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> </service>
Element <intent-filter>
konfiguruje filtr, który jest aktywowany przez działanie intencji android.content.SyncAdapter
wysłane przez system w celu uruchomienia adaptera synchronizacji. Po uruchomieniu filtra system uruchamia utworzoną przez Ciebie usługę powiązaną – w tym przykładzie jest to SyncService
. Atrybut android:exported="false"
umożliwia dostęp do elementu Service
tylko aplikacji i systemowi. Atrybut android:process=":sync"
informuje system, że ma uruchomić Service
w globalnym udostępnionym procesie o nazwie sync
. Jeśli w aplikacji masz wiele adapterów synchronizacji, mogą oni korzystać z tego procesu, co zmniejsza nakład pracy.
Element <meta-data>
zawiera nazwę utworzonego wcześniej pliku XML metadanych adaptera synchronizacji.
Atrybut android:name
wskazuje, że te metadane dotyczą platformy adaptera synchronizacji. Element android:resource
określa nazwę pliku metadanych.
Masz już wszystkie komponenty adaptera synchronizacji. Z kolejnej lekcji dowiesz się, jak skonfigurować platformę adaptera synchronizacji, aby uruchamiała adapter synchronizacji w odpowiedzi na zdarzenia lub regularnie.