Android'de Looper, Handler ve MessageQueue arasındaki ilişki nedir?


96

Ben resmi Android dokümantasyon / rehber kontrol ettikten Looper, Handlerve MessageQueue. Ama anlayamadım. Android'de yeniyim ve bu kavramlarla kafam çok karıştı.

Yanıtlar:


103

A Looper, bir mesaj işleme döngüsüdür: bir MessageQueue. LooperSınıf genellikle ile bağlantılı olarak kullanılır HandlerThread(bir alt sınıfı Thread).

A Handler, a ile etkileşimi kolaylaştıran bir yardımcı sınıftır - esas olarak iş parçacığına Loopermesajlar ve Runnablenesneler göndererek MessageQueue. Bir Handleroluşturulduğunda, belirli bir Looper(ve ilişkili iş parçacığı ve mesaj kuyruğu) bağlanır .

Tipik kullanımda, HandlerThreadbir Handlernesneyi yaratır ve başlatırsınız , ardından diğer iş parçacıklarının HandlerThreadörnekle etkileşime girebileceği bir nesne (veya nesneler) oluşturursunuz . Bir kez oluşturulduktan sonra hangi iş parçacığının zamanlama yöntemlerini ( vb.) Kullanabileceği konusunda herhangi bir kısıtlama olmamasına rağmen, Handlerüzerinde çalıştırılırken HandlerThreadoluşturulmalıdır .Handlerpost(Runnable)

Bir Android uygulamasındaki ana iş parçacığı (UI iş parçacığı olarak da bilinir), uygulama örneğiniz oluşturulmadan önce bir işleyici iş parçacığı olarak ayarlanır.

Sınıf dokümanlarının yanı sıra, burada tüm bunların güzel bir tartışması var .

Not: Yukarıda belirtilen tüm sınıflar paketin içindedir android.os.


@Ted Hopp - Looper'in mesaj kuyruğu, Thread'ın mesaj kuyruğundan farklı mı?
CopsOnRoad

2
@Jack - Aynı şey. Android API belgeleri, MessageQueueMessageQueueLooper
Ted Hopp

95

Android'deki ana iş parçacığı dışındaki parçacıklarından doğrudan UI bileşenlerini güncellemenin yasa dışı olduğu yaygın olarak bilinmektedir . Bu android belgesi ( UI İş Parçacığında Pahalı İşlemleri İşleme ), bazı pahalı işler yapmak ve bittikten sonra kullanıcı arabirimini güncellemek için ayrı bir iş parçacığı başlatmamız gerekirse izlenecek adımları önerir . Buradaki fikir, ana iş parçacığı ile ilişkilendirilmiş bir İşleyici nesnesi oluşturmak ve uygun zamanda ona bir Çalıştırılabilir öğe göndermektir. Bu , ana iş parçacığında çağrılacaktır . Bu mekanizma, Looper ve Handler sınıflarıyla gerçekleştirilir.Runnable

LooperSınıf bir tutar MessageQueue bir liste içerir mesajlar . Looper'ın önemli bir karakteri, içinde oluşturulduğu iş parçacığı ile ilişkili olmasıdır . Bu ilişki sonsuza kadar korunur ve bozulamaz veya değiştirilemez. Ayrıca iplik dikkat aşkın ile ilişkili olamaz biri . Bu ilişkiyi garanti etmek için , iş parçacığı yerel depolamada saklanır ve doğrudan kurucusu aracılığıyla oluşturulamaz. Bunu oluşturmanın tek yolu, hazır statik yöntemi çağırmaktır . yöntemi hazırlamak önce ThreadLocal'ı incelerLooperLooperLooperLoopermevcut iş parçacığının, iş parçacığı ile ilişkili bir Looper olmadığından emin olmak için. İncelemeden sonra yeni Looperbir tane oluşturulur ve kaydedilir ThreadLocal. Hazırladıktan sonra Looper, yeni mesajları kontrol etmek ve onlarla ilgilenmek için üzerinde döngü yöntemini çağırabiliriz Handler.

Adından da anlaşılacağı gibi, Handlersınıf esas olarak mevcut iş parçacığının mesajlarını işlemekten (eklemek, kaldırmak, göndermek) sorumludur MessageQueue. Bir Handlerörnek ayrıca bir iş parçacığına da bağlıdır. Handler ve iplik arasındaki bağlanma ile elde edilir Looperve MessageQueue. Bir Handlerolduğu zaman bağlı bir Looperve daha sonra bağlanmış ilişkili iplik ile Looper. Bunun tersine Looper, birden çok İşleyici örneği aynı iş parçacığına bağlanabilir. Post veya benzeri herhangi bir yöntemi çağırdığımızda Handler, ilişkili olana yeni bir mesaj eklenir MessageQueue. Mesajın hedef alanı, mevcut Handlerörneğe ayarlanır . Ne zamanLooperbu mesajı aldığında , mesajın hedef alanında dispatchMessage'ı çağırır , böylece mesaj işlenecek İşleyici örneğine, ancak doğru iş parçacığına geri döner. Arasındaki ilişkiler Looper, Handlerve MessageQueueaşağıda gösterilmiştir:

görüntü açıklamasını buraya girin


5
Teşekkürler! ama işleyicinin önce mesajı mesaj kuyruğuna göndermesinin ve ardından aynı kuyruktaki mesajı işlemesinin ne anlamı var ? neden doğrudan mesajla ilgilenmiyor?
Blake

4
@Blake b / bir iplik (sigara tutucu ipliği) gelen gönderme ama başka bir iplik (tutucu ipliği) mesajı ele alma vardır C
numan salati

Developer.android.com adresinde belgelendirilenden çok daha iyi - ancak sağladığınız diyagramın kodunu görmek güzel olurdu.
tfmontague

@numansalati - Handler lüper iş parçacığından mesaj gönderemiyor mu?
CopsOnRoad

79

Looper ile başlayalım. Looper'ın ne olduğunu anladığınızda Looper, Handler ve MessageQueue arasındaki ilişkiyi daha kolay anlayabilirsiniz. Ayrıca, GUI çerçevesi bağlamında Looper'ın ne olduğunu daha iyi anlayabilirsiniz. Looper 2 şey yapmak için yapılmıştır.

1) Looper , yöntemi geri döndüğünde sona eren normal bir iş parçacığını , GUI çerçevesinde ihtiyaç duyulan Android uygulaması çalışana kadar sürekli çalışan bir şeye dönüştürür (Teknik olarak, yöntem döndüğünde hala sona erer . Ama ne demek istediğimi açıklığa kavuşturmama izin verin, altında).run()run()

2) Looper , GUI çerçevesinde de gerekli olan, yapılacak işlerin sıraya alındığı bir kuyruk sağlar .

Bildiğiniz gibi, bir uygulama başlatıldığında, sistem uygulama için "ana" adı verilen bir yürütme dizisi oluşturur ve Android uygulamaları normalde tamamen tek bir iş parçacığı üzerinde varsayılan olarak "ana iş parçacığı" üzerinde çalışır. Ancak ana konu gizli, özel bir konu değil . Bu sadece new Thread()kodla oluşturabileceğiniz normal bir iş parçacığıdır , yani run()metodu döndüğünde sona erer ! Aşağıdaki örneği düşünün.

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

Şimdi bu basit prensibi Android uygulamasına uygulayalım. Bir Android uygulaması normal bir iş parçacığında çalıştırılırsa ne olur? "Ana" veya "UI" adı verilen bir iş parçacığı veya uygulamayı ne başlatırsa başlatır ve tüm UI'yi çizer. Böylece kullanıcılara ilk ekran gösterilir. Peki şimdi ne olacak? Ana iş parçacığı sona mı eriyor? Hayır, olmamalı. Kullanıcılar bir şey yapana kadar beklemeli, değil mi? Ama bu davranışı nasıl başarabiliriz? Peki, deneyebiliriz Object.wait()veyaThread.sleep(). Örneğin, ana iş parçacığı ilk ekranı görüntülemek için ilk işini bitirir ve uyur. Yapılacak yeni bir iş getirildiğinde uyanır, yani kesintiye uğrar. Şimdiye kadar her şey yolunda, ancak şu anda birden fazla işi tutmak için kuyruk benzeri bir veri yapısına ihtiyacımız var. Bir kullanıcının ekrana seri olarak dokunduğu ve bir görevin tamamlanması daha uzun sürdüğü bir durumu düşünün. Bu nedenle, ilk giren ilk çıkar tarzında yapılacak işleri tutmak için bir veri yapısına ihtiyacımız var. Ayrıca, kesme kullanarak iş parçacığını sürekli çalıştıran ve işleyen işi uygulamanın kolay olmadığını ve karmaşık ve çoğu zaman sürdürülemez bir koda yol açtığını hayal edebilirsiniz. Böyle bir amaç için yeni bir mekanizma yaratmayı tercih ederiz ve Looper da bundan ibarettir . Looper sınıfının resmi belgeder, "İş parçacığı varsayılan olarak kendileriyle ilişkili bir ileti döngüsüne sahip değildir" ve Looper, "iş parçacığı için bir ileti döngüsü çalıştırmak için kullanılan bir sınıftır". Şimdi ne anlama geldiğini anlayabilirsiniz.

Handler ve MessageQueue'ya geçelim. Öncelikle, MessageQueue yukarıda bahsettiğim kuyruktur. Bir Looper içinde bulunur ve işte bu kadar. Looper sınıfının kaynak koduyla kontrol edebilirsiniz . Looper sınıfı, MessageQueue'nun bir üye değişkenine sahiptir.

O halde Handler nedir? Bir kuyruk varsa, kuyruğa yeni bir görevi sıraya koymamızı sağlayacak bir yöntem olmalı, değil mi? Handler'ın yaptığı budur. Yeni bir görevi çeşitli post(Runnable r)yöntemler kullanarak bir kuyruğa (MessageQueue) sıralayabiliriz . Bu kadar. Bu tamamen Looper, Handler ve MessageQueue ile ilgili.

Son sözüm, temelde Looper, GUI çerçevesinde meydana gelen bir sorunu çözmek için yapılmış bir sınıftır. Ancak bu tür ihtiyaçlar başka durumlarda da olabilir. Aslında, çok iş parçacıklı uygulama için oldukça ünlü bir kalıptır ve Doug Lea'nın "Java'da Eşzamanlı Programlama" bölümünde daha fazla bilgi edinebilirsiniz (Özellikle, bölüm 4.1.4 "İş Parçacıkları" yardımcı olacaktır). Ayrıca, bu tür bir mekanizmanın Android çerçevesinde benzersiz olmadığını düşünebilirsiniz, ancak tüm GUI çerçevelerinin buna biraz benzer olması gerekebilir. Java Swing çerçevesinde de hemen hemen aynı mekanizmayı bulabilirsiniz.


4
En iyi cevap. Bu ayrıntılı açıklamadan daha fazlasını öğrendiniz. Daha ayrıntılı olarak giden bir blog yazısı var mı merak ediyorum.
capt.swag

Mesajlar, İşleyici kullanılmadan MessageQueue'ya eklenebilir mi?
CopsOnRoad

@CopsOnRoad hayır, doğrudan eklenemezler.
Faisal Naseer

Günümü yaptım ... sana çok sevgiler :)
Rahul Matte

26

MessageQueue: A. Tarafından gönderilecek mesajların listesini tutan düşük seviyeli bir sınıftır Looper. Mesajlar doğrudan a'ya değil MessageQueue, bunun yerine . [ 3 ] Handlerile ilişkili nesneler aracılığıyla eklenir Looper.

Looper: MessageQueueGönderilecek mesajları içeren a üzerinde döngü oluşturur. Kuyruğun yönetilmesinin asıl görevi Handler, mesaj kuyruğundaki mesajların işlenmesinden (ekleme, çıkarma, gönderme) sorumlu olan tarafından yapılır . [ 2 ]

Handler: Bu gönderip süreç sağlar Messageve Runnablebir iş parçacığı en ilişkili nesnelerin MessageQueue. Her İşleyici örneği, tek bir iş parçacığı ve bu iş parçacığının ileti kuyruğu ile ilişkilidir. [ 4 ]

Yeni bir tane oluşturduğunuzda Handler, onu oluşturan iş parçacığının iş parçacığı / ileti kuyruğuna bağlıdır - bu noktadan sonra, iletileri ve çalıştırılabilirleri bu ileti kuyruğuna gönderir ve ileti kuyruğundan çıktıkça bunları yürütür. .

Daha iyi anlamak için lütfen aşağıdaki görüntüye [ 2 ] bakın.

görüntü açıklamasını buraya girin


0

Cevabı @K_Anas ile bir örnekle genişletmek, belirtildiği gibi

Android'deki ana iş parçacığı dışındaki iş parçacıklarından doğrudan UI bileşenlerini güncellemenin yasa dışı olduğu yaygın olarak bilinmektedir.

örneğin, UI'yi Thread kullanarak güncellemeye çalışırsanız.

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

uygulamanız istisnai olarak kilitlenecek.

android.view.ViewRoot $ CalledFromWrongThreadException: Yalnızca bir görünüm hiyerarşisi oluşturan orijinal iş parçacığı görünümlerine dokunabilir.

başka bir deyişle ie veya 'ye Handleratıfta bulunan ve görevi olarak ileten' i kullanmanız gerekir .MainLooperMain ThreadUI ThreadRunnable

  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() ;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.