Uruchamianie adaptera synchronizacji

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.

Podczas poprzednich lekcji omówiliśmy, jak utworzyć komponent adaptera synchronizacji, który zawiera kod transferu danych, oraz jak dodawać kolejne komponenty, które umożliwiają podłączenie adaptera synchronizacji do systemu. Masz już wszystko, czego potrzebujesz do zainstalowania aplikacji zawierającej adapter synchronizacji, ale żaden z widocznych kodów nie obsługuje tej wtyczki.

Działanie adaptera synchronizacji powinno być ustalane na podstawie harmonogramu lub jako pośrednia konsekwencja jakiegoś zdarzenia. Możesz na przykład chcieć, aby adapter synchronizacji uruchamiał się regularnie, o określonej godzinie lub o określonej porze dnia. Możesz też uruchomić adapter synchronizacji, gdy dane zapisane na urządzeniu zostaną zmienione. Nie uruchamiaj adaptera synchronizacji w bezpośrednim wyniku działania użytkownika, bo nie zapewni to pełnego wykorzystania możliwości planowania platformy adaptera synchronizacji. Na przykład nie umieszczaj w interfejsie przycisku odświeżania.

Dostępne są te opcje włączania adaptera synchronizacji:

Gdy zmienią się dane serwera
Uruchom adapter synchronizacji w odpowiedzi na komunikat z serwera, który wskazuje, że dane serwerowe uległy zmianie. Ta opcja umożliwia odświeżanie danych z serwera na urządzeniu bez obniżania wydajności i zużywania baterii przez odpytywanie serwera.
Gdy zmienią się dane na urządzeniu
Uruchom adapter synchronizacji w przypadku zmiany danych na urządzeniu. Ta opcja umożliwia wysyłanie zmodyfikowanych danych z urządzenia na serwer i jest szczególnie przydatna, gdy chcesz mieć pewność, że na serwerze zawsze dostępne są najnowsze dane z urządzenia. Tę opcję możesz łatwo wdrożyć, jeśli faktycznie przechowujesz dane u dostawcy treści. Jeśli korzystasz z usług dostawcy wycinka kodu, wykrywanie zmian w danych może być trudniejsze.
W regularnych odstępach czasu
Uruchamiaj adapter synchronizacji po wygaśnięciu wybranego okresu lub uruchamiaj go codziennie o określonej godzinie.
Na żądanie
Uruchom adapter synchronizacji w odpowiedzi na działanie użytkownika. Aby jednak zapewnić użytkownikom jak najlepsze wrażenia, korzystaj przede wszystkim z jednej z bardziej automatycznych opcji. Korzystanie z automatycznych opcji pozwala oszczędzać baterię i zasoby sieciowe.

W pozostałej części tej lekcji szczegółowo opisano każdą z tych opcji.

Uruchamianie adaptera synchronizacji w przypadku zmiany danych serwera

Jeśli Twoja aplikacja przesyła dane z serwera, a dane serwera często się zmieniają, możesz użyć adaptera synchronizacji, by w odpowiedzi na zmiany danych wykonać pobieranie. Aby uruchomić adapter synchronizacji, poproś serwer o wysłanie specjalnej wiadomości na adres BroadcastReceiver w aplikacji. W odpowiedzi na tę wiadomość użyj wywołania ContentResolver.requestSync(), aby zasygnalizować platformę adaptera synchronizacji, aby uruchomić adapter synchronizacji.

Google Cloud Messaging (GCM) udostępnia komponenty serwera i urządzenia niezbędne do działania tego systemu przesyłania wiadomości. Używanie GCM do aktywowania transferów jest bardziej niezawodne i wydajniejsze niż serwery odpytywania stanu. Mimo że odpytywanie wymaga parametru Service, który jest zawsze aktywny, GCM używa parametru BroadcastReceiver, który jest aktywowany po otrzymaniu wiadomości. Chociaż odpytywanie w regularnych odstępach czasu zużywa baterię, nawet gdy nie są dostępne żadne aktualizacje, GCM wysyła komunikaty tylko wtedy, gdy są potrzebne.

Uwaga: jeśli używasz GCM do aktywowania adaptera synchronizacji przez komunikat do wszystkich urządzeń, na których jest zainstalowana Twoja aplikacja, pamiętaj, że otrzymują one wiadomość mniej więcej w tym samym czasie. Taka sytuacja może spowodować jednoczesne działanie wielu instancji adaptera synchronizacji, powodując przeciążenie serwera i sieci. Aby uniknąć tej sytuacji w przypadku przesyłania transmisji na wszystkie urządzenia, rozważ odłożenie uruchomienia adaptera synchronizacji na czas, który jest inny dla każdego urządzenia.

Ten fragment kodu pokazuje, jak uruchomić requestSync() w odpowiedzi na przychodzący komunikat GCM:

Kotlin

...
// Constants
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account type
const val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
const val ACCOUNT = "default_account"
// Incoming Intent key for extended data
const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"
...
class GcmBroadcastReceiver : BroadcastReceiver() {
    ...
    override fun onReceive(context: Context, intent: Intent) {
        // Get a GCM object instance
        val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context)
        // Get the type of GCM message
        val messageType: String? = gcm.getMessageType(intent)
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType
            && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null)
            ...
        }
        ...
    }
    ...
}

Java

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Incoming Intent key for extended data
    public static final String KEY_SYNC_REQUEST =
            "com.example.android.datasync.KEY_SYNC_REQUEST";
    ...
    @Override
    public void onReceive(Context context, Intent intent) {
        // Get a GCM object instance
        GoogleCloudMessaging gcm =
                GoogleCloudMessaging.getInstance(context);
        // Get the type of GCM message
        String messageType = gcm.getMessageType(intent);
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
            &&
            intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

Uruchamianie adaptera synchronizacji w przypadku zmiany danych dostawcy treści

Jeśli Twoja aplikacja zbiera dane u dostawcy treści i chcesz, aby serwer był aktualizowany po każdej aktualizacji dostawcy, możesz skonfigurować w aplikacji automatyczne uruchamianie adaptera synchronizacji. W tym celu rejestrujesz obserwatora jako dostawcę treści. Gdy dane u dostawcy treści ulegną zmianie, platforma dostawcy treści wywołuje obserwatora. W obserwatorze wywołaj requestSync(), aby nakazać platformie uruchomienie adaptera synchronizacji.

Uwaga: jeśli korzystasz z dostawcy treści skróconej, nie masz żadnych danych u tego dostawcy, a usługa onChange() nigdy nie jest wywoływana. W takim przypadku musisz udostępnić własny mechanizm wykrywania zmian w danych urządzenia. Mechanizm ten odpowiada też za wywoływanie funkcji requestSync() w przypadku zmiany danych.

Aby utworzyć obserwatora dla swojego dostawcy treści, rozszerz klasę ContentObserver i zaimplementuj obie formy metody onChange(). W onChange() wywołaj requestSync(), aby uruchomić adapter synchronizacji.

Aby zarejestrować obserwatora, przekaż go jako argument w wywołaniu funkcji registerContentObserver(). W wywołaniu tego wywołania musisz też przekazać identyfikator URI treści dla danych, które chcesz obserwować. Platforma dostawcy treści porównuje ten identyfikator URI zegarka z identyfikatorami URI treści przekazywanymi jako argumenty do metod ContentResolver, które modyfikują dostawcę, np. ContentResolver.insert(). W przypadku wystąpienia dopasowania wywoływana jest implementacja ContentObserver.onChange().

Ten fragment kodu pokazuje, jak zdefiniować ContentObserver, który wywołuje requestSync() po zmianie tabeli:

Kotlin

// Constants
// Content provider scheme
const val SCHEME = "content://"
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Path for the content provider table
const val TABLE_PATH = "data_table"
...
class MainActivity : FragmentActivity() {
    ...
    // A content URI for the content provider's data table
    private lateinit var uri: Uri
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver
    ...
    inner class TableObserver(...) : ContentObserver(...) {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        override fun onChange(selfChange: Boolean) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null)
        }

        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        override fun onChange(selfChange: Boolean, changeUri: Uri?) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(account, AUTHORITY, null)
        }
        ...
    }
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver object for your app
        mResolver = contentResolver
        // Construct a URI that points to the content provider data table
        uri = Uri.Builder()
                .scheme(SCHEME)
                .authority(AUTHORITY)
                .path(TABLE_PATH)
                .build()
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        val observer = TableObserver(false)
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider scheme
    public static final String SCHEME = "content://";
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Path for the content provider table
    public static final String TABLE_PATH = "data_table";
    // Account
    public static final String ACCOUNT = "default_account";
    // Global variables
    // A content URI for the content provider's data table
    Uri uri;
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    public class TableObserver extends ContentObserver {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        @Override
        public void onChange(boolean selfChange) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null);
        }
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        @Override
        public void onChange(boolean selfChange, Uri changeUri) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
        }
        ...
    }
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver object for your app
        mResolver = getContentResolver();
        // Construct a URI that points to the content provider data table
        uri = new Uri.Builder()
                  .scheme(SCHEME)
                  .authority(AUTHORITY)
                  .path(TABLE_PATH)
                  .build();
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        TableObserver observer = new TableObserver(false);
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer);
        ...
    }
    ...
}

Okresowo uruchamiaj adapter synchronizacji

Możesz okresowo uruchamiać adapter synchronizacji, ustawiając odstęp między uruchomieniami lub uruchamiając go o określonych porach dnia lub obydwie te czynności. Okresowe uruchamianie adaptera synchronizacji pozwala w przybliżeniu dopasować częstotliwość aktualizacji serwera.

Możesz też przesyłać dane z urządzenia, gdy serwer jest względnie nieaktywny, zaplanując działanie adaptera synchronizacji na noc. Większość użytkowników pozostawia urządzenie włączone i podłączone w nocy, więc ta godzina jest zwykle dostępna. Dodatkowo na urządzeniu nie są wykonywane inne zadania w tym samym czasie co adapter synchronizacji. Jednak przy takim podejściu musisz zadbać, by każde urządzenie inicjowało przesyłanie danych w nieco innym czasie. Jeśli wszystkie urządzenia korzystają z adaptera synchronizacji w tym samym czasie, może to spowodować przeciążenie sieci danych Twojego serwera i operatora sieci komórkowej.

Ogólnie mówiąc, uruchamianie okresowe ma sens, jeśli użytkownicy nie potrzebują natychmiastowych aktualizacji, ale spodziewają się regularnych aktualizacji. Okresowe uruchamianie ma sens również wtedy, gdy chcesz zrównoważyć dostępność aktualnych danych z wydajnością mniejszych uruchomień adaptera synchronizacji, które nie wykorzystują zasobów urządzenia nadmiernie.

Aby regularnie uruchamiać adapter synchronizacji, wywołaj addPeriodicSync(). Powoduje to zaplanowanie uruchomienia adaptera synchronizacji po upływie określonego czasu. Platforma adaptera synchronizacji musi uwzględniać uruchamianie innych adapterów synchronizacji i stara się zmaksymalizować wydajność baterii, dlatego czas, który upłynął, może być różny o kilka sekund. Ponadto platforma nie uruchomi adaptera synchronizacji, jeśli sieć będzie niedostępna.

Zwróć uwagę, że addPeriodicSync() nie włącza adaptera synchronizacji o określonej porze dnia. Aby adapter synchronizacji uruchamiał się mniej więcej o tej samej godzinie każdego dnia, ustaw powtarzający się alarm jako wyzwalacz. Powtarzające się alarmy zostały szczegółowo opisane w dokumentacji referencyjnej AlarmManager. Jeśli używasz metody setInexactRepeating() do ustawiania reguł o określonych porach dnia, które się różnią, i tak musisz ustawić losowe godziny rozpoczęcia, aby zapewnić, że adapter synchronizacji uruchamia się z różnych urządzeń i rozłożone w czasie.

Metoda addPeriodicSync() nie wyłącza setSyncAutomatically(), więc w stosunkowo krótkim czasie możesz uzyskać wiele uruchomień synchronizacji. W wywołaniu do addPeriodicSync() dozwolonych jest tylko kilka flag sterujących adaptera synchronizacji. Niedozwolone flagi są opisane w przywołanej dokumentacji funkcji addPeriodicSync().

Ten fragment kodu pokazuje, jak zaplanować okresowe uruchamianie adaptera synchronizacji:

Kotlin

// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account
const val ACCOUNT = "default_account"
// Sync interval constants
const val SECONDS_PER_MINUTE = 60L
const val SYNC_INTERVAL_IN_MINUTES = 60L
const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE
...
class MainActivity : FragmentActivity() {
    ...
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver for your app
        mResolver = contentResolver
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account
    public static final String ACCOUNT = "default_account";
    // Sync interval constants
    public static final long SECONDS_PER_MINUTE = 60L;
    public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
    public static final long SYNC_INTERVAL =
            SYNC_INTERVAL_IN_MINUTES *
            SECONDS_PER_MINUTE;
    // Global variables
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver for your app
        mResolver = getContentResolver();
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        ...
    }
    ...
}

Uruchamianie adaptera synchronizacji na żądanie

Uruchamianie adaptera synchronizacji w odpowiedzi na żądanie użytkownika jest najmniej zalecaną strategią uruchamiania adaptera synchronizacji. Platforma została zaprojektowana tak, aby oszczędzać baterię, gdy działa adaptery synchronizacji zgodnie z harmonogramem. Opcje, które uruchamiają synchronizację w odpowiedzi na zmiany danych, efektywnie zużywają baterię, ponieważ zasilanie jest wykorzystywane do dostarczania nowych danych.

Z kolei zezwolenie użytkownikom na synchronizację na żądanie oznacza, że synchronizacja działa samoczynnie, co nie wpływa na wykorzystanie zasobów sieci i energii. Ponadto synchronizacja na żądanie powoduje, że użytkownicy zgłaszają żądanie synchronizacji, nawet jeśli nie ma dowodów na to, że dane się zmieniły, a przeprowadzanie synchronizacji bez odświeżania danych jest nieefektywnym wykorzystaniem energii z baterii. Ogólnie aplikacja powinna używać innych sygnałów do aktywowania synchronizacji lub planować je w regularnych odstępach czasu, bez danych wejściowych użytkownika.

Jeśli jednak nadal chcesz używać adaptera synchronizacji na żądanie, ustaw flagi adaptera synchronizacji dla ręcznego uruchomienia adaptera synchronizacji, a następnie wywołaj ContentResolver.requestSync().

Uruchamiaj transfery na żądanie z tymi flagami:

SYNC_EXTRAS_MANUAL
Wymusza synchronizację ręczną. Platforma adaptera synchronizacji ignoruje dotychczasowe ustawienia, takie jak flaga ustawiona przez setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Wymusza natychmiastowe rozpoczęcie synchronizacji. Jeśli tego nie zrobisz, system może czekać kilka sekund przed uruchomieniem żądania synchronizacji, ponieważ będzie próbował zoptymalizować wykorzystanie baterii, planując wiele żądań w krótkim czasie.

Ten fragment kodu pokazuje, jak wywołać requestSync() w odpowiedzi na kliknięcie przycisku:

Kotlin

// Constants
// Content provider authority
val AUTHORITY = "com.example.android.datasync.provider"
// Account type
val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
val ACCOUNT = "default_account"
...
class MainActivity : FragmentActivity() {
    ...
    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = createSyncAccount()
        ...
    }

    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    fun onRefreshButtonClick(v: View) {
        // Pass the settings flags by inserting them in a bundle
        val settingsBundle = Bundle().apply {
            putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
            putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)
        }
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle)
    }

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY =
            "com.example.android.datasync.provider";
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = CreateSyncAccount(this);
        ...
    }
    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    public void onRefreshButtonClick(View v) {
        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
    }