Запустите адаптер синхронизации

Примечание. Мы рекомендовали WorkManager в качестве рекомендуемого решения для большинства случаев использования фоновой обработки. Обратитесь к руководству по фоновой обработке, чтобы узнать, какое решение лучше всего подойдет вам.

На предыдущих уроках этого курса вы узнали, как создать компонент адаптера синхронизации, инкапсулирующий код передачи данных, и как добавить дополнительные компоненты, позволяющие подключить адаптер синхронизации к системе. Теперь у вас есть все необходимое для установки приложения, включающего адаптер синхронизации, но ни один из представленных вами кодов на самом деле не запускает адаптер синхронизации.

Вам следует попытаться запустить адаптер синхронизации по расписанию или в результате косвенного результата какого-либо события. Например, вы можете захотеть, чтобы ваш адаптер синхронизации запускался по регулярному расписанию либо через определенный период времени, либо в определенное время дня. Вы также можете запустить адаптер синхронизации при изменении данных, хранящихся на устройстве. Вам следует избегать запуска адаптера синхронизации как прямого результата действия пользователя, поскольку в этом случае вы не сможете в полной мере воспользоваться возможностями планирования платформы адаптера синхронизации. Например, вам следует избегать использования кнопки обновления в пользовательском интерфейсе.

У вас есть следующие варианты запуска адаптера синхронизации:

Когда данные сервера меняются
Запустите адаптер синхронизации в ответ на сообщение от сервера, указывающее, что данные на сервере изменились. Этот параметр позволяет обновлять данные с сервера на устройство без снижения производительности и без траты времени автономной работы на опрос сервера.
Когда данные устройства меняются
Запускайте адаптер синхронизации при изменении данных на устройстве. Эта опция позволяет отправлять измененные данные с устройства на сервер и особенно полезна, если вам необходимо обеспечить, чтобы на сервере всегда были самые последние данные об устройстве. Этот вариант легко реализовать, если вы действительно храните данные у своего контент-провайдера. Если вы используете поставщика контента-заглушки, обнаружение изменений данных может оказаться более сложным.
Через регулярные промежутки времени
Запускайте адаптер синхронизации по истечении выбранного вами интервала или запускайте его в определенное время каждый день.
По требованию
Запустите адаптер синхронизации в ответ на действие пользователя. Однако для обеспечения наилучшего пользовательского опыта вам следует полагаться в первую очередь на один из наиболее автоматизированных вариантов. Используя автоматизированные параметры, вы экономите аккумулятор и сетевые ресурсы.

В оставшейся части урока каждый из вариантов описывается более подробно.

Запускать адаптер синхронизации при изменении данных сервера

Если ваше приложение передает данные с сервера и данные сервера часто меняются, вы можете использовать адаптер синхронизации для загрузки в ответ на изменения данных. Чтобы запустить адаптер синхронизации, попросите сервер отправить специальное сообщение BroadcastReceiver в вашем приложении. В ответ на это сообщение вызовите ContentResolver.requestSync() чтобы дать сигнал платформе адаптера синхронизации запустить ваш адаптер синхронизации.

Google Cloud Messaging (GCM) предоставляет как сервер, так и компоненты устройства, необходимые для работы этой системы обмена сообщениями. Использование GCM для запуска передачи более надежно и эффективно, чем опрос серверов на предмет статуса. Хотя для опроса требуется Service , которая всегда активна, GCM использует BroadcastReceiver , который активируется при поступлении сообщения. Хотя опрос через регулярные промежутки времени расходует заряд батареи, даже если обновления недоступны, GCM отправляет сообщения только при необходимости.

Примечание. Если вы используете GCM для запуска адаптера синхронизации посредством широковещательной рассылки на все устройства, на которых установлено ваше приложение, помните, что они получают ваше сообщение примерно в одно и то же время. Эта ситуация может привести к одновременному запуску нескольких экземпляров вашего адаптера синхронизации, что приведет к перегрузке сервера и сети. Чтобы избежать такой ситуации при широковещательной передаче на все устройства, следует рассмотреть возможность отсрочки запуска адаптера синхронизации на период, уникальный для каждого устройства.

Следующий фрагмент кода показывает, как запустить requestSync() в ответ на входящее сообщение GCM:

Котлин

...
// 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)
            ...
        }
        ...
    }
    ...
}

Ява

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);
            ...
        }
        ...
    }
    ...
}

Запускайте адаптер синхронизации при изменении данных поставщика контента.

Если ваше приложение собирает данные у поставщика контента и вы хотите обновлять сервер при каждом обновлении поставщика, вы можете настроить свое приложение на автоматический запуск адаптера синхронизации. Для этого вы регистрируете наблюдателя для поставщика контента. Когда данные в вашем поставщике контента изменяются, платформа поставщика контента вызывает наблюдателя. В обозревателе вызовите requestSync() чтобы сообщить платформе о запуске вашего адаптера синхронизации.

Примечание. Если вы используете поставщика контента-заглушки, у вас нет никаких данных в поставщике контента, и onChange() никогда не вызывается. В этом случае вам придется предусмотреть собственный механизм обнаружения изменений данных устройства. Этот механизм также отвечает за вызов requestSync() при изменении данных.

Чтобы создать наблюдателя для вашего поставщика контента, расширьте класс ContentObserver и реализуйте обе формы его метода onChange() . В onChange() вызовите requestSync() , чтобы запустить адаптер синхронизации.

Чтобы зарегистрировать наблюдателя, передайте его в качестве аргумента при вызове registerContentObserver() . В этом вызове вам также необходимо передать URI контента для данных, которые вы хотите просмотреть. Платформа поставщика контента сравнивает этот URI просмотра с URI контента, передаваемыми в качестве аргументов методам ContentResolver , которые изменяют вашего поставщика, например ContentResolver.insert() . Если есть совпадение, вызывается ваша реализация ContentObserver.onChange() .

В следующем фрагменте кода показано, как определить ContentObserver , который вызывает requestSync() при изменении таблицы:

Котлин

// 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)
        ...
    }
    ...
}

Ява

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);
        ...
    }
    ...
}

Периодически запускайте адаптер синхронизации

Вы можете запускать адаптер синхронизации периодически, задав период ожидания между запусками или запустив его в определенное время дня, или и то, и другое. Периодический запуск адаптера синхронизации позволяет примерно соответствовать интервалу обновления вашего сервера.

Аналогичным образом вы можете загружать данные с устройства, когда ваш сервер относительно простаивает, запланировав работу адаптера синхронизации на ночь. Большинство пользователей оставляют свои устройства включенными и подключенными к сети на ночь, поэтому это время обычно доступно. Более того, на устройстве не выполняются другие задачи одновременно с адаптером синхронизации. Однако если вы воспользуетесь этим подходом, вам необходимо убедиться, что каждое устройство запускает передачу данных в несколько разное время. Если на всех устройствах одновременно работает адаптер синхронизации, вы, скорее всего, перегрузите свой сервер и сети передачи данных сотового оператора.

В общем, периодические запуски имеют смысл, если вашим пользователям не нужны мгновенные обновления, но они ожидают регулярных обновлений. Периодические запуски также имеют смысл, если вы хотите сбалансировать доступность актуальных данных с эффективностью небольших запусков адаптера синхронизации, которые не перегружают ресурсы устройства.

Чтобы регулярно запускать адаптер синхронизации, вызовите addPeriodicSync() . Это запланирует запуск вашего адаптера синхронизации по истечении определенного времени. Поскольку платформа адаптера синхронизации должна учитывать выполнение других адаптеров синхронизации и пытается максимизировать эффективность использования батареи, затраченное время может отличаться на несколько секунд. Кроме того, платформа не будет запускать ваш адаптер синхронизации, если сеть недоступна.

Обратите внимание, что addPeriodicSync() не запускает адаптер синхронизации в определенное время суток. Чтобы адаптер синхронизации запускался примерно в одно и то же время каждый день, используйте повторяющийся сигнал в качестве триггера. Более подробно повторяющиеся сигналы тревоги описаны в справочной документации к AlarmManager . Если вы используете метод setInexactRepeating() для установки триггеров времени суток, которые имеют некоторые вариации, вам все равно следует рандомизировать время запуска, чтобы гарантировать, что адаптер синхронизации запускается с разных устройств в шахматном порядке.

Метод addPeriodicSync() не отключает setSyncAutomatically() , поэтому вы можете выполнить несколько запусков синхронизации за относительно короткий период времени. Кроме того, при вызове addPeriodicSync() разрешено использовать только несколько флагов управления адаптером синхронизации; неразрешенные флаги описаны в документации по addPeriodicSync() .

В следующем фрагменте кода показано, как запланировать периодические запуски адаптера синхронизации:

Котлин

// 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)
        ...
    }
    ...
}

Ява

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);
        ...
    }
    ...
}

Запуск адаптера синхронизации по требованию

Запуск адаптера синхронизации в ответ на запрос пользователя — наименее предпочтительная стратегия запуска адаптера синхронизации. Платформа специально разработана для экономии заряда батареи при запуске адаптеров синхронизации по расписанию. Опции, запускающие синхронизацию в ответ на изменения данных, эффективно используют заряд аккумулятора, поскольку он используется для предоставления новых данных.

Для сравнения, разрешение пользователям запускать синхронизацию по требованию означает, что синхронизация выполняется сама по себе, что является неэффективным использованием сетевых и энергетических ресурсов. Кроме того, предоставление синхронизации по требованию заставляет пользователей запрашивать синхронизацию, даже если нет никаких доказательств того, что данные изменились, а выполнение синхронизации, которая не обновляет данные, является неэффективным использованием заряда батареи. Как правило, ваше приложение должно либо использовать другие сигналы для запуска синхронизации, либо планировать их через регулярные промежутки времени без участия пользователя.

Однако если вы по-прежнему хотите запускать адаптер синхронизации по требованию, установите флаги адаптера синхронизации для запуска адаптера синхронизации вручную, а затем вызовите ContentResolver.requestSync() .

Запускайте передачу по требованию со следующими флагами:

SYNC_EXTRAS_MANUAL
Вызывает ручную синхронизацию. Платформа адаптера синхронизации игнорирует существующие настройки, такие как флаг, установленный setSyncAutomatically() .
SYNC_EXTRAS_EXPEDITED
Принудительно запускает синхронизацию немедленно. Если вы не установите этот параметр, система может подождать несколько секунд перед запуском запроса на синхронизацию, поскольку она пытается оптимизировать использование батареи, планируя множество запросов на короткий период времени.

Следующий фрагмент кода показывает, как вызвать requestSync() в ответ на нажатие кнопки:

Котлин

// 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)
    }

Ява

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);
    }