Mengapa ContentResolver.requestSync tidak memicu sinkronisasi?

112

Saya mencoba menerapkan pola Adaptor Sinkronisasi Penyedia Konten seperti yang dibahas di Google IO - slide 26. Penyedia konten saya berfungsi, dan sinkronisasi saya berfungsi saat saya memicunya dari aplikasi Penguji Sinkronisasi Alat Dev, namun saat saya memanggil ContentResolver. requestSync (akun, otoritas, bundel) dari ContentProvider saya, sinkronisasi saya tidak pernah terpicu.

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

Edit - menambahkan cuplikan manifes Xml manifes saya berisi:

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

--Edit

Syncadapter.xml saya yang terkait dengan layanan sinkronisasi saya berisi:

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

Tidak yakin kode lain apa yang akan berguna. Akun yang diteruskan ke requestSync adalah "myaccounttype" dan KEWENANGAN yang diteruskan ke panggilan tersebut cocok dengan adaptor syc saya xml.

Apakah ContentResolver.requestSync cara yang benar untuk meminta sinkronisasi? Sepertinya alat penguji sinkronisasi mengikat langsung ke layanan dan panggilan mulai sinkronisasi, tetapi tampaknya itu mengalahkan tujuan integrasi dengan arsitektur sinkronisasi.

Jika itu adalah cara yang benar untuk meminta sinkronisasi, mengapa penguji sinkronisasi berfungsi, tetapi bukan panggilan saya ke ContentResolver.requestSync? Apakah ada sesuatu yang harus saya berikan dalam bundel?

Saya menguji di emulator pada perangkat yang menjalankan 2.1 dan 2.2.

Ben
sumber
3
Masalah saya adalah break point saya di adaptor sinkronisasi tidak tercapai ... Kemudian saya menyadari saya mencoba men-debug layanan ... Semoga ini membantu orang lain seperti saya.
dangalg
2
Titik putus dalam layanan adaptor sinkronisasi akan GAGAL dipicu. Itu karena layanan adaptor sinkronisasi berjalan dalam proses terpisah. Inilah yang diisyaratkan oleh @danglang. Lihat juga pertanyaan ini: stackoverflow.com/questions/8559458/…
Konstantin Schubert
1
Dalam kasus saya, menghapus android:process=":sync"dari layanan sinkronisasi biarkan debugger mencapai titik paruh. Layanan sinkronisasi itu sendiri berfungsi sebelumnya, karena saya bisa melihat pesan log dari onPerformSyncmetode atas nama proses lain.
Sergey
Penyebab lainnya adalah WiFi dimatikan, jadi jika Anda mencoba menyinkronkan dengan data palsu, periksa koneksi Anda.
Allan Veloso

Jawaban:

280

Panggilan requestSync() hanya akan berfungsi pada pasangan {Account, ContentAuthority} yang dikenal oleh sistem. Aplikasi Anda perlu melalui sejumlah langkah untuk memberi tahu Android bahwa Anda mampu menyinkronkan jenis konten tertentu menggunakan jenis akun tertentu. Ini dilakukan di AndroidManifest.

1. Beri tahu Android bahwa paket aplikasi Anda menyediakan sinkronisasi

Pertama, di AndroidManifest.xml, Anda harus menyatakan bahwa Anda memiliki Layanan Sinkronisasi:

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

Atribut nama dari <service>tag adalah nama kelas Anda untuk menyambung sinkronisasi ... Saya akan membicarakannya sebentar lagi.

Pengaturan export true membuatnya terlihat oleh komponen lain (diperlukan sehingga ContentResolverdapat memanggilnya).

Filter maksud memungkinkannya menangkap maksud yang meminta sinkronisasi. (Ini Intentberasal dari ContentResolversaat Anda meneleponContentResolver.requestSync() atau metode penjadwalan terkait.)

The <meta-data>tag akan dibahas di bawah.

2. Sediakan Android layanan yang digunakan untuk menemukan SyncAdapter Anda

Jadi kelasnya sendiri ... Berikut contohnya:

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

Kelas Anda harus memperpanjang Serviceatau salah satu subkelasnya, harus mengimplementasikan public IBinder onBind(Intent), dan harus mengembalikan SyncAdapterBinderketika dipanggil ... Anda memerlukan variabel tipe AbstractThreadedSyncAdapter. Jadi seperti yang Anda lihat, hampir semuanya di kelas itu. Satu-satunya alasan mengapa ada untuk menyediakan Layanan, yang menawarkan antarmuka standar untuk Android untuk menanyakan kelas Anda tentang apa Anda SyncAdaptersendiri.

3. Sediakan class SyncAdapteruntuk benar-benar melakukan sinkronisasi.

mySyncAdapter adalah tempat menyimpan logika sinkronisasi yang sebenarnya. NyaonPerformSync() metode dipanggil ketika saatnya untuk sync. Saya pikir Anda sudah memiliki ini.

4. Menetapkan ikatan antara tipe Akun dan Otoritas Konten

Melihat kembali lagi di AndroidManifest, <meta-data>tag aneh dalam layanan kami adalah bagian kunci yang menetapkan pengikatan antara ContentAuthority dan akun. Ini secara eksternal mereferensikan file xml lain (sebut saja sesuka Anda, sesuatu yang relevan dengan aplikasi Anda.) Mari kita lihat 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" /> 

Oke, jadi apa fungsinya? Ini memberi tahu Android bahwa adaptor sinkronisasi yang telah kita tentukan (kelas yang dipanggil dalam elemen nama dari <service>tag yang menyertakan<meta-data> tag yang mereferensikan file ini ...) akan menyinkronkan kontak menggunakan akun gaya com.google.

Semua string contentAuthority Anda harus semuanya cocok, dan cocok dengan apa yang Anda sinkronkan - Ini harus berupa string yang Anda tentukan, jika Anda membuat database Anda sendiri, atau Anda harus menggunakan beberapa string perangkat yang ada jika Anda menyinkronkan diketahui jenis data (seperti kontak atau acara kalender atau apa saja.) Di atas ("com.android.contacts") kebetulan adalah string ContentAuthority untuk data jenis kontak (kejutan, kejutan.)

accountType juga harus cocok dengan salah satu jenis akun yang diketahui yang sudah dimasukkan, atau harus cocok dengan yang Anda buat (Ini melibatkan pembuatan subkelas AccountAuthenticator untuk mendapatkan auth di server Anda ... Layak sebuah artikel, itu sendiri.) Sekali lagi, "com.google" adalah string yang ditentukan yang mengidentifikasi kredensial akun gaya ... google.com (sekali lagi, ini seharusnya tidak mengejutkan.)

5. Aktifkan Sinkronisasi pada pasangan Akun / Otoritas Konten tertentu

Terakhir, sinkronisasi harus diaktifkan. Anda dapat melakukan ini di halaman Akun & Sinkronisasi di panel kontrol dengan membuka aplikasi Anda dan menyetel kotak centang di sebelah aplikasi Anda dalam akun yang sesuai. Sebagai alternatif, Anda dapat melakukannya di beberapa kode penyiapan di aplikasi Anda:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Agar sinkronisasi terjadi, pasangan akun / otoritas Anda harus diaktifkan untuk sinkronisasi (seperti di atas) dan keseluruhan bendera sinkronisasi global pada sistem harus disetel, dan perangkat harus memiliki konektivitas jaringan.

Jika sinkronisasi akun / otoritas Anda atau sinkronisasi global dinonaktifkan, memanggil RequestSync () memang memiliki efek - Ini menetapkan tanda bahwa sinkronisasi telah diminta, dan akan dilakukan segera setelah sinkronisasi diaktifkan.

Juga, per mgv , pengaturanContentResolver.SYNC_EXTRAS_MANUAL ke true dalam paket ekstra requestSync Anda akan meminta android untuk memaksa sinkronisasi bahkan jika sinkronisasi global tidak aktif (hormati pengguna Anda di sini!)

Terakhir, Anda dapat mengatur sinkronisasi terjadwal berkala, lagi-lagi dengan fungsi ContentResolver.

6. Pertimbangkan implikasi dari banyak akun

Dimungkinkan untuk memiliki lebih dari satu akun dengan jenis yang sama (dua akun @ gmail.com disiapkan pada satu perangkat atau dua akun facebook, atau dua akun twitter, dll ...) Anda harus mempertimbangkan implikasi aplikasi dari melakukan itu. .. Jika Anda memiliki dua akun, Anda mungkin tidak ingin mencoba menyinkronkan keduanya ke dalam tabel database yang sama. Mungkin Anda perlu menentukan bahwa hanya satu yang dapat aktif pada satu waktu, dan hapus tabel dan sinkronkan ulang jika Anda beralih akun. (melalui halaman properti yang menanyakan akun apa yang ada). Mungkin Anda membuat database yang berbeda untuk setiap akun, mungkin tabel yang berbeda, mungkin kolom kunci di setiap tabel. Semua aplikasi spesifik dan layak untuk dipikirkan. ContentResolver.setIsSyncable(Account account, String authority, int syncable)mungkin menarik di sini. setSyncAutomatically()mengontrol apakah pasangan akun / otoritas dicentang atautidak dicentang , sedangkan setIsSyncable()menyediakan cara untuk menghapus centang dan mengubah garis menjadi abu-abu sehingga pengguna tidak dapat mengaktifkannya. Anda dapat mengatur satu akun Syncable dan yang lainnya tidak Syncable (dsabled).

7. Waspadai ContentResolver.notifyChange ()

Satu hal yang rumit. ContentResolver.notifyChange()adalah fungsi yang digunakan oleh ContentProviders untuk memberi tahu Android bahwa database lokal telah diubah. Ini melayani dua fungsi, pertama, ini akan menyebabkan kursor mengikuti konten uri untuk diperbarui, dan pada gilirannya meminta dan membatalkan dan menggambar ulang ListView, dll ... Sangat ajaib, database berubah dan AndaListView hanya memperbarui secara otomatis. Hebat. Selain itu, saat database berubah, Android akan meminta Sinkronisasi untuk Anda, bahkan di luar jadwal normal Anda, sehingga perubahan tersebut diambil dari perangkat dan disinkronkan ke server secepat mungkin. Juga luar biasa.

Ada satu kasus tepi. Jika Anda menarik dari server, dan mendorong pembaruan ke dalam ContentProvider, itu akan dengan patuh memanggil notifyChange()dan android akan berkata, "Oh, database berubah, lebih baik letakkan di server!" (Doh!) Penulisan yang baik ContentProvidersakan memiliki beberapa pengujian untuk melihat apakah perubahan itu berasal dari jaringan atau dari pengguna, dan akan menyetel boolean syncToNetworkflag false jika demikian, untuk mencegah sinkronisasi ganda yang sia-sia ini. Jika Anda memasukkan data ke fileContentProvider , Anda harus mencari cara untuk membuatnya berfungsi - Jika tidak, Anda akan selalu melakukan dua sinkronisasi saat hanya satu yang diperlukan.

8. Merasa senang!

Setelah Anda memiliki semua metadata xml ini, dan sinkronisasi diaktifkan, Android akan tahu cara menghubungkan semuanya untuk Anda, dan sinkronisasi akan mulai berfungsi. Pada titik ini, banyak hal yang bagus akan langsung masuk ke tempatnya dan akan terasa sangat ajaib. Nikmati!

jcwenger.dll
sumber
11
ContentResolver.setSyncAutomatically (akun, AUTHORITY, true);
jcwenger
1
Tidak masalah, senang saya bisa membantu. Sekali lagi dari sudut pandang gaya, jika "OTORITAS" dan "myaccounttype" adalah string sebenarnya yang Anda gunakan (Dan bukan hanya contoh untuk copy-past ke situs), Anda pasti perlu membersihkan konvensi penamaan Anda. String tersebut harus unik di semua perangkat, dan Anda akan mengalami masalah nyata jika programmer lain dengan malas membuat paket dengan string yang cocok untuk otoritas dan Anda mendapatkan konflik. Bersulang!
jcwenger
22
Anda dapat meminta sinkronisasi meskipun pengaturan sinkronisasi global dimatikan. Cukup tambahkan satu ContentResolver.SYNC_EXTRAS_MANUALset ke true ke Bundel ekstra dan Anda akan memaksa sinkronisasi :)
mgv
2
@kaciula: Saya tidak tahu apa-apa, tetapi perangkat AKAN mengingat bahwa ia perlu disinkronkan, dan akan dimulai segera setelah sinkronisasi global diaktifkan. Anda sebaiknya tidak mencoba mengada-ada pengguna yang satu ini - Terutama karena "Sinkronisasi global mati" adalah salah satu cara utama untuk menghemat baterai dalam situasi kritis. Jika Anda benar-benar khawatir tentang data yang tidak disinkronkan, pertimbangkan munculan yang memberi tahu pengguna MENGAPA data tidak bergerak, jika sudah lama diam. Dengan begitu, Anda dapat mendidik pengguna yang secara tidak sengaja salah konfigurasi perangkat mereka dan mengingatkan power-user jika mereka lupa.
jcwenger
2
Mungkin ada baiknya menambahkan bahwa jika Anda ingin menggunakan addPeriodicSync (), HANYA tampaknya berfungsi jika Anda JUGA setSyncAutomatically () - Saya menambahkannya karena putus asa, mencoba membuat SESUATU bekerja. Saya tahu itu bukan bagian dari pertanyaan awal, tetapi ini adalah jawaban yang lengkap!
android.weasel
0

Saya menelepon setIsSyncablesetelah setAuthTokenmetode AccountManager . Tapi setAuthTokenfungsi kembali sebelum setIsSyncabletercapai. Setelah pesanan berubah semuanya bekerja dengan baik!

Andris
sumber