Saya telah memeriksa dokumentasi / panduan resmi Android Looper
, Handler
dan MessageQueue
. Tapi saya tidak bisa mendapatkannya. Saya baru mengenal android, dan menjadi sangat bingung dengan konsep ini.
Jawaban:
A Looper
adalah loop penanganan pesan: ia membaca dan memproses item dari a MessageQueue
. The Looper
kelas biasanya digunakan dalam hubungannya dengan HandlerThread
(subclass dari Thread
).
A Handler
adalah kelas utilitas yang memfasilitasi interaksi dengan Looper
—terutama dengan memposting pesan dan Runnable
objek ke utas MessageQueue
. Ketika a Handler
dibuat, itu terikat ke tertentu Looper
(dan utas terkait dan antrian pesan).
Dalam penggunaan umum, Anda membuat dan memulai HandlerThread
, lalu membuat Handler
objek (atau objek) yang dengannya utas lain dapat berinteraksi dengan HandlerThread
instance. The Handler
harus dibuat sementara berjalan pada HandlerThread
, meskipun sekali dibuat tidak ada pembatasan pada apa benang dapat menggunakan Handler
's metode penjadwalan ( post(Runnable)
, dll)
Utas utama (alias utas UI) dalam aplikasi Android disiapkan sebagai utas penangan sebelum instance aplikasi Anda dibuat.
Selain dokumen kelas, ada diskusi bagus tentang semua ini di sini .
PS Semua kelas yang disebutkan di atas ada di dalam paket android.os
.
MessageQueue
Android API menyatakan bahwa a MessageQueue
adalah " kelas tingkat rendah yang menyimpan daftar pesan yang akan dikirim oleh Looper
. "
Telah diketahui secara luas bahwa memperbarui komponen UI langsung dari utas selain utas utama di android adalah ilegal . Dokumen android ini ( Menangani Operasi yang Mahal di UI Thread ) menyarankan langkah-langkah yang harus diikuti jika kita perlu memulai thread terpisah untuk melakukan pekerjaan yang mahal dan memperbarui UI setelah selesai. Idenya adalah untuk membuat objek Handler yang terkait dengan utas utama , dan memposting Runnable padanya pada waktu yang tepat. Ini Runnable
akan dipanggil di utas utama . Mekanisme ini diimplementasikan dengan kelas Looper dan Handler .
The Looper
kelas mempertahankan MessageQueue , yang berisi daftar pesan . Karakter penting Looper adalah bahwa ini terkait dengan utas tempat Looper
dibuatnya . Asosiasi ini disimpan selamanya dan tidak dapat dipatahkan atau diubah. Perhatikan juga bahwa utas tidak dapat dikaitkan dengan lebih dari satu Looper
. Untuk menjamin pengaitan ini, Looper
disimpan dalam penyimpanan lokal-thread, dan tidak dapat dibuat melalui konstruktornya secara langsung. Satu-satunya cara untuk membuatnya adalah dengan memanggil siapkan metode statis Looper
. persiapkan metode pertama memeriksa ThreadLocaldari utas saat ini untuk memastikan bahwa belum ada Looper yang terkait dengan utas tersebut. Setelah pemeriksaan, yang baru Looper
dibuat dan disimpan di ThreadLocal
. Setelah menyiapkan Looper
, kita dapat memanggil metode loop untuk memeriksa pesan baru dan harus Handler
menanganinya.
Seperti namanya, Handler
kelas ini terutama bertanggung jawab untuk menangani (menambah, menghapus, mengirim) pesan dari utas saat ini MessageQueue
. Sebuah Handler
instance juga terikat ke utas. The mengikat antara Handler dan Thread dicapai melalui Looper
dan MessageQueue
. Sebuah Handler
tersebut selalu terikat sebuah Looper
, dan kemudian terikat pada benang terkait dengan Looper
. Tidak seperti Looper
, beberapa contoh Handler dapat diikat ke utas yang sama. Setiap kali kita memanggil post atau metode serupa di Handler
, pesan baru ditambahkan ke terkait MessageQueue
. Bidang target pesan disetel ke Handler
contoh saat ini . KetikaLooper
menerima pesan ini, ia memanggil dispatchMessage pada bidang target pesan, sehingga pesan tersebut dirutekan kembali ke contoh Handler untuk ditangani, tetapi pada utas yang benar. Hubungan antara Looper
, Handler
dan MessageQueue
ditampilkan di bawah ini:
Mari kita mulai dengan Looper. Anda dapat memahami hubungan antara Looper, Handler, dan MessageQueue dengan lebih mudah jika Anda memahami apa itu Looper. Anda juga dapat lebih memahami apa itu Looper dalam konteks kerangka GUI. Looper dibuat untuk melakukan 2 hal.
1) Looper mengubah utas normal , yang berhenti ketika run()
metodenya kembali, menjadi sesuatu yang berjalan terus menerus hingga aplikasi Android berjalan , yang diperlukan dalam kerangka GUI (Secara teknis, ini masih berakhir ketika run()
metode kembali. Tapi izinkan saya menjelaskan apa yang saya maksud, di bawah).
2) Looper menyediakan antrian di mana pekerjaan yang harus diselesaikan diantrekan, yang juga diperlukan dalam kerangka GUI.
Seperti yang Anda ketahui, saat aplikasi diluncurkan, sistem membuat thread eksekusi untuk aplikasi tersebut, yang disebut "main", dan aplikasi Android biasanya berjalan sepenuhnya pada satu thread secara default sebagai "thread utama". Tapi utas utama bukanlah utas rahasia, utas khusus . Itu hanya utas normal yang juga dapat Anda buat dengan new Thread()
kode, yang berarti itu berakhir ketika run()
metodenya kembali! Pikirkan contoh di bawah ini.
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
Sekarang, mari terapkan prinsip sederhana ini ke aplikasi Android. Apa yang akan terjadi jika aplikasi Android dijalankan di thread normal? Sebuah utas bernama "main" atau "UI" atau apa pun yang memulai aplikasi, dan menggambar semua UI. Jadi, layar pertama ditampilkan kepada pengguna. Jadi bagaimana sekarang? Utas utama berakhir? Tidak, seharusnya tidak. Itu harus menunggu sampai pengguna melakukan sesuatu, bukan? Tetapi bagaimana kita bisa mencapai perilaku ini? Nah, kita bisa coba dengan Object.wait()
atauThread.sleep()
. Misalnya, utas utama menyelesaikan tugas awalnya untuk menampilkan layar pertama, dan tidur. Itu bangun, yang berarti terputus, ketika pekerjaan baru yang harus dilakukan diambil. Sejauh ini bagus, tapi saat ini kita membutuhkan struktur data seperti antrian untuk menampung banyak pekerjaan. Pikirkan tentang kasus ketika pengguna menyentuh layar secara berurutan, dan tugas membutuhkan waktu lebih lama untuk menyelesaikannya. Jadi, kita perlu memiliki struktur data untuk menampung pekerjaan yang harus dilakukan dengan cara pertama masuk pertama keluar. Selain itu, Anda mungkin membayangkan, mengimplementasikan thread yang selalu-berjalan-dan-proses-pekerjaan-ketika-tiba menggunakan interupsi tidaklah mudah, dan mengarah ke kode yang kompleks dan seringkali tidak dapat dikelola. Kami lebih suka membuat mekanisme baru untuk tujuan seperti itu, dan itulah Looper . The dokumen resmi kelas Loopermengatakan, "Thread secara default tidak memiliki loop pesan yang terkait dengannya", dan Looper adalah kelas "yang digunakan untuk menjalankan loop pesan untuk thread". Sekarang Anda bisa mengerti artinya.
Mari pindah ke Handler dan MessageQueue. Pertama, MessageQueue adalah antrian yang saya sebutkan di atas. Itu berada di dalam Looper, dan hanya itu. Anda dapat memeriksanya dengan kode sumber kelas Looper . Kelas Looper memiliki variabel anggota MessageQueue.
Lalu, apakah Handler itu? Jika ada antrian, maka harus ada metode yang memungkinkan kita memasukkan tugas baru ke antrian, bukan? Itulah yang dilakukan Handler. Kita bisa memasukkan tugas baru ke antrean (MessageQueue) menggunakan berbagai post(Runnable r)
metode. Itu dia. Ini semua tentang Looper, Handler, dan MessageQueue.
Kata terakhir saya adalah, jadi pada dasarnya Looper adalah class yang dibuat untuk mengatasi masalah yang terjadi pada framework GUI. Tetapi kebutuhan semacam ini juga dapat terjadi dalam situasi lain. Sebenarnya ini adalah pola yang cukup terkenal untuk aplikasi multi utas, dan Anda dapat mempelajarinya lebih lanjut di "Pemrograman Bersamaan di Java" oleh Doug Lea (Terutama, bab 4.1.4 "Utas Pekerja" akan sangat membantu). Selain itu, Anda dapat membayangkan mekanisme semacam ini tidak unik dalam kerangka kerja Android, tetapi semua kerangka kerja GUI mungkin perlu serupa dengan ini. Anda dapat menemukan mekanisme yang hampir sama dalam kerangka Java Swing.
MessageQueue
: Ini adalah kelas tingkat rendah yang menyimpan daftar pesan yang akan dikirim oleh a Looper
. Pesan tidak ditambahkan secara langsung ke a MessageQueue
, melainkan melalui Handler
objek yang terkait dengan Looper
. [ 3 ]
Looper
: Ini loop di atas MessageQueue
yang berisi pesan yang akan dikirim. Tugas sebenarnya dari mengatur antrian dilakukan oleh Handler
yang bertanggung jawab untuk menangani (menambah, menghapus, mengirim) pesan dalam antrian pesan. [ 2 ]
Handler
: Ini memungkinkan Anda untuk mengirim dan memproses Message
dan Runnable
objek yang terkait dengan utas MessageQueue
. Setiap contoh Handler dikaitkan dengan satu utas dan antrian pesan utas itu. [ 4 ]
Ketika Anda membuat yang baru Handler
, itu terikat ke utas / antrian pesan dari utas yang membuatnya - sejak saat itu , itu akan mengirimkan pesan dan runnable ke antrian pesan itu dan mengeksekusinya saat mereka keluar dari antrian pesan .
Mohon, lihat gambar di bawah [ 2 ] untuk pemahaman yang lebih baik.
Memperluas jawabannya, oleh @K_Anas, dengan contoh, Seperti yang dinyatakan
Telah diketahui secara luas bahwa memperbarui komponen UI langsung dari utas selain utas utama di android adalah ilegal.
misalnya jika Anda mencoba memperbarui UI menggunakan Thread.
int count = 0;
new Thread(new Runnable(){
@Override
public void run() {
try {
while(true) {
sleep(1000);
count++;
textView.setText(String.valueOf(count));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start();
aplikasi Anda akan mogok dengan pengecualian.
android.view.ViewRoot $ CalledFromWrongThreadException: Hanya utas asli yang membuat hierarki tampilan yang dapat menyentuh tampilannya.
dengan kata lain Anda perlu menggunakan Handler
yang tetap mengacu pada tugas MainLooper
ie Main Thread
atau UI Thread
dan lulus sebagai Runnable
.
Handler handler = new Handler(getApplicationContext().getMainLooper);
int count = 0;
new Thread(new Runnable(){
@Override
public void run() {
try {
while(true) {
sleep(1000);
count++;
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(String.valueOf(count));
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start() ;