Почему ContentResolver.requestSync не запускает синхронизацию?

112

Я пытаюсь реализовать шаблон адаптера Content-Provider-Sync, как обсуждалось на Google IO - слайд 26. Мой поставщик контента работает, и моя синхронизация работает, когда я запускаю его из приложения Dev Tools Sync Tester, но когда я вызываю ContentResolver. requestSync (account, author, bundle) от моего ContentProvider, моя синхронизация никогда не запускается.

ContentResolver.requestSync(
        account, 
        AUTHORITY, 
        new Bundle());

Изменить - добавлен фрагмент манифеста Мой XML-манифест содержит:

<service
    android:name=".sync.SyncService"
    android:exported="true">
    <intent-filter>
        <action
            android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data android:name="android.content.SyncAdapter"
    android:resource="@xml/syncadapter" />
</service>

--Редактировать

Мой файл syncadapter.xml, связанный с моей службой синхронизации, содержит:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
    android:contentAuthority="AUTHORITY"
    android:accountType="myaccounttype"
    android:supportsUploading="true"
/>

Не уверен, какой еще код будет полезен. Учетная запись, переданная в requestSync, имеет «myaccounttype», а АВТОРИТЕТ, переданный вызову, соответствует моему адаптеру syc xml.

Является ли ContentResolver.requestSync правильным способом запроса синхронизации? Похоже, что инструмент тестирования синхронизации напрямую связывается со службой, и вызовы запускают синхронизацию, но похоже, что это противоречит цели интеграции с архитектурой синхронизации.

Если это правильный способ запросить синхронизацию, то почему тестер синхронизации должен работать, но не мой вызов ContentResolver.requestSync? Есть ли что-то, что мне нужно передать в комплекте?

Тестирую в эмуляторе на устройствах 2.1 и 2.2.

Бен
источник
3
Моя проблема заключалась в том, что моя точка останова в адаптере синхронизации не была достигнута ... Затем я понял, что пытался отладить службу ... Надеюсь, это поможет другим, как я.
dangalg
2
Точки останова в службе адаптера синхронизации не сработают. Это потому, что служба адаптера синхронизации выполняется в отдельном процессе. Это то, на что намекал @danglang. См. Также этот вопрос: stackoverflow.com/questions/8559458/…
Константин Шуберт
1
В моем случае удаление android:process=":sync"из службы синхронизации позволило отладчику поразить клюв. Сама служба синхронизации работала до этого, потому что я мог видеть сообщения журнала из onPerformSyncметода от имени другого процесса.
Сергей
Другой причиной является отключение Wi-Fi, поэтому, если вы пытаетесь синхронизироваться с поддельными данными, проверьте подключение.
Аллан Велосо,

Ответы:

280

Вызов requestSync()будет работать только для пары {Account, ContentAuthority}, которая известна системе. Ваше приложение должно пройти ряд шагов, чтобы сообщить Android, что вы можете синхронизировать определенный тип контента с помощью определенного типа учетной записи. Это делается в AndroidManifest.

1. Сообщите Android, что пакет вашего приложения обеспечивает синхронизацию.

Во-первых, в AndroidManifest.xml вы должны объявить, что у вас есть служба синхронизации:

<service android:name=".sync.mySyncService" android:exported="true">
   <intent-filter>
      <action android:name="android.content.SyncAdapter" /> 
    </intent-filter>
    <meta-data 
        android:name="android.content.SyncAdapter" 
        android:resource="@xml/sync_myapp" /> 
</service>

Атрибут name <service>тега - это имя вашего класса для подключения к синхронизации ... Я поговорю об этом через секунду.

Установка exported true делает его видимым для других компонентов (необходимо, чтобы ContentResolverможно было его назвать).

Фильтр намерения позволяет ему улавливать намерение, запрашивающее синхронизацию. (Это Intentпроисходит, ContentResolverкогда вы вызываете ContentResolver.requestSync()или связанные с ним методы планирования.)

О <meta-data>теге речь пойдет ниже.

2. Предоставьте Android сервис для поиска вашего SyncAdapter.

Итак, сам класс ... Вот пример:

public class mySyncService extends Service {

    private static mySyncAdapter mSyncAdapter = null;

    public SyncService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (mSyncAdapter == null) {
            mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

Ваш класс должен расширять Serviceили один из его подклассов, должен реализовывать public IBinder onBind(Intent)и должен возвращать SyncAdapterBinderпри его вызове ... Вам нужна переменная типа AbstractThreadedSyncAdapter. Как видите, это почти все в этом классе. Единственная причина, по которой он существует, - предоставить Сервис, который предлагает стандартный интерфейс для Android для запроса вашего класса о том, что вы из SyncAdapterсебя представляете.

3. Обеспечьте class SyncAdapterфактическое выполнение синхронизации.

mySyncAdapter - это место, где хранится сама реальная логика синхронизации. Его onPerformSync()метод вызывается, когда приходит время для синхронизации. Я полагаю, у вас это уже есть.

4. Установите связь между Account-type и Content Authority.

Еще раз оглядываясь на AndroidManifest, этот странный <meta-data>тег в нашем сервисе является ключевым элементом, который устанавливает связь между ContentAuthority и учетной записью. Он ссылается извне на другой XML-файл (назовите его как хотите, что-нибудь относящееся к вашему приложению). Давайте посмотрим на sync_myapp.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<sync-adapter 
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:contentAuthority="com.android.contacts"
    android:accountType="com.google" 
    android:userVisible="true" /> 

Хорошо, так что это делает? Он сообщает Android, что определенный нами адаптер синхронизации (класс, который был вызван в элементе имени <service>тега, который включает <meta-data>тег, который ссылается на этот файл ...) будет синхронизировать контакты с использованием учетной записи в стиле com.google.

Все ваши строки contentAuthority должны совпадать и соответствовать тому, что вы синхронизируете - это должна быть строка, которую вы определяете, если вы создаете свою собственную базу данных, или вы должны использовать некоторые существующие строки устройства, если вы синхронизируете известные типы данных (например, контакты, события календаря или что-то еще.) Вышеупомянутое ("com.android.contacts") оказывается строкой ContentAuthority для данных типа контактов (сюрприз, сюрприз).

accountType также должен соответствовать одному из тех известных типов учетных записей, которые уже введены, или он должен соответствовать тому, который вы создаете (это включает создание подкласса AccountAuthenticator для аутентификации на вашем сервере ... Сама стоит статья). Опять же, com.google - это определенная строка, определяющая ... учетные данные учетной записи в стиле google.com (опять же, это не должно быть сюрпризом).

5. Включите синхронизацию для данной пары "Аккаунт / ContentAuthority".

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

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

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

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

Кроме того, для mgv установка значения ContentResolver.SYNC_EXTRAS_MANUALtrue в дополнительном пакете вашего requestSync попросит Android принудительно выполнить синхронизацию, даже если глобальная синхронизация отключена (уважайте своего пользователя здесь!)

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

6. Учитывайте последствия использования нескольких аккаунтов.

Возможно иметь несколько учетных записей одного и того же типа (две учетные записи @ gmail.com, настроенные на одном устройстве или две учетные записи facebook, или две учетные записи twitter и т. Д.). Вы должны учитывать последствия этого для приложения. .. Если у вас есть две учетные записи, вы, вероятно, не хотите пытаться синхронизировать их обе с одними и теми же таблицами базы данных. Возможно, вам нужно указать, что только один может быть активным одновременно, и очистить таблицы и выполнить повторную синхронизацию при переключении учетных записей. (через страницу свойств, которая запрашивает, какие учетные записи присутствуют). Возможно, вы создаете отдельную базу данных для каждой учетной записи, может быть, разные таблицы, может быть, ключевой столбец в каждой таблице. Все приложения специфичны и заслуживают некоторого размышления. ContentResolver.setIsSyncable(Account account, String authority, int syncable)может быть здесь интересно. setSyncAutomatically()контролирует ли помощница счета / орган проверил илиunchecked , в то время как setIsSyncable()предоставляет способ снять флажок и выделить строку серым цветом, чтобы пользователь не мог ее включить. Вы можете установить одну учетную запись синхронизируемой, а другую - не синхронизируемой (dsabled).

7. Помните о ContentResolver.notifyChange ().

Одна хитрость. ContentResolver.notifyChange()- это функция, используемая ContentProviders для уведомления Android об изменении локальной базы данных. Это выполняет две функции: во-первых, это приведет к обновлению курсоров, следующих за этим uri содержимого, и, в свою очередь, запрашивает, аннулирует и перерисовывает и ListViewт. Д. Это очень волшебно, база данных изменяется, а ваши ListViewпросто обновляются автоматически. Потрясающие. Кроме того, при изменении базы данных Android запросит синхронизацию для вас, даже вне вашего обычного расписания, чтобы эти изменения были сняты с устройства и синхронизированы с сервером как можно быстрее. Также круто.

Однако есть один крайний случай. Если вы возьмете с сервера и загрузите обновление в ContentProvider, он послушно вызовет notifyChange()и андроид скажет: «О, изменения базы данных, лучше поместите их на сервер!» (Дох!) Хорошо написанный ContentProvidersбудет иметь несколько тестов, чтобы увидеть, пришли ли изменения из сети или от пользователя, и в этом случае установит логический syncToNetworkфлаг false, чтобы предотвратить эту расточительную двойную синхронизацию. Если вы вводите данные в ContentProviderфайл, вам следует выяснить, как заставить это работать - иначе вы всегда будете выполнять две синхронизации, когда требуется только одна.

8. Будьте счастливы!

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

Jcwenger
источник
11
ContentResolver.setSyncAutomatically (account, AUTHORITY, true);
jcwenger
1
Нет проблем, рад, что смог помочь. Опять же, с точки зрения стиля, если «AUTHORITY» и «myaccounttype» - это фактические строки, которые вы используете (а не просто образцы для копирования на сайт), вам определенно нужно очистить свои соглашения об именах. Эти строки должны быть уникальными для всего устройства, и вы столкнетесь с настоящими проблемами, если какой-нибудь другой программист лениво создаст пакет с соответствующей строкой для полномочий, и вы получите конфликт. Ура!
jcwenger
22
Вы можете запросить синхронизацию, даже если глобальные настройки синхронизации отключены. Просто добавьте ContentResolver.SYNC_EXTRAS_MANUALзначение true в пакет дополнений, и вы принудительно синхронизируете :)
MGV
2
@kaciula: Я ничего не знаю, но устройство БУДЕТ помнить, что ему нужно синхронизировать, и оно отключится, как только глобальная синхронизация будет включена. Вам действительно не следует пытаться превзойти пользователя в этом - тем более, что «Глобальная синхронизация отключена» является одним из ключевых способов экономии заряда батареи в критических ситуациях. Если вы действительно беспокоитесь о том, что данные не синхронизируются, подумайте о всплывающем окне, которое сообщает пользователю, ПОЧЕМУ данные не перемещаются, если оно какое-то время сидело. Таким образом, вы можете обучать пользователей, которые случайно неправильно настроили свои устройства, и напоминать опытным пользователям, если они забыли.
jcwenger
2
Возможно, стоит добавить, что если вы хотите использовать addPeriodicSync (), он работает ТОЛЬКО, если вы ТАКЖЕ setSyncAutomatically () - я добавил это от отчаяния, пытаясь заставить ЧТО-ТО работало. Я знаю, что это не было частью первоначального вопроса, но это такой полный ответ!
android.weasel
0

Я звонил setIsSyncableпосле setAuthTokenметода AccountManager . Но setAuthTokenдо этого setIsSyncableдоходила возвращаемая функция . После смены заказа все заработало!

Андрис
источник