ผมได้ตรวจสอบอย่างเป็นทางการ Android เอกสาร / คู่มือสำหรับLooper
, และHandler
MessageQueue
แต่ฉันไม่สามารถรับมันได้ ฉันยังใหม่กับ Android และสับสนกับแนวคิดเหล่านี้มาก
ผมได้ตรวจสอบอย่างเป็นทางการ Android เอกสาร / คู่มือสำหรับLooper
, และHandler
MessageQueue
แต่ฉันไม่สามารถรับมันได้ ฉันยังใหม่กับ Android และสับสนกับแนวคิดเหล่านี้มาก
คำตอบ:
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
ทุกชั้นเรียนดังกล่าวข้างต้นอยู่ในแพคเกจ
MessageQueue
Android ระบุว่า a MessageQueue
เป็น " คลาสระดับต่ำที่เก็บรายชื่อข้อความที่จะส่งโดยกLooper
. "
เป็นที่ทราบกันดีอยู่แล้วว่าการอัปเดตส่วนประกอบ 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
MessageQueue
A Handler
ถูกผูกไว้กับ a เสมอLooper
และต่อมาจะผูกไว้กับเธรดที่เกี่ยวข้องกับLooper
. ต่างจากLooper
อินสแตนซ์ Handler หลายตัวที่สามารถผูกไว้กับเธรดเดียวกัน เมื่อใดก็ตามที่เราเรียกว่าการโพสต์หรือวิธีการเหมือนกันในข้อความใหม่จะถูกเพิ่มที่เกี่ยวข้องHandler
MessageQueue
ฟิลด์เป้าหมายของข้อความถูกตั้งค่าเป็นHandler
อินสแตนซ์ปัจจุบัน เมื่อLooper
ได้รับข้อความนี้มันจะเรียกใช้dispatchMessageบนฟิลด์เป้าหมายของข้อความเพื่อให้ข้อความส่งกลับไปยังอินสแตนซ์ตัวจัดการที่จะจัดการ แต่อยู่บนเธรดที่ถูกต้อง ความสัมพันธ์ระหว่างLooper
, Handler
และMessageQueue
แสดงอยู่ด้านล่าง:
เริ่มต้นด้วย 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
MessageQueue
: เป็นคลาสระดับต่ำที่เก็บรายชื่อข้อความที่จะส่งโดยไฟล์Looper
. ข้อความจะไม่ถูกเพิ่มลงใน a โดยตรงMessageQueue
แต่จะผ่านHandler
วัตถุที่เกี่ยวข้องกับLooper
. [ 3 ]
Looper
: มันวนซ้ำMessageQueue
ซึ่งมีข้อความที่จะส่ง งานที่แท้จริงในการจัดการคิวจะทำโดยผู้Handler
รับผิดชอบในการจัดการ (การเพิ่มการลบการจัดส่ง) ข้อความในคิวข้อความ [ 2 ]
Handler
: จะช่วยให้คุณสามารถส่งและกระบวนการMessage
และวัตถุที่เกี่ยวข้องกับของด้ายRunnable
MessageQueue
แต่ละอินสแตนซ์ของ Handler เชื่อมโยงกับเธรดเดียวและคิวข้อความของเธรดนั้น [ 4 ]
เมื่อคุณสร้างใหม่Handler
มันจะถูกผูกไว้กับเธรด / คิวข้อความของเธรดที่กำลังสร้างเธรด - จากจุดนั้นเป็นต้นไปมันจะส่งข้อความและ runnables ไปยังคิวข้อความนั้นและดำเนินการตามที่ออกมาจากคิวข้อความ .
กรุณาอ่านภาพด้านล่าง [ 2 ] เพื่อความเข้าใจที่ดีขึ้น
ขยายคำตอบโดย @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 Thread
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() ;