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

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

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

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

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

Остальная часть этого урока описывает эти варианты более подробно.

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

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

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

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

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

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(ACCOUNT, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

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

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

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

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

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

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

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 mUri;
    // 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(ACCOUNT, 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
        mUri = 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(mUri, true, observer);
        ...
    }
    ...
}

Запуск адаптера синхронизации после сетевого сообщения

Когда сетевое соединение доступно, Android система посылает сообщение каждые несколько секунд, чтобы держать TCP/IP соединение открытым. Это сообщение также идет в ContentResolver каждого приложения. Вызвав setSyncAutomatically(), вы можете настроить запуск адаптера синхронизации каждый раз, когда ContentResolver принимает сообщение.

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

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

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

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";
    // 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 automatic syncing for the default account and authority
        mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
        ...
    }
    ...
}

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

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

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

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

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

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

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

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

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 MILLISECONDS_PER_SECOND = 1000L;
    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 *
            MILLISECONDS_PER_SECOND;
    // 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(
                ACCOUNT,
                AUTHORITY,
                null,
                SYNC_INTERVAL);
        ...
    }
    ...
}

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

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

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

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

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

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

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

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