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

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

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

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

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

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

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

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

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

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

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

Котлин

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

Ява

/**
 * 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 для поставщика контента, на который указывает аргумент полномочий. ContentProviderClient — это облегченный общедоступный интерфейс для поставщика контента. Он имеет ту же базовую функциональность, что и ContentResolver . Если вы используете поставщика контента для хранения данных для своего приложения, вы можете подключиться к поставщику с помощью этого объекта. В противном случае вы можете игнорировать это.
Результат синхронизации
Объект SyncResult , который вы используете для отправки информации в платформу адаптера синхронизации.

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

Котлин

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

Ява

/*
 * 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:

Котлин

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

Ява

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() открытия вашего приложения. Следующий фрагмент кода показывает, как это сделать:

Котлин

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

Ява

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 указывает имя файла метаданных.

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