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

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

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

Класс адаптера синхронизации.
Класс, который упаковывает код передачи данных в интерфейс, совместимый с фреймворком адаптера синхронизации.
Привязано: Service.
Компонент, который позволяет платформе адаптера синхронизации выполнять код в классе адаптера синхронизации.
XML-файл метаданных адаптера синхронизации.
Файл, содержащий информацию об адаптере синхронизации. Фреймворк считывает этот файл, чтобы определить, как загружать и планировать передачу данных.
декларации в манифесте приложения;
XML-код, который объявляет привязанную службу и указывает на метаданные, относящиеся к адаптеру синхронизации.

На этом уроке вы узнаете, как определить эти элементы.

Создание класса адаптера синхронизации

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

Расширение базового класса адаптера синхронизации

Чтобы создать компонент адаптера синхронизации, сначала расширьте AbstractThreadedSyncAdapter и напишите его конструкторы. Используйте конструкторы для выполнения задач настройки каждый раз, когда компонент адаптера синхронизации создается с нуля, так же, как вы используете Activity.onCreate() для настройки действия. Например, если ваше приложение использует поставщика контента для хранения данных, используйте конструкторы, чтобы получить экземпляр ContentResolver. Поскольку в версии 3.0 платформы Android была добавлена вторая форма конструктора для поддержки аргумента parallelSyncs, вам нужно создать две формы конструктора для обеспечения совместимости.

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

В следующем примере показано, как реализовать AbstractThreadedSyncAdapter и его конструкторы:

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

Добавьте код передачи данных

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

Чтобы упростить передачу данных из основного кода приложения в компонент адаптера синхронизации, фреймворк адаптера синхронизации вызывает onPerformSync() со следующими аргументами:

Аккаунт
Объект Account, связанный с событием, которое активировало адаптер синхронизации. Если на вашем сервере не используются аккаунты, вам не нужно использовать информацию из этого объекта.
Дополнительный контент
Bundle, содержащий флаги, отправленные событием, которое активировало адаптер синхронизации.
Авторитет
Полномочия поставщика контента в системе. У вашего приложения должен быть доступ к этому поставщику. Обычно он соответствует поставщику контента в вашем приложении.
Клиент поставщика контента
Объект ContentProviderClient для поставщика контента, на которого указывает аргумент authority. ContentProviderClient – это простой общедоступный интерфейс для поставщика контента. Она обладает теми же базовыми функциями, что и ContentResolver. Если вы используете поставщика контента для хранения данных приложения, вы можете подключиться к нему с помощью этого объекта. В противном случае его можно игнорировать.
Результат синхронизации
Объект SyncResult, который используется для отправки информации в платформу адаптера синхронизации.

Ниже показана общая структура 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.
     */
}

Реализация onPerformSync() зависит от требований к синхронизации данных и протоколов подключения к серверу, но в любом случае она должна выполнять следующие задачи:

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

Примечание. Фреймворк адаптера синхронизации работает в фоновом потоке onPerformSync(), поэтому вам не нужно настраивать собственную фоновую обработку.

Помимо задач, связанных с синхронизацией, вы можете объединить обычные задачи, связанные с сетью, и добавить их в onPerformSync(). Сосредоточив все сетевые задачи в этом методе, вы экономите заряд батареи, необходимый для запуска и остановки сетевых интерфейсов. Чтобы узнать больше о том, как сделать доступ к сети более эффективным, ознакомьтесь с учебным курсом Передача данных без разрядки батареи, в котором описывается несколько задач доступа к сети, которые вы можете включить в свой код передачи данных.

Привязка адаптера синхронизации к фреймворку

Теперь у вас есть код передачи данных, инкапсулированный в компонент адаптера синхронизации, но вам нужно предоставить фреймворку доступ к вашему коду. Для этого необходимо создать привязанный сервис Service, который передает специальный объект связывателя Android из компонента адаптера синхронизации в платформу. С помощью этого объекта фреймворк может вызывать метод onPerformSync() и передавать ему данные.

Создайте экземпляр компонента адаптера синхронизации как синглтон в методе onCreate() сервиса. Создавая экземпляр компонента в onCreate(), вы откладываете его создание до запуска сервиса, который происходит, когда фреймворк впервые пытается выполнить передачу данных. Вам необходимо создать экземпляр компонента потокобезопасным способом на случай, если фреймворк адаптера синхронизации поставит в очередь несколько выполнений вашего адаптера синхронизации в ответ на триггеры или планирование.

Например, в следующем фрагменте кода показано, как создать класс, который реализует связанный сервис Service, создает экземпляр компонента адаптера синхронизации и получает объект связывателя Android:

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

Примечание. Более подробный пример привязанной службы для адаптера синхронизации можно найти в примере приложения.

Добавьте аккаунт, необходимый для фреймворка

Фреймворк адаптера синхронизации требует, чтобы у каждого адаптера синхронизации был тип аккаунта. Вы указали значение типа аккаунта в разделе Добавление файла метаданных аутентификатора. Теперь вам нужно настроить этот тип аккаунта в системе Android. Чтобы настроить тип аккаунта, добавьте аккаунт-плейсхолдер, в котором используется этот тип аккаунта, вызвав addAccountExplicitly().

Лучше всего вызывать этот метод в методе onCreate() начального действия приложения. Ниже показано, как это сделать с помощью следующего фрагмента кода.

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

Добавьте файл метаданных адаптера синхронизации

Чтобы подключить компонент адаптера синхронизации к фреймворку, необходимо предоставить фреймворку метаданные, описывающие компонент, и дополнительные флаги. В метаданных указывается тип аккаунта, созданного для адаптера синхронизации, объявляется поставщик контента, связанный с приложением, настраивается часть системного пользовательского интерфейса, связанная с адаптерами синхронизации, и объявляются другие флаги, связанные с синхронизацией. Объявите эти метаданные в специальном XML-файле, хранящемся в каталоге /res/xml/ в проекте приложения. Файлу можно присвоить любое название, но обычно используется syncadapter.xml.

Этот XML-файл содержит один XML-элемент <sync-adapter> со следующими атрибутами:

android:contentAuthority
URI-адрес поставщика контента. Если на предыдущем уроке Создание фиктивного поставщика контента вы создали фиктивного поставщика контента для своего приложения, используйте значение, указанное для атрибута android:authorities в элементе <provider>, который вы добавили в манифест приложения. Этот атрибут подробно описан в разделе Объявление поставщика в манифесте.
Если вы переносите данные от поставщика контента на сервер с помощью адаптера синхронизации, это значение должно совпадать с источником URI контента, который вы используете для этих данных. Это значение также является одним из центров сертификации, которые вы указываете в атрибуте android:authorities элемента <provider>, объявляющего поставщика в манифесте приложения.
android:accountType
Тип аккаунта, необходимый для фреймворка адаптера синхронизации. Значение должно совпадать с типом аккаунта, указанным при создании файла метаданных аутентификатора, как описано в разделе Добавьте файл метаданных аутентификатора. Это также значение, которое вы указали для константы ACCOUNT_TYPE во фрагменте кода в разделе Добавьте аккаунт, необходимый для работы фреймворка.
Атрибуты настроек
android:userVisible
Задает видимость типа аккаунта адаптера синхронизации. По умолчанию значок аккаунта и ярлык, связанный с типом аккаунта, видны в разделе Аккаунты системного приложения "Настройки". Поэтому вам следует сделать адаптер синхронизации невидимым, если у вас нет типа аккаунта или домена, который легко связать с вашим приложением. Если вы сделаете тип аккаунта невидимым, вы все равно сможете разрешить пользователям управлять адаптером синхронизации с помощью пользовательского интерфейса в одном из действий вашего приложения.
android:supportsUploading
Позволяет загружать данные в облако. Если приложение только скачивает данные, укажите значение false.
android:allowParallelSyncs
Позволяет одновременно запускать несколько экземпляров компонента адаптера синхронизации. Используйте этот вариант, если ваше приложение поддерживает несколько аккаунтов пользователей и вы хотите разрешить нескольким пользователям передавать данные параллельно. Этот флаг не действует, если вы никогда не запускаете несколько передач данных.
android:isAlwaysSyncable
Указывает фреймворку адаптера синхронизации, что он может запустить адаптер синхронизации в любое указанное вами время. Если вы хотите программно управлять запуском адаптера синхронизации, установите для этого флага значение false, а затем вызовите requestSync(), чтобы запустить адаптер. Подробнее о том, как запустить адаптер синхронизации, рассказывается в уроке Запуск адаптера синхронизации.

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

<?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"/>

Объявление адаптера синхронизации в манифесте

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

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

Чтобы запросить эти разрешения, добавьте в манифест приложения следующие дочерние элементы <manifest>:

android.permission.INTERNET
Позволяет коду адаптера синхронизации получать доступ к интернету, чтобы скачивать или загружать данные с устройства на сервер. Если вы уже запрашивали это разрешение, то запрашивать его повторно не нужно.
android.permission.READ_SYNC_SETTINGS
Приложение сможет считывать текущие настройки адаптера синхронизации. Например, это разрешение необходимо для вызова getIsSyncable().
android.permission.WRITE_SYNC_SETTINGS
Позволяет приложению управлять настройками адаптера синхронизации. Это разрешение необходимо, чтобы настроить периодический запуск адаптера синхронизации с помощью addPeriodicSync(). Это разрешение не требуется для вызова requestSync(). Подробнее о том, как запустить адаптер синхронизации…

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

<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>

Наконец, чтобы объявить связанный сервис Service, который фреймворк использует для взаимодействия с адаптером синхронизации, добавьте в манифест приложения следующий код XML в качестве дочернего элемента <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>

Элемент <intent-filter> настраивает фильтр, который активируется действием намерения android.content.SyncAdapter, отправленным системой для запуска адаптера синхронизации. Когда фильтр срабатывает, система запускает созданную вами привязанную службу, которая в этом примере называется SyncService. Атрибут android:exported="false" разрешает доступ к Service только вашему приложению и системе. Атрибут android:process=":sync" указывает системе, что Service должен выполняться в глобальном общем процессе с именем sync. Если в приложении несколько адаптеров синхронизации, они могут использовать один процесс, что снижает нагрузку.

Элемент <meta-data> содержит название XML-файла метаданных адаптера синхронизации, который вы создали ранее. Атрибут android:name указывает, что эти метаданные предназначены для платформы адаптера синхронизации. Элемент android:resource указывает название файла метаданных.

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