ความสัมพันธ์ระหว่าง Looper, Handler และ MessageQueue ใน Android คืออะไร?


96

ผมได้ตรวจสอบอย่างเป็นทางการ Android เอกสาร / คู่มือสำหรับLooper, และHandler MessageQueueแต่ฉันไม่สามารถรับมันได้ ฉันยังใหม่กับ Android และสับสนกับแนวคิดเหล่านี้มาก

คำตอบ:


103

A Looperคือลูปการจัดการข้อความ: อ่านและประมวลผลรายการจากไฟล์MessageQueue. โดยLooperปกติคลาสนี้จะใช้ร่วมกับHandlerThread(คลาสย่อยของThread)

Handlerเป็นชั้นสาธารณูปโภคที่อำนวยความสะดวกในการมีปฏิสัมพันธ์กับLooper-mainly โดยโพสต์ข้อความและวัตถุของด้ายRunnable MessageQueueเมื่อสร้าง a Handlerจะถูกผูกไว้กับเฉพาะLooper(และเธรดที่เกี่ยวข้องและคิวข้อความ)

ในการใช้งานทั่วไปคุณจะสร้างและเริ่มต้น a HandlerThreadจากนั้นสร้างHandlerวัตถุ (หรือวัตถุ) โดยที่เธรดอื่นสามารถโต้ตอบกับHandlerThreadอินสแตนซ์ได้ Handlerจะต้องสร้างขึ้นในขณะที่วิ่งอยู่บนHandlerThreadแม้ว่าครั้งหนึ่งที่สร้างขึ้นมีข้อ จำกัด ในสิ่งที่หัวข้อสามารถใช้Handler's วิธีการตั้งเวลา ( post(Runnable)อื่น ๆ )

เธรดหลัก (aka เธรด UI) ในแอปพลิเคชัน Android ถูกตั้งค่าเป็นเธรดตัวจัดการก่อนที่จะสร้างอินสแตนซ์แอปพลิเคชันของคุณ

นอกเหนือจากเอกสารชั้นเรียนมีการสนทนาที่ดีของทั้งหมดนี้ที่นี่

PS android.osทุกชั้นเรียนดังกล่าวข้างต้นอยู่ในแพคเกจ


@Ted Hopp - คิวข้อความของ Looper แตกต่างจากคิวข้อความของเธรดหรือไม่?
CopsOnRoad

2
@ แจ็ค - พวกเดียวกัน เอกสาร API ของMessageQueue Android ระบุว่า a MessageQueueเป็น " คลาสระดับต่ำที่เก็บรายชื่อข้อความที่จะส่งโดยกLooper. "
Ted Hopp

95

เป็นที่ทราบกันดีอยู่แล้วว่าการอัปเดตส่วนประกอบ UIโดยตรงจากเธรดอื่นที่ไม่ใช่เธรดหลักใน Android นั้นผิดกฎหมาย เอกสาร Android นี้ (การจัดการการดำเนินการราคาแพงในเธรด UI ) แนะนำขั้นตอนในการปฏิบัติตามหากเราจำเป็นต้องเริ่มเธรดแยกต่างหากเพื่อทำงานที่มีราคาแพงและอัปเดต UI หลังจากเสร็จสิ้น แนวคิดคือการสร้างอ็อบเจ็กต์Handler ที่เชื่อมโยงกับเธรดหลักและโพสต์Runnableลงในเวลาที่เหมาะสม นี้Runnableจะถูกเรียกในหัวข้อหลัก กลไกนี้ใช้กับคลาสLooperและHandler

LooperระดับรักษาMessageQueueซึ่งมีรายการข้อความ ตัวละครที่สำคัญของ Looper ก็คือว่ามันเกี่ยวข้องกับด้ายภายในที่Looperถูกสร้างขึ้น สมาคมนี้จะถูกเก็บไว้ตลอดไปและไม่สามารถแตกหักหรือเปลี่ยนแปลงได้ โปรดทราบว่าเธรดไม่สามารถเชื่อมโยงกับมากกว่าหนึ่งชุดLooperได้ เพื่อรับประกันการเชื่อมโยงนี้Looperจะถูกเก็บไว้ในที่จัดเก็บเธรดโลคัลและไม่สามารถสร้างผ่านตัวสร้างได้โดยตรง วิธีเดียวที่จะสร้างมันขึ้นมาคือการเรียกเตรียมLooperวิธีการคงที่ เตรียมวิธีการตรวจสอบThreadLocal ก่อนของเธรดปัจจุบันเพื่อให้แน่ใจว่าไม่มี Looper ที่เชื่อมโยงกับเธรด หลังจากการตรวจสอบระบบLooperจะสร้างและบันทึกThreadLocalรายการใหม่ เมื่อเตรียมการLooperแล้วเราสามารถเรียกใช้วิธีการวนซ้ำเพื่อตรวจสอบข้อความใหม่และต้องHandlerจัดการกับพวกเขา

เป็นชื่อที่แสดงในHandlerชั้นเรียนเป็นหลักรับผิดชอบในการจัดการ (เพิ่ม, ลบเยี่ยงอย่าง) MessageQueueข้อความของเธรดปัจจุบัน Handlerอินสแตนซ์ยังถูกผูกไว้กับด้าย ผูกพันระหว่าง Handler และด้ายจะประสบความสำเร็จผ่านทางและLooper MessageQueueA Handlerถูกผูกไว้กับ a เสมอLooperและต่อมาจะผูกไว้กับเธรดที่เกี่ยวข้องกับLooper. ต่างจากLooperอินสแตนซ์ Handler หลายตัวที่สามารถผูกไว้กับเธรดเดียวกัน เมื่อใดก็ตามที่เราเรียกว่าการโพสต์หรือวิธีการเหมือนกันในข้อความใหม่จะถูกเพิ่มที่เกี่ยวข้องHandler MessageQueueฟิลด์เป้าหมายของข้อความถูกตั้งค่าเป็นHandlerอินสแตนซ์ปัจจุบัน เมื่อLooperได้รับข้อความนี้มันจะเรียกใช้dispatchMessageบนฟิลด์เป้าหมายของข้อความเพื่อให้ข้อความส่งกลับไปยังอินสแตนซ์ตัวจัดการที่จะจัดการ แต่อยู่บนเธรดที่ถูกต้อง ความสัมพันธ์ระหว่างLooper, HandlerและMessageQueueแสดงอยู่ด้านล่าง:

ป้อนคำอธิบายภาพที่นี่


5
ขอบคุณ! แต่จะเป็นอย่างไรหากตัวจัดการโพสต์ข้อความไปยังคิวข้อความก่อนแล้วจัดการข้อความจากคิวเดียวกัน ทำไมไม่จัดการกับข้อความโดยตรง
Blake

4
@Blake b / c คุณกำลังโพสต์จากเธรดหนึ่ง (ไม่ใช่เธรดแบบวนซ้ำ) แต่จัดการข้อความในเธรดอื่น (เธรด looper)
numan salati

ดีกว่าสิ่งที่บันทึกไว้ในdeveloper.android.com - แต่จะเป็นการดีที่จะเห็นโค้ดสำหรับไดอะแกรมที่คุณให้มา
tfmontague

@numansalati - ไม่สามารถจัดการโพสต์ข้อความจากเธรด looper ได้หรือไม่?
CopsOnRoad

79

เริ่มต้นด้วย Looper คุณสามารถเข้าใจความสัมพันธ์ระหว่าง Looper, Handler และ MessageQueue ได้ง่ายขึ้นเมื่อคุณเข้าใจว่า Looper คืออะไร นอกจากนี้คุณยังสามารถเข้าใจได้ดีขึ้นว่า Looper คืออะไรในบริบทของกรอบงาน GUI Looper มีไว้ทำ 2 อย่าง

1) Looper เปลี่ยนเธรดปกติซึ่งจะสิ้นสุดเมื่อrun()เมธอดส่งคืนเป็นสิ่งที่ทำงานอย่างต่อเนื่องจนกว่าแอพ Android จะทำงานซึ่งจำเป็นในเฟรมเวิร์ก GUI (ในทางเทคนิคมันยังคงสิ้นสุดเมื่อrun()วิธีการส่งคืน แต่ขอฉันอธิบายสิ่งที่ฉันหมายถึง ด้านล่าง)

2) Looper จัดเตรียมคิวสำหรับงานที่ต้องทำซึ่งจำเป็นในกรอบงาน GUI

ดังที่คุณทราบเมื่อเปิดแอปพลิเคชันระบบจะสร้างเธรดการดำเนินการสำหรับแอปพลิเคชันที่เรียกว่า“ main” และโดยปกติแอปพลิเคชัน Android จะทำงานบนเธรดเดียวโดยค่าเริ่มต้นคือ“ เธรดหลัก” แต่หัวข้อหลักไม่ได้เป็นความลับบางอย่างด้ายพิเศษ มันเป็นเพียงเธรดปกติที่คุณสามารถสร้างด้วยnew Thread()โค้ดซึ่งหมายความว่าจะสิ้นสุดเมื่อrun()วิธีการส่งคืน! ลองนึกถึงตัวอย่างด้านล่าง

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

ตอนนี้เรามาใช้หลักการง่ายๆนี้กับแอพ Android จะเกิดอะไรขึ้นหากแอป Android ทำงานบนเธรดปกติ เธรดที่เรียกว่า "main" หรือ "UI" หรืออะไรก็ตามที่เริ่มต้นแอปพลิเคชันและดึง UI ทั้งหมด ดังนั้นหน้าจอแรกจะแสดงให้ผู้ใช้เห็น แล้วตอนนี้ล่ะ? กระทู้หลักยุติ? ไม่มันไม่ควร ควรรอจนกว่าผู้ใช้จะทำอะไรบางอย่างใช่ไหม แต่เราจะบรรลุพฤติกรรมนี้ได้อย่างไร? เราจะลองใช้Object.wait()หรือThread.sleep(). ตัวอย่างเช่นเธรดหลักเสร็จสิ้นงานเริ่มต้นเพื่อแสดงหน้าจอแรกและเข้าสู่โหมดสลีป มันจะตื่นขึ้นซึ่งหมายถึงการหยุดชะงักเมื่อมีการดึงงานใหม่ที่ต้องทำ จนถึงตอนนี้ดีมาก แต่ในขณะนี้เราต้องการโครงสร้างข้อมูลแบบคิวเพื่อรองรับงานหลาย ๆ งาน ลองนึกถึงกรณีที่ผู้ใช้แตะหน้าจอแบบอนุกรมและงานต้องใช้เวลานานกว่าจะเสร็จ ดังนั้นเราจำเป็นต้องมีโครงสร้างข้อมูลเพื่อรองรับงานที่ต้องทำในลักษณะก่อนเข้าก่อนออก นอกจากนี้คุณอาจจินตนาการได้ว่าการใช้เธรดที่เคยรันและกระบวนการงานเมื่อมาถึงโดยใช้การขัดจังหวะนั้นไม่ใช่เรื่องง่ายและนำไปสู่โค้ดที่ซับซ้อนและมักจะไม่สามารถเข้าถึงได้ เราอยากจะสร้างกลไกใหม่เพื่อจุดประสงค์ดังกล่าวและนั่นคือสิ่งที่ Looper เป็นข้อมูลเกี่ยวกับ เอกสารอย่างเป็นทางการของการเรียน Looperกล่าวว่า "โดยค่าเริ่มต้นเธรดจะไม่มีลูปข้อความที่เกี่ยวข้อง" และ Looper เป็นคลาส "ที่ใช้ในการรันข้อความวนซ้ำสำหรับเธรด" ตอนนี้คุณสามารถเข้าใจความหมายได้แล้ว

ไปที่ Handler และ MessageQueue อย่างแรก MessageQueue คือคิวที่ผมกล่าวไว้ข้างต้น มันอาศัยอยู่ใน Looper และนั่นแหล่ะ คุณสามารถตรวจสอบกับรหัสที่มาระดับของ Looper คลาส Looper มีตัวแปรสมาชิกของ MessageQueue

แล้ว Handler คืออะไร? ถ้ามีคิวก็ควรมีวิธีที่จะทำให้เราสามารถจัดคิวงานใหม่ให้กับคิวได้ใช่ไหม? นั่นคือสิ่งที่ Handler ทำ เราสามารถจัดคิวงานใหม่เป็นคิว (MessageQueue) โดยใช้post(Runnable r)วิธีการต่างๆ แค่นั้นแหละ. ทั้งหมดนี้เกี่ยวกับ Looper, Handler และ MessageQueue

คำสุดท้ายของฉันคือโดยพื้นฐานแล้ว Looper เป็นคลาสที่สร้างขึ้นเพื่อแก้ไขปัญหาที่เกิดขึ้นในกรอบงาน GUI แต่ความต้องการแบบนี้ก็เกิดขึ้นได้ในสถานการณ์อื่นเช่นกัน จริงๆแล้วมันเป็นรูปแบบที่มีชื่อเสียงมากสำหรับแอปพลิเคชันหลายเธรดและคุณสามารถเรียนรู้เพิ่มเติมได้ใน "การเขียนโปรแกรมพร้อมกันใน Java" โดย Doug Lea (โดยเฉพาะตอนที่ 4.1.4 "Worker Threads" จะเป็นประโยชน์) นอกจากนี้คุณสามารถจินตนาการได้ว่ากลไกแบบนี้ไม่ได้มีลักษณะเฉพาะในเฟรมเวิร์กของ Android แต่เฟรมเวิร์ก GUI ทั้งหมดอาจต้องการสิ่งนี้บ้าง คุณสามารถค้นหากลไกเกือบเหมือนกันใน Java Swing framework


4
คำตอบที่ดีที่สุด เรียนรู้เพิ่มเติมจากคำอธิบายโดยละเอียดนี้ ฉันสงสัยว่ามีบล็อกโพสต์ที่ให้รายละเอียดเพิ่มเติมหรือไม่
capt.swag

สามารถเพิ่มข้อความลงใน MessageQueue โดยไม่ใช้ Handler ได้หรือไม่?
CopsOnRoad

@CopsOnRoad ไม่สามารถเพิ่มได้โดยตรง
Faisal Naseer

ทำให้วันของฉัน ... รักคุณมากมาย :)
Rahul Matte

26

MessageQueue: เป็นคลาสระดับต่ำที่เก็บรายชื่อข้อความที่จะส่งโดยไฟล์Looper. ข้อความจะไม่ถูกเพิ่มลงใน a โดยตรงMessageQueueแต่จะผ่านHandlerวัตถุที่เกี่ยวข้องกับLooper. [ 3 ]

Looper: มันวนซ้ำMessageQueueซึ่งมีข้อความที่จะส่ง งานที่แท้จริงในการจัดการคิวจะทำโดยผู้Handlerรับผิดชอบในการจัดการ (การเพิ่มการลบการจัดส่ง) ข้อความในคิวข้อความ [ 2 ]

Handler: จะช่วยให้คุณสามารถส่งและกระบวนการMessageและวัตถุที่เกี่ยวข้องกับของด้ายRunnable MessageQueueแต่ละอินสแตนซ์ของ Handler เชื่อมโยงกับเธรดเดียวและคิวข้อความของเธรดนั้น [ 4 ]

เมื่อคุณสร้างใหม่Handlerมันจะถูกผูกไว้กับเธรด / คิวข้อความของเธรดที่กำลังสร้างเธรด - จากจุดนั้นเป็นต้นไปมันจะส่งข้อความและ runnables ไปยังคิวข้อความนั้นและดำเนินการตามที่ออกมาจากคิวข้อความ .

กรุณาอ่านภาพด้านล่าง [ 2 ] เพื่อความเข้าใจที่ดีขึ้น

ป้อนคำอธิบายภาพที่นี่


0

ขยายคำตอบโดย @K_Anas พร้อมตัวอย่างตามที่ระบุไว้

เป็นที่ทราบกันดีอยู่แล้วว่าการอัปเดตส่วนประกอบ UI โดยตรงจากเธรดอื่นที่ไม่ใช่เธรดหลักใน Android นั้นผิดกฎหมาย

ตัวอย่างเช่นหากคุณพยายามอัปเดต UI โดยใช้ 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();

แอปของคุณจะขัดข้องโดยมีข้อยกเว้น

android.view.ViewRoot $ calledFromWrongThreadException: เฉพาะเธรดเดิมที่สร้างลำดับชั้นการดูเท่านั้นที่สามารถสัมผัสมุมมองได้

ในคำอื่น ๆ ที่คุณจำเป็นต้องใช้Handlerที่ช่วยให้การอ้างอิงถึงMainLooper คือMain Threadหรือและผ่านงานเป็นUI 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() ;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.