Dlaczego ContentResolver.requestSync nie wyzwala synchronizacji?

112

Próbuję zaimplementować wzorzec adaptera Content-Provider-Sync, jak omówiono w Google IO - slajd 26. Mój dostawca treści działa, a moja synchronizacja działa, gdy uruchamiam ją z aplikacji Dev Tools Sync Tester, ale gdy wywołuję ContentResolver. requestSync (konto, uprawnienia, pakiet) z mojego dostawcy treści, moja synchronizacja nigdy nie jest wyzwalana.

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

Edycja - dodany fragment manifestu Mój manifest xml zawiera:

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

--Edytować

Mój syncadapter.xml powiązany z moją usługą synchronizacji zawiera:

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

Nie jestem pewien, jaki inny kod byłby przydatny. Konto przekazane do requestSync ma wartość „myaccounttype”, a AUTORYTET przekazany do wywołania jest zgodny z moim adapterem syc xml.

Czy ContentResolver.requestSync to właściwy sposób żądania synchronizacji? Wygląda na to, że narzędzie do testowania synchronizacji łączy się bezpośrednio z usługą i wywołuje synchronizację, ale wydaje się, że nie spełnia ono celu integracji z architekturą synchronizacji.

Jeśli to jest właściwy sposób żądania synchronizacji, to dlaczego tester synchronizacji miałby działać, ale nie moje wywołanie ContentResolver.requestSync? Czy jest coś, co muszę przekazać w pakiecie?

Testuję w emulatorze na urządzeniach z systemem 2.1 i 2.2.

Ben
źródło
3
Mój problem polegał na tym, że mój punkt przerwania w adapterze synchronizacji nie został osiągnięty ... Potem zdałem sobie sprawę, że próbuję debugować usługę ... Mam nadzieję, że pomoże to innym, takim jak ja.
dangalg
2
Punkty przerwania w usłudze adaptera synchronizacji nie zostaną wyzwolone. Dzieje się tak, ponieważ usługa adaptera synchronizacji działa w oddzielnym procesie. To właśnie sugerował @danglang. Zobacz także to pytanie: stackoverflow.com/questions/8559458/…
Konstantin Schubert
1
W moim przypadku usunięcie android:process=":sync"z usługi synchronizacji niech debugger trafi w punkty dziobowe. Sama usługa synchronizacji działała wcześniej, ponieważ mogłem zobaczyć komunikaty dziennika z onPerformSyncmetody w imieniu innego procesu.
Siergiej
Inną przyczyną jest wyłączenie Wi-Fi, więc jeśli próbujesz zsynchronizować się z fałszywymi danymi, sprawdź połączenie.
Allan Veloso

Odpowiedzi:

280

Wywołanie requestSync()będzie działać tylko na parze {Account, ContentAuthority}, która jest znana w systemie. Twoja aplikacja musi wykonać szereg czynności, aby poinformować Androida, że ​​możesz synchronizować określony rodzaj treści przy użyciu określonego rodzaju konta. Robi to w AndroidManifest.

1. Powiadom system Android, że pakiet aplikacji zapewnia synchronizację

Po pierwsze, w AndroidManifest.xml musisz zadeklarować, że masz usługę synchronizacji:

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

Atrybut name <service>tagu to nazwa Twojej klasy, z którą chcesz się połączyć, synchronizować ... Porozmawiam o tym za chwilę.

Ustawienie exported true sprawia, że ​​jest on widoczny dla innych komponentów (jest to potrzebne, aby ContentResolvermożna było to wywołać).

Filtr intencji pozwala przechwycić intencję żądającą synchronizacji. (Wynika Intentto z ContentResolverwywołania ContentResolver.requestSync()lub powiązanych metod planowania).

<meta-data>Tag zostaną omówione poniżej.

2. Zapewnij Androidowi usługę używaną do znajdowania adaptera SyncAdapter

A więc sama klasa ... Oto przykład:

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

Twoja klasa musi rozszerzać Servicelub jedną z jej podklas, musi implementować public IBinder onBind(Intent)i musi zwracać, SyncAdapterBindergdy jest wywoływana ... Potrzebujesz zmiennej typu AbstractThreadedSyncAdapter. Jak widać, to prawie wszystko w tej klasie. Jedynym powodem, dla którego istnieje, jest zapewnienie usługi, która oferuje standardowy interfejs dla Androida do wysyłania zapytań Twojej klasie o to, czym SyncAdapterona jest.

3. Podaj, class SyncAdapteraby faktycznie wykonać synchronizację.

mySyncAdapter jest miejscem, w którym przechowywana jest sama logika synchronizacji. Jego onPerformSync()metoda jest wywoływana, gdy nadejdzie czas na synchronizację. Myślę, że masz już to na miejscu.

4. Ustanów powiązanie między typem konta a uprawnieniami do treści

Patrząc ponownie na AndroidManifest, ten dziwny <meta-data>tag w naszej usłudze jest kluczowym elementem, który ustanawia powiązanie między ContentAuthority a kontem. Zewnętrznie odwołuje się do innego pliku xml (nazwij go jak chcesz, coś związanego z twoją aplikacją). Spójrzmy na 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" /> 

Ok, więc co to robi? Mówi Androidowi, że zdefiniowany przez nas adapter synchronizacji (klasa, która została wywołana w elemencie name <service>tagu zawierającym <meta-data>tag odwołujący się do tego pliku ...) zsynchronizuje kontakty przy użyciu konta w stylu com.google.

Cała zawartość Ciągi autoryzacji muszą być zgodne i zgodne z tym, co synchronizujesz - powinien to być zdefiniowany przez Ciebie ciąg, jeśli tworzysz własną bazę danych, lub jeśli synchronizujesz znane, powinieneś użyć niektórych istniejących ciągów urządzeń typy danych (takie jak kontakty, wydarzenia w kalendarzu lub informacje o Tobie). Powyższe („com.android.contacts”) jest ciągiem ContentAuthority dla danych typu kontaktów (niespodzianka, niespodzianka).

accountType musi również pasować do jednego z tych znanych typów kont, które zostały już wprowadzone, lub musi pasować do tego, który tworzysz (obejmuje to utworzenie podklasy AccountAuthenticator w celu uzyskania autoryzacji na serwerze ... Warto sam artykuł). Ponownie „com.google” to zdefiniowany ciąg znaków identyfikujący dane logowania do konta w stylu ... google.com (znowu nie powinno to być zaskoczeniem).

5. Włącz synchronizację na danej parze Konto / ContentAuthority

Na koniec należy włączyć synchronizację. Możesz to zrobić na stronie Konta i synchronizacja w panelu sterowania, przechodząc do aplikacji i zaznaczając pole wyboru obok aplikacji na odpowiednim koncie. Alternatywnie możesz to zrobić w kodzie konfiguracji w swojej aplikacji:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Aby nastąpiła synchronizacja, Twoja para konto / uprawnienia musi być włączona do synchronizacji (jak powyżej), a ogólna flaga globalnej synchronizacji w systemie musi być ustawiona, a urządzenie musi mieć łączność z siecią.

Jeśli synchronizacja konta / uprawnień lub synchronizacja globalna są wyłączone, wywołanie RequestSync () ma wpływ - ustawia flagę, że zażądano synchronizacji i zostanie wykonana, gdy tylko synchronizacja zostanie włączona.

Ponadto, na mgv , ustawienie ContentResolver.SYNC_EXTRAS_MANUALna true w pakiecie dodatków twojego requestSync poprosi Androida o wymuszenie synchronizacji, nawet jeśli globalna synchronizacja jest wyłączona (szanuj tutaj swojego użytkownika!)

Na koniec możesz skonfigurować okresową zaplanowaną synchronizację, ponownie z funkcjami ContentResolver.

6. Rozważ konsekwencje wielu kont

Możliwe jest posiadanie więcej niż jednego konta tego samego typu (dwa konta @ gmail.com założone na jednym urządzeniu lub dwa konta na Facebooku, lub dwa konta na Twitterze, itd.). Należy rozważyć konsekwencje dla aplikacji. .. Jeśli masz dwa konta, prawdopodobnie nie chcesz próbować synchronizować ich obu w tych samych tabelach bazy danych. Może musisz określić, że tylko jeden może być aktywny w danym momencie, a także opróżnić tabele i ponownie zsynchronizować, jeśli zmienisz konto. (poprzez stronę właściwości, która pyta, jakie konta są obecne). Może tworzysz inną bazę danych dla każdego konta, może różne tabele, może kolumnę klucza w każdej tabeli. Wszystkie aplikacje specyficzne i warte przemyślenia. ContentResolver.setIsSyncable(Account account, String authority, int syncable)może być tutaj interesujące. setSyncAutomatically()kontroluje, czy sprawdzana jest para konto / uprawnienia, czyniezaznaczone , ale setIsSyncable()umożliwia odznaczenie i wyszarzenie linii, aby użytkownik nie mógł jej włączyć. Możesz ustawić jedno konto jako Syncable, a drugie jako Not Syncable (dsabled).

7. Pamiętaj o ContentResolver.notifyChange ()

Jedna trudna rzecz. ContentResolver.notifyChange()to funkcja używana przez ContentProviders do powiadamiania systemu Android o zmianie lokalnej bazy danych. Służy to dwóm funkcjom, po pierwsze, spowoduje aktualizację kursorów podążających za tą zawartością uri, a następnie ponowne zażądanie, unieważnienie i ponowne narysowanie ListView, itd. To bardzo magiczne, baza danych zmienia się, a ListViewpo prostu aktualizuje się automatycznie. Niesamowite. Ponadto, gdy baza danych ulegnie zmianie, system Android zażąda synchronizacji, nawet poza normalnym harmonogramem, aby zmiany te zostały usunięte z urządzenia i zsynchronizowane z serwerem tak szybko, jak to możliwe. Również super.

Jest jednak jeden skrajny przypadek. Jeśli ściągniesz z serwera i wepchniesz aktualizację do ContentProvider, to sumiennie zadzwoni, notifyChange()a android powie: „Och, zmiany w bazie danych, lepiej umieść je na serwerze!” (Doh!) Dobrze napisany ContentProvidersbędzie miał kilka testów, aby sprawdzić, czy zmiany pochodziły z sieci, czy od użytkownika, i ustawi syncToNetworkflagę boolean na false, jeśli tak, aby zapobiec marnotrawnej podwójnej synchronizacji. Jeśli dostarczasz dane do a ContentProvider, powinieneś dowiedzieć się, jak to działa - w przeciwnym razie zawsze będziesz wykonywać dwie synchronizacje, gdy tylko jedna będzie potrzebna.

8. Ciesz się!

Gdy masz już wszystkie te metadane xml i włączasz synchronizację, Android będzie wiedział, jak połączyć wszystko za Ciebie, a synchronizacja powinna zacząć działać. W tym momencie wiele fajnych rzeczy po prostu zatrzaśnie się na swoim miejscu i będzie to trochę jak magia. Cieszyć się!

jcwenger
źródło
11
ContentResolver.setSyncAutomatically (konto, AUTHORITY, true);
jcwenger
1
Żaden problem, cieszę się, że mogłem pomóc. Jednak z punktu widzenia stylu, jeśli „AUTHORITY” i „myaccounttype” są faktycznie używanymi ciągami znaków (a nie tylko próbkami kopiowania w przeszłości do witryny), zdecydowanie musisz uporządkować konwencje nazewnictwa. Te ciągi muszą być unikalne na całym urządzeniu, a będziesz mieć poważne kłopoty, jeśli jakiś inny programista leniwie utworzy pakiet z pasującym ciągiem znaków dla autorytetu i wystąpi konflikt. Twoje zdrowie!
jcwenger
22
Możesz poprosić o synchronizację, nawet jeśli globalne ustawienia synchronizacji są wyłączone. Po prostu dodaj ContentResolver.SYNC_EXTRAS_MANUALzestaw true do zestawu dodatków, a
wymusisz
2
@kaciula: Nie znam żadnego, ale urządzenie BĘDZIE pamiętać, że musi się zsynchronizować i uruchomi się, gdy tylko zostanie włączona synchronizacja globalna. Naprawdę nie powinieneś próbować prześcigać użytkownika w tym przypadku - zwłaszcza, że ​​„Globalna synchronizacja wyłączona” jest jednym z kluczowych sposobów oszczędzania baterii w krytycznych sytuacjach. Jeśli naprawdę martwisz się, że dane nie zostaną zsynchronizowane, rozważ wyskakujące okienko, które informuje użytkownika, DLACZEGO dane się nie poruszają, jeśli są przez jakiś czas. W ten sposób możesz edukować użytkowników, którzy przypadkowo źle skonfigurowali swoje urządzenia i przypominać użytkownikom zaawansowanym, gdyby zapomnieli.
jcwenger
2
Warto dodać, że jeśli chcesz używać addPeriodicSync (), to wydaje się działać TYLKO jeśli ustawisz RÓWNIEŻ setSyncAutomatically () - dodałem to z desperacji, próbując sprawić, by COŚ działało. Wiem, że to nie było częścią pierwotnego pytania, ale to jest taka pełna odpowiedź!
android.weasel
0

Korzystałem z metody setIsSyncableAccountManager setAuthToken. Ale setAuthTokenfunkcja zwrócona wcześniej setIsSyncablezostała osiągnięta. Po zmianie zamówienia wszystko działało dobrze!

Andris
źródło