Perbedaan antara initLoader dan restartLoader di LoaderManager

129

Saya benar-benar bingung mengenai perbedaan antara initLoaderdan restartLoaderfungsi dari LoaderManager:

  • Mereka berdua memiliki tanda tangan yang sama.
  • restartLoader juga membuat loader, jika tidak ada ("Mulai yang baru atau me-restart Loader yang ada di manajer ini").

Apakah ada hubungan antara kedua metode tersebut? Apakah menelepon restartLoaderselalu menelepon initLoader? Bisakah saya menelepon restartLoadertanpa harus menelepon initLoader? Apakah aman menelepon initLoaderdua kali untuk menyegarkan data? Kapan saya harus menggunakan salah satu dari keduanya dan mengapa ?

theomega
sumber

Jawaban:

202

Untuk menjawab pertanyaan ini, Anda perlu menggali ke dalam LoaderManagerkode. Walaupun dokumentasi untuk LoaderManager itu sendiri tidak cukup jelas (atau tidak akan ada pertanyaan ini), dokumentasi untuk LoaderManagerImpl, subkelas dari LoaderManager abstrak, jauh lebih mencerahkan.

initLoader

Panggilan untuk menginisialisasi ID tertentu dengan Loader. Jika ID ini sudah memiliki Loader yang terkait dengannya, ID itu dibiarkan tidak berubah dan panggilan balik sebelumnya diganti dengan yang baru disediakan. Jika saat ini tidak ada Loader untuk ID, yang baru dibuat dan dimulai.

Fungsi ini umumnya harus digunakan ketika suatu komponen diinisialisasi, untuk memastikan bahwa Loader yang diandalkan dibuat. Ini memungkinkannya untuk menggunakan kembali data Loader yang ada jika sudah ada, sehingga misalnya ketika suatu Aktivitas dibuat kembali setelah perubahan konfigurasi, ia tidak perlu membuat ulang loadernya.

restartLoader

Panggilan untuk membuat kembali Loader yang terkait dengan ID tertentu. Jika saat ini ada Loader yang terkait dengan ID ini, itu akan dibatalkan / dihentikan / dihancurkan sebagaimana mestinya. Loader baru dengan argumen yang diberikan akan dibuat dan datanya dikirimkan kepada Anda setelah tersedia.

[...] Setelah memanggil fungsi ini, semua Loader sebelumnya yang terkait dengan ID ini akan dianggap tidak valid, dan Anda tidak akan menerima pembaruan data lebih lanjut darinya.

Pada dasarnya ada dua kasus:

  1. Pemuat dengan id tidak ada: kedua metode akan membuat pemuat baru sehingga tidak ada perbedaan di sana
  2. Loader dengan id sudah ada: initLoaderhanya akan mengganti callback yang dilewatkan sebagai parameter tetapi tidak akan membatalkan atau menghentikan loader. Untuk CursorLoaderitu berarti kursor tetap terbuka dan aktif (jika itu yang terjadi sebelum initLoaderpanggilan). `restartLoader, di sisi lain, akan membatalkan, menghentikan, dan menghancurkan loader (dan menutup sumber data yang mendasarinya seperti kursor) dan membuat loader baru (yang juga akan membuat kursor baru dan menjalankan kembali kueri jika loader tersebut adalah a CursorLoader).

Berikut kode yang disederhanakan untuk kedua metode:

initLoader

LoaderInfo info = mLoaders.get(id);
if (info == null) {
    // Loader doesn't already exist -> create new one
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   // Loader exists -> only replace callbacks   
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}

restartLoader

LoaderInfo info = mLoaders.get(id);
if (info != null) {
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        // does a lot of stuff to deal with already inactive loaders
    } else {
        // Keep track of the previous instance of this loader so we can destroy
        // it when the new one completes.
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

Seperti yang dapat kita lihat jika loader tidak ada (info == null) kedua metode akan membuat loader baru (info = createAndInstallLoader (...)). Dalam hal loader sudah ada initLoaderhanya menggantikan callback (info.mCallbacks = ...) saat restartLoadermenonaktifkan loader lama (itu akan dihancurkan ketika loader baru menyelesaikan pekerjaannya) dan kemudian membuat yang baru.

Dengan demikian dikatakan sekarang jelas kapan harus digunakan initLoaderdan kapan harus digunakan restartLoaderdan mengapa masuk akal untuk memiliki kedua metode tersebut. initLoaderdigunakan untuk memastikan ada loader yang diinisialisasi. Jika tidak ada yang baru dibuat, jika sudah ada itu digunakan kembali. Kami selalu menggunakan metode ini KECUALI kami membutuhkan loader baru karena permintaan untuk menjalankan telah berubah (bukan data yang mendasarinya tetapi permintaan aktual seperti dalam pernyataan SQL untuk CursorLoader), dalam hal ini kami akan memanggil restartLoader.

The Activity / Fragment siklus hidup tidak ada hubungannya dengan keputusan untuk menggunakan satu atau metode lain (dan tidak perlu untuk melacak panggilan menggunakan bendera satu-shot sebagai Simon disarankan)! Keputusan ini dibuat semata-mata berdasarkan "kebutuhan" untuk loader baru. Jika kita ingin menjalankan kueri yang sama dengan yang kita gunakan initLoader, jika kita ingin menjalankan kueri lain yang kita gunakan restartLoader.

Kita selalu bisa menggunakan restartLoadertetapi itu tidak efisien. Setelah rotasi layar atau jika pengguna bernavigasi menjauh dari aplikasi dan kembali lagi ke Aktivitas yang sama, kami biasanya ingin menampilkan hasil kueri yang sama sehingga restartLoadertidak perlu lagi membuat ulang loader dan mengabaikan hasil kueri yang mendasari (berpotensi mahal).

Sangat penting untuk memahami perbedaan antara data yang dimuat dan "kueri" untuk memuat data itu. Mari kita asumsikan kita menggunakan CursorLoader yang menanyakan tabel untuk pesanan. Jika pesanan baru ditambahkan ke tabel itu CursorLoader menggunakan onContentChanged () untuk menginformasikan UI untuk memperbarui dan menunjukkan pesanan baru (tidak perlu digunakan restartLoaderdalam kasus ini). Jika kami ingin menampilkan hanya pesanan terbuka, kami memerlukan kueri baru dan kami akan gunakan restartLoaderuntuk mengembalikan CursorLoader baru yang mencerminkan kueri baru.


Apakah ada hubungan antara kedua metode tersebut?

Mereka membagikan kode untuk membuat Loader baru tetapi mereka melakukan hal yang berbeda ketika loader sudah ada.

Apakah menelepon restartLoaderselalu menelepon initLoader?

Tidak, itu tidak pernah terjadi.

Bisakah saya menelepon restartLoadertanpa harus menelepon initLoader?

Iya.

Apakah aman menelepon initLoaderdua kali untuk menyegarkan data?

Aman untuk menelepon initLoaderdua kali tetapi tidak ada data yang di-refresh.

Kapan saya harus menggunakan salah satu dari keduanya dan mengapa ?


Itu seharusnya (semoga) menjadi jelas setelah penjelasan saya di atas.

Konfigurasi berubah

LoaderManager mempertahankan statusnya di seluruh perubahan konfigurasi (termasuk perubahan orientasi) sehingga Anda akan berpikir tidak ada yang tersisa untuk kami lakukan. Pikirkan lagi...

Pertama-tama, LoaderManager tidak mempertahankan panggilan balik, jadi jika Anda tidak melakukan apa pun, Anda tidak akan menerima panggilan ke metode panggilan balik seperti onLoadFinished()dan sejenisnya dan yang kemungkinan besar akan merusak aplikasi Anda.

Karena itu kita HARUS menelepon setidaknya initLoaderuntuk mengembalikan metode panggilan balik (a restartLoader, tentu saja, mungkin juga). The dokumentasi menyatakan:

Jika pada titik panggilan pemanggil dalam keadaan mulai, dan loader yang diminta sudah ada dan telah menghasilkan datanya, maka panggilan balik onLoadFinished(Loader, D)akan segera dipanggil (di dalam fungsi ini) [...].

Itu berarti jika kita memanggil initLoadersetelah perubahan orientasi, kita akan langsung mendapat onLoadFinishedpanggilan karena data sudah dimuat (dengan asumsi itu adalah kasus sebelum perubahan). Meskipun kedengarannya langsung, itu bisa rumit (bukankah kita semua menyukai Android ...).

Kita harus membedakan antara dua kasus:

  1. Menangani perubahan konfigurasi itu sendiri: ini adalah kasus untuk Fragmen yang menggunakan setRetainInstance (true) atau untuk Aktivitas dengan android:configChangestag yang sesuai dalam manifes. Komponen-komponen ini tidak akan menerima panggilan onCreate setelah mis. Rotasi layar, jadi ingatlah untuk memanggil initLoader/restartLoadermetode panggilan balik lain (misalnya dalam onActivityCreated(Bundle)). Agar dapat menginisialisasi Loader, id loader harus disimpan (misalnya dalam Daftar). Karena komponen dipertahankan di seluruh perubahan konfigurasi kita hanya dapat mengulangi id loader dan panggilan yang ada initLoader(loaderid, ...).
  2. Tidak menangani perubahan konfigurasi itu sendiri: Dalam hal ini Loader dapat diinisialisasi di onCreate tetapi kita perlu secara manual mempertahankan id loader atau kita tidak akan dapat membuat panggilan initLoader / restartLoader yang diperlukan. Jika id disimpan dalam ArrayList, kita akan melakukan
    outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)in onSaveInstanceState dan mengembalikan id di onCreate: loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey)sebelum kita membuat panggilan initLoader.
Emanuel Moecklin
sumber
: +1: Satu poin terakhir. Jika Anda menggunakan initLoader(dan semua panggilan balik telah selesai, Loader menganggur) setelah rotasi Anda tidak akan mendapatkan onLoadFinishedpanggilan balik tetapi jika Anda menggunakan restartLoaderAnda akan?
Blundell
Salah. Metode initLoader memanggil metode onLoadFinished () sebelum kembali (jika loader dimulai dan memiliki data). Saya menambahkan paragraf tentang perubahan konfigurasi untuk menjelaskan ini secara lebih rinci.
Emanuel Moecklin
6
ah tentu saja, kombinasi jawaban Anda dan @ alexlockwood memberikan gambaran lengkap. Saya kira jawabannya untuk orang lain, gunakan initLoader jika Kueri Anda statis dan mulai ulangLoader jika Anda ingin mengubah kueri
Blundell
1
Itu memanggil itu dengan baik: "gunakan initLoader jika Permintaan Anda statis dan restartLoader jika Anda ingin mengubah kueri"
Emanuel Moecklin
1
@Mhd. Tahawi Anda tidak mengubah callback, Anda hanya mengaturnya ke mana pun mereka harus pergi. Setelah rotasi layar, mereka perlu diatur ulang karena Android tidak akan membiarkannya di sekitar untuk mencegah kebocoran memori. Anda bebas mengaturnya untuk apa pun yang Anda inginkan selama mereka melakukan hal yang benar.
Emanuel Moecklin
46

Memanggil initLoaderketika Loader sudah dibuat (ini biasanya terjadi setelah perubahan konfigurasi, misalnya) memberitahu LoaderManager untuk mengirimkan data terbaru Loader ke onLoadFinishedsegera. Jika Loader belum dibuat (ketika aktivitas / fragmen pertama kali diluncurkan, misalnya) panggilan untuk initLoadermemberi tahu LoaderManager untuk memanggil onCreateLoaderuntuk membuat Loader baru.

Memanggil restartLoadermenghancurkan Loader yang sudah ada (serta data yang ada yang terkait dengannya) dan memberi tahu LoaderManager untuk memanggil onCreateLoaderuntuk membuat Loader baru dan untuk memulai load baru.


Dokumentasi juga cukup jelas tentang ini:

  • initLoadermemastikan Loader diinisialisasi dan aktif. Jika loader belum ada, dibuat dan (jika aktivitas / fragmen dimulai) memulai loader. Kalau tidak, loader yang dibuat terakhir digunakan kembali.

  • restartLoadermemulai yang baru atau me-restart Loader yang ada di manajer ini, mendaftarkan panggilan balik ke sana, dan (jika aktivitas / fragmen sedang dimulai) mulai memuatnya. Jika pemuat dengan id yang sama sebelumnya telah dimulai, pemuat itu akan secara otomatis dihancurkan ketika pemuat baru menyelesaikan pekerjaannya. Callback akan dikirim sebelum loader lama dihancurkan.

Alex Lockwood
sumber
@TomanMoney saya menjelaskan apa artinya dalam jawaban saya. Bagian apa yang membuat Anda bingung?
Alex Lockwood
Anda baru saja mengulangi dok. Tetapi dokter tidak memberikan indikasi ke mana masing-masing metode harus digunakan dan mengapa itu buruk untuk mengacaukannya. Dalam pengalaman saya, hanya memanggil restartLoader dan tidak pernah memanggil initLoader berfungsi dengan baik. Jadi ini masih membingungkan.
Tom anMoney
3
@TomanMoney Biasanya Anda gunakan initLoader()di onCreate()/ onActivityCreated()saat aktivitas / fragmen pertama kali dimulai. Dengan cara ini, ketika pengguna pertama kali membuka suatu kegiatan, loader akan dibuat untuk pertama kalinya ... tetapi pada setiap perubahan konfigurasi berikutnya di mana seluruh aktivitas / fragmen harus dihancurkan, panggilan berikut untuk initLoader()hanya akan mengembalikan yang lama Loaderalih - alih membuat yang baru. Biasanya Anda gunakan restartLoader()ketika Anda perlu mengubah Loaderkueri (yaitu Anda ingin mendapatkan data yang difilter / diurutkan, dll.).
Alex Lockwood
4
Saya masih bingung tentang keputusan API untuk memiliki kedua metode, karena mereka memiliki tanda tangan yang sama. Mengapa API tidak bisa menjadi metode startLoader () tunggal yang melakukan "hal yang benar" setiap saat? Saya pikir ini adalah bagian yang membingungkan banyak orang.
Tom anMoney
1
@TomanMoney Dokumentasi di sini mengatakan: developer.android.com/guide/components/loaders.html . "Mereka secara otomatis menyambung kembali ke kursor pemuat terakhir ketika diciptakan kembali setelah perubahan konfigurasi. Dengan demikian, mereka tidak perlu meminta kembali data mereka."
IgorGanapolsky
16

Baru-baru ini saya menemukan masalah dengan beberapa manajer pemuat dan perubahan orientasi layar dan ingin mengatakan bahwa setelah banyak coba-coba, pola berikut ini berfungsi untuk saya di Aktivitas dan Fragmen:

onCreate: call initLoader(s)
          set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
          unset the one-shot in either case.

(dengan kata lain, atur beberapa flag sehingga initLoader adalah selalu dijalankan sekali & yang restartLoader dijalankan pada 2 & selanjutnya lewat melalui onResume )

Juga, ingatlah untuk menetapkan id yang berbeda untuk masing-masing loader Anda dalam suatu Kegiatan (yang bisa sedikit masalah dengan fragmen dalam aktivitas itu jika Anda tidak berhati-hati dengan penomoran Anda)


Saya mencoba menggunakan initLoader saja .... sepertinya tidak bekerja secara efektif.

Mencoba initLoader di onCreate dengan null args (docs bilang ini ok) & restartLoader (dengan args yang valid) di onResume .... docs salah & initLoader melempar pengecualian nullpointer.

Mencoba me-restartLoader saja ... berfungsi untuk sementara waktu tetapi gagal pada orientasi ulang layar ke-5 atau ke-6.

Mencoba initLoader di onResume ; lagi bekerja sebentar & kemudian berhembus. (khusus "Called doRetain when not start:" ... error)

Mencoba yang berikut: (kutipan dari kelas sampul yang memiliki id loader dilewatkan ke konstruktor)

/**
 * start or restart the loader (why bother with 2 separate functions ?) (now I know why)
 * 
 * @param manager
 * @param args
 * @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate 
 */
@Deprecated 
public void start(LoaderManager manager, Bundle args) {
    if (manager.getLoader(this.id) == null) {
        manager.initLoader(this.id, args, this);
    } else {
        manager.restartLoader(this.id, args, this);
    }
}

(yang saya temukan di suatu tempat di Stack-Overflow)

Sekali lagi, ini bekerja untuk sementara waktu tetapi sesekali masih menimbulkan kesalahan.


Dari apa yang saya tahu saat debugging, saya pikir ada sesuatu yang harus dilakukan dengan save / restore keadaan instance yang mengharuskan initLoader (/ s) dijalankan di onCreate dari siklus hidup jika mereka ingin bertahan dari putaran siklus . (Saya mungkin salah.)

dalam kasus Manajer yang tidak dapat dimulai sampai hasilnya kembali dari manajer atau tugas lain (mis. tidak dapat diinisialisasi di onCreate ), saya hanya menggunakan initLoader . (Saya mungkin tidak benar dalam hal ini tetapi tampaknya berfungsi. Loader sekunder ini bukan bagian dari kondisi instan sehingga menggunakan initLoader mungkin benar dalam kasus ini)

lingkaran kehidupan


Melihat diagram dan dokumen, saya akan berpikir bahwa initLoader harus masuk onCreate & restartLoader di onRestart untuk Kegiatan tetapi meninggalkan Fragmen menggunakan beberapa pola yang berbeda dan saya tidak punya waktu untuk menyelidiki apakah ini benar-benar stabil. Adakah yang bisa mengomentari orang lain jika mereka sukses dengan pola kegiatan ini?

Simon
sumber
/ @ Simon 100% benar dan ini harus menjadi jawaban yang diterima. Saya tidak begitu percaya jawabannya dan menghabiskan beberapa jam mencoba menemukan cara yang berbeda untuk membuat ini berhasil. Segera setelah saya memindahkan panggilan initLoader ke onCreate, semuanya mulai bekerja. Anda kemudian perlu bendera satu-shot untuk memperhitungkan waktu onStart dipanggil tetapi tidak onCreate
CjS
2
"Hanya mencoba restartLoader ... berfungsi untuk sementara waktu tetapi pukulan pada orientasi ulang layar ke-5 atau ke-6." Iya kan? Saya hanya mencobanya dan memutar layar seratus kali dan tidak meledak. Pengecualian seperti apa yang Anda dapatkan?
Tom anMoney
-1 Saya menghargai upaya penelitian di balik jawaban ini tetapi sebagian besar hasilnya tidak benar.
Emanuel Moecklin
1
@IgorGanapolsky hampir semuanya. Jika Anda membaca dan memahami jawaban saya, Anda akan memahami apa yang initLoader dan restartLoader lakukan dan kapan harus menggunakannya dan Anda juga akan mengerti mengapa hampir semua kesimpulan Simon salah. Tidak ada koneksi antara siklus hidup fragmen / aktivitas dan keputusan kapan harus menggunakan initLoader / restartLoader (dengan peringatan saya jelaskan di bawah perubahan konfigurasi). Simon menyimpulkan dari percobaan & kesalahan bahwa siklus hidup adalah petunjuk untuk memahami dua metode tetapi tidak.
Emanuel Moecklin
@IgorGanapolsky Saya tidak mencoba mengiklankan jawaban saya sendiri. Saya hanya mencoba membantu pengembang lain dan mencegah mereka menggunakan hasil Simon untuk aplikasi mereka sendiri. Setelah Anda memahami apa yang dimaksudkan untuk kedua metode tersebut, semuanya menjadi cukup jelas dan mudah untuk diterapkan.
Emanuel Moecklin
0

initLoaderakan menggunakan kembali parameter yang sama jika loader sudah ada. Itu segera kembali jika data lama sudah dimuat, bahkan jika Anda menyebutnya dengan parameter baru. Loader idealnya secara otomatis memberitahukan aktivitas data baru. Jika layar diputar, initLoaderakan dipanggil lagi dan data lama akan segera ditampilkan.

restartLoaderadalah untuk saat Anda ingin memaksa memuat ulang dan mengubah parameter juga. Jika Anda membuat layar masuk menggunakan loader, Anda hanya akan menelepon restartLoadersetiap kali tombol diklik. (Tombol dapat diklik beberapa kali karena kredensial yang salah, dll.). Anda hanya akan menelepon initLoadersaat memulihkan keadaan instance tersimpan aktivitas jika layar diputar saat login sedang berlangsung.

Monstieur
sumber
-1

Jika loader sudah ada, restartLoader akan menghentikan / membatalkan / menghancurkan yang lama, sementara initLoader hanya akan menginisialisasi dengan panggilan balik yang diberikan. Saya tidak bisa mengetahui apa yang dilakukan callback lama dalam kasus ini, tapi saya kira mereka akan ditinggalkan begitu saja.

Saya memindai melalui http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java tetapi saya tidak dapat menemukan apa persisnya Perbedaannya adalah, terlepas dari itu metode melakukan hal yang berbeda. Jadi saya akan mengatakan, gunakan initLoader pertama kali dan restart untuk kali berikut, meskipun saya tidak bisa mengatakan dengan pasti apa yang masing-masing dari mereka akan lakukan dengan tepat.

koljaTM
sumber
Dan apa yang akan initLoaderdilakukan dalam kasus ini?
theomega
-1

Loader init pada start pertama menggunakan metode loadInBackground (), pada start kedua akan dihilangkan. Jadi, menurut saya, solusi yang lebih baik adalah:

Loader<?> loa; 
try {
    loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
    loa = null;
}
if (loa == null) {
    getLoaderManager().initLoader(0, null, this);
} else {
    loa.forceLoad();
}

//////////////////////////////////////////////////// ///////////////////////////

protected SimpleCursorAdapter mAdapter;

private abstract class SimpleCursorAdapterLoader 
    extends AsyncTaskLoader <Cursor> {

    public SimpleCursorAdapterLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (takeContentChanged() || mAdapter.isEmpty()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }
}

Saya menghabiskan banyak waktu untuk menemukan solusi ini - restartLoader (...) tidak berfungsi dengan baik dalam kasus saya. Satu-satunya forceLoad () memungkinkan untuk menyelesaikan utas pemuatan sebelumnya tanpa panggilan balik (sehingga Anda akan menyelesaikan semua transaksi db dengan benar) dan memulai kembali utas baru. Ya, ini membutuhkan waktu ekstra, tetapi lebih stabil. Hanya utas mulai terakhir yang akan menerima panggilan balik. Jadi, jika Anda ingin melakukan tes dengan mengganggu transaksi db Anda - selamat datang, cobalah untuk me-restartLoader (...), jika tidak, forceLoad (). Satu-satunya kemudahan restartLoader (...) adalah untuk memberikan data awal baru, maksud saya parameter. Dan jangan lupa untuk menghancurkan loader di metode onDetach () dari Fragment yang sesuai dalam kasus ini. Juga perlu diingat, bahwa beberapa kali, ketika Anda memiliki aktivitas dan, biarkan mereka berkata, 2 fragmen dengan Loader, masing-masing aktivitas inklusif - Anda hanya akan mencapai 2 Manajer Loader, jadi Activity membagikan LoaderManager-nya dengan Fragmen, yang diperlihatkan di layar terlebih dahulu saat memuat. Coba LoaderManager.enableDebugLogging (true); untuk melihat detail dalam setiap kasus tertentu.

pengguna1700099
sumber
2
-1 untuk membungkus panggilan ke getLoader(0)dalam try { ... } catch (Exception e) { ... }.
Alex Lockwood