Saya telah menyelidiki masalah ini selama berbulan-bulan sekarang, muncul dengan solusi yang berbeda untuk itu, yang saya tidak senang karena mereka semua peretasan besar. Saya masih tidak percaya bahwa kelas yang cacat dalam desain membuatnya masuk ke kerangka kerja dan tidak ada yang membicarakannya, jadi saya kira saya pasti kehilangan sesuatu.
Masalahnya dengan AsyncTask
. Menurut dokumentasi itu
"memungkinkan untuk melakukan operasi latar belakang dan menerbitkan hasil pada utas UI tanpa harus memanipulasi utas dan / atau penangan."
Contoh kemudian terus menunjukkan bagaimana beberapa showDialog()
metode teladan dipanggil onPostExecute()
. Ini, bagaimanapun, tampaknya sepenuhnya dibuat - buat untuk saya, karena menunjukkan dialog selalu membutuhkan referensi ke yang valid Context
, dan AsyncTask tidak boleh memiliki referensi yang kuat untuk objek konteks .
Alasannya jelas: bagaimana jika aktivitas itu hancur yang memicu tugas? Ini dapat terjadi setiap saat, misalnya karena Anda membalik layar. Jika tugas akan menyimpan referensi ke konteks yang membuatnya, Anda tidak hanya berpegang pada objek konteks yang tidak berguna (jendela akan hancur dan interaksi UI apa pun akan gagal dengan pengecualian!), Anda bahkan berisiko membuat kebocoran memori.
Kecuali jika logika saya cacat di sini, ini berarti: onPostExecute()
sama sekali tidak berguna, karena apa gunanya metode ini berjalan di utas UI jika Anda tidak memiliki akses ke konteks apa pun? Anda tidak dapat melakukan sesuatu yang berarti di sini.
Satu solusi adalah dengan tidak mengirimkan instance konteks ke AsyncTask, tetapi sebuah Handler
instance. Itu bekerja: karena Handler secara longgar mengikat konteks dan tugas, Anda dapat bertukar pesan di antara mereka tanpa risiko kebocoran (kan?). Tetapi itu berarti bahwa premis AsyncTask, yaitu bahwa Anda tidak perlu repot dengan penangan, salah. Sepertinya juga menyalahgunakan Handler, karena Anda mengirim dan menerima pesan pada utas yang sama (Anda membuatnya pada utas UI dan mengirimkannya dalam onPostExecute () yang juga dijalankan pada utas UI).
Untuk melengkapi semuanya, bahkan dengan penyelesaian itu, Anda masih memiliki masalah bahwa ketika konteksnya dihancurkan, Anda tidak memiliki catatan tugas yang dipecatnya. Itu berarti bahwa Anda harus memulai kembali tugas apa pun ketika menciptakan kembali konteks, misalnya setelah perubahan orientasi layar. Ini lambat dan boros.
Solusi saya untuk ini (seperti yang diterapkan di perpustakaan Droid-Fu ) adalah mempertahankan pemetaan WeakReference
s dari nama komponen ke instance mereka saat ini pada objek aplikasi yang unik. Setiap kali AsyncTask dimulai, ia merekam konteks panggilan di peta itu, dan pada setiap panggilan balik, ia akan mengambil instance konteks saat ini dari pemetaan itu. Ini memastikan bahwa Anda tidak akan pernah merujuk contoh konteks basi dan Anda selalu memiliki akses ke konteks yang valid dalam panggilan balik sehingga Anda dapat melakukan pekerjaan UI yang bermakna di sana. Itu juga tidak bocor, karena referensi lemah dan dihapus ketika tidak ada contoh komponen yang diberikan ada lagi.
Namun, ini merupakan solusi yang kompleks dan mengharuskan untuk mensubkelas beberapa kelas perpustakaan Droid-Fu, menjadikannya pendekatan yang cukup mengganggu.
Sekarang saya hanya ingin tahu: Apakah saya hanya melewatkan sesuatu secara besar-besaran atau apakah AsyncTask sepenuhnya cacat? Bagaimana pengalaman Anda bekerja dengannya? Bagaimana Anda memecahkan masalah ini?
Terima kasih atas masukan Anda.
sumber
Jawaban:
Bagaimana dengan sesuatu yang seperti ini:
sumber
Secara manual memisahkan aktivitas dari
AsyncTask
dalamonDestroy()
. Kaitkan kembali aktivitas baru secara manual keAsyncTask
dalamonCreate()
. Ini membutuhkan kelas dalam statis atau kelas Java standar, ditambah mungkin 10 baris kode.sumber
Sepertinya
AsyncTask
sedikit lebih dari sekedar cacat secara konseptual . Itu juga tidak dapat digunakan oleh masalah kompatibilitas. Dokumen Android membaca:Ketika pertama kali diperkenalkan, AsyncTasks dieksekusi secara serial pada utas latar belakang tunggal. Dimulai dengan DONUT, ini diubah menjadi kumpulan untaian yang memungkinkan beberapa tugas beroperasi secara paralel. Mulai HONEYCOMB, tugas kembali dieksekusi pada utas tunggal untuk menghindari kesalahan aplikasi umum yang disebabkan oleh eksekusi paralel. Jika Anda benar-benar ingin eksekusi paralel, Anda dapat menggunakan
executeOnExecutor(Executor, Params...)
versi metode ini denganTHREAD_POOL_EXECUTOR
; Namun, lihat komentar di sana untuk peringatan tentang penggunaannya.Kedua
executeOnExecutor()
danTHREAD_POOL_EXECUTOR
yang Ditambahkan di tingkat API 11 (3.0.x Android, Honeycomb).Ini berarti bahwa jika Anda membuat dua
AsyncTask
untuk mengunduh dua file, unduhan ke-2 tidak akan mulai sampai yang pertama selesai. Jika Anda mengobrol melalui dua server, dan server pertama mati, Anda tidak akan terhubung ke yang kedua sebelum koneksi ke yang pertama kali habis. (Kecuali jika Anda menggunakan fitur API11 baru, tentu saja, tetapi ini akan membuat kode Anda tidak kompatibel dengan 2.x).Dan jika Anda ingin menargetkan 2.x dan 3.0+, semuanya menjadi sangat rumit.
Selain itu, dokumen mengatakan:
Perhatian: Masalah lain yang mungkin Anda temui saat menggunakan utas pekerja adalah restart yang tidak terduga dalam aktivitas Anda karena perubahan konfigurasi runtime (seperti ketika pengguna mengubah orientasi layar), yang dapat menghancurkan utas pekerja Anda . Untuk melihat bagaimana Anda bisa bertahan tugas Anda selama salah satu dari ini restart dan bagaimana cara membatalkan tugas dengan benar ketika aktivitas dihancurkan, lihat kode sumber untuk aplikasi sampel Rak.
sumber
Mungkin kita semua, termasuk Google, menyalahgunakan
AsyncTask
dari sudut pandang MVC .Suatu Kegiatan adalah Pengendali , dan pengontrol tidak boleh memulai operasi yang mungkin lebih lama dari Tampilan . Yaitu, AsyncTasks harus digunakan dari Model , dari kelas yang tidak terikat pada siklus hidup Kegiatan - ingat bahwa Kegiatan dihancurkan secara bergiliran. (Mengenai View , Anda biasanya tidak memprogram kelas yang berasal dari mis. Android.widget.Button, tetapi Anda bisa. Biasanya, satu-satunya hal yang Anda lakukan tentang View adalah xml.)
Dengan kata lain, salah menempatkan derivatif AsyncTask dalam metode Kegiatan. OTOH, jika kita tidak boleh menggunakan AsyncTasks dalam Aktivitas, AsyncTask kehilangan daya tariknya: Dulu diiklankan sebagai perbaikan cepat dan mudah.
sumber
Saya tidak yakin benar bahwa Anda berisiko kebocoran memori dengan referensi ke konteks dari AsyncTask.
Cara biasa untuk mengimplementasikannya adalah dengan membuat instance AsyncTask baru dalam ruang lingkup salah satu metode Kegiatan. Jadi jika aktivitasnya dihancurkan, maka setelah AsyncTask selesai, bukankah itu tidak dapat dijangkau dan memenuhi syarat untuk pengumpulan sampah? Jadi referensi ke aktivitas itu tidak masalah karena AsyncTask itu sendiri tidak akan bergaul.
sumber
Akan lebih kuat untuk menyimpan WeekReference pada aktivitas Anda:
sumber
Mengapa tidak mengganti
onPause()
metode dalam Aktivitas yang dimiliki dan membatalkannyaAsyncTask
dari sana?sumber
onPause
yang sama buruknya dengan memegangnya di tempat lain? Yaitu, Anda bisa mendapatkan ANR?onPause
anithing maupun lainnya) karena kita berisiko terkena ANR.Anda benar sekali - itulah sebabnya mengapa gerakan yang jauh dari menggunakan tugas / pemuat async dalam aktivitas untuk mengambil data mendapatkan momentum. Salah satu cara baru adalah dengan menggunakan kerangka kerja Volley yang pada dasarnya menyediakan panggilan balik setelah data siap - jauh lebih konsisten dengan model MVC. Volley dipopulerkan di Google I / O 2013. Tidak yakin mengapa lebih banyak orang tidak menyadari hal ini.
sumber
Secara pribadi, saya hanya memperluas Utas dan menggunakan antarmuka panggilan balik untuk memperbarui UI. Saya tidak pernah bisa membuat AsyncTask bekerja dengan benar tanpa masalah FC. Saya juga menggunakan antrian yang tidak menghalangi untuk mengelola kumpulan eksekusi.
sumber
Saya pikir batal bekerja tetapi tidak.
di sini mereka RTFMing tentang hal itu:
"" Jika tugas sudah dimulai, maka parameter mayInterruptIfRunning menentukan apakah utas yang menjalankan tugas ini harus diinterupsi dalam upaya untuk menghentikan tugas. "
Itu tidak berarti, bagaimanapun, bahwa utas itu interruptible. Itu hal Java, bukan hal AsyncTask. "
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
sumber
Anda akan lebih baik memikirkan AsyncTask sebagai sesuatu yang lebih erat digabungkan dengan Activity, Context, ContextWrapper, dll. Lebih nyaman ketika ruang lingkupnya dipahami sepenuhnya.
Pastikan bahwa Anda memiliki kebijakan pembatalan dalam siklus hidup Anda sehingga pada akhirnya akan menjadi sampah yang dikumpulkan dan tidak lagi menyimpan referensi ke aktivitas Anda dan itu juga bisa menjadi sampah yang dikumpulkan.
Tanpa membatalkan AsyncTask Anda saat melintasi jauh dari Konteks Anda, Anda akan mengalami kebocoran memori dan NullPointerExceptions, jika Anda hanya perlu memberikan umpan balik seperti Toast dialog sederhana, maka tunggal Konteks Aplikasi Anda akan membantu menghindari masalah NPE.
AsyncTask tidak semuanya buruk tetapi pasti ada banyak keajaiban yang terjadi yang dapat menyebabkan beberapa jebakan yang tidak terduga.
sumber
Mengenai "pengalaman bekerja dengannya": adalah mungkin untuk menghentikan proses bersama dengan semua AsyncTasks, Android akan menciptakan kembali tumpukan aktivitas sehingga pengguna tidak akan menyebutkan apa pun.
sumber