Mối quan hệ giữa Looper, Handler và MessageQueue trong Android là gì?


95

Tôi đã kiểm tra các tài liệu hướng dẫn Android / hướng dẫn chính thức cho Looper, HandlerMessageQueue. Nhưng tôi không thể hiểu được. Tôi mới làm quen với android và rất bối rối với những khái niệm này.

Câu trả lời:


103

A Looperlà một vòng lặp xử lý thông báo: nó đọc và xử lý các mục từ a MessageQueue. Các Looperlớp học thường được sử dụng kết hợp với một HandlerThread(một lớp con của Thread).

A Handlerlà một lớp tiện ích hỗ trợ việc tương tác với một — chủ Looperyếu bằng cách đăng các thông báo và Runnableđối tượng lên chuỗi MessageQueue. Khi một Handlerđược tạo, nó được liên kết với một chuỗi cụ thể Looper(và chuỗi liên kết và hàng đợi tin nhắn).

Trong cách sử dụng thông thường, bạn tạo và bắt đầu một HandlerThread, sau đó tạo một Handlerđối tượng (hoặc các đối tượng) mà các luồng khác có thể tương tác với HandlerThreadcá thể. Các Handlerphải được tạo ra khi chạy trên HandlerThread, mặc dù một lần tạo ra không có hạn chế về những gì đề có thể sử dụng Handler's phương pháp lập kế hoạch ( post(Runnable), vv)

Chuỗi chính (hay còn gọi là chuỗi giao diện người dùng) trong ứng dụng Android được thiết lập làm chuỗi xử lý trước khi phiên bản ứng dụng của bạn được tạo.

Ngoài các tài liệu của lớp, có một cuộc thảo luận thú vị về tất cả những điều này ở đây .

PS Tất cả các lớp được đề cập ở trên đều nằm trong gói android.os.


@Ted Hopp - Hàng đợi tin nhắn của Looper có khác với hàng đợi tin nhắn của Thread không?
CopsOnRoad,

2
@Jack - Chúng giống nhau. Tài liệu APIMessageQueue Android cho trạng thái rằng a MessageQueuelà " lớp cấp thấp nắm giữ danh sách các tin nhắn được gửi bởi a Looper. "
Ted Hopp

95

Mọi người đều biết rằng việc cập nhật các thành phần UI trực tiếp từ các luồng không phải luồng chính trong android là bất hợp pháp . Tài liệu android này ( Xử lý các hoạt động tốn kém trong chuỗi giao diện người dùng ) đề xuất các bước cần làm theo nếu chúng ta cần bắt đầu một chuỗi riêng để thực hiện một số công việc tốn kém và cập nhật giao diện người dùng sau khi hoàn thành. Ý tưởng là tạo một đối tượng Handler được liên kết với main thread và đăng một Runnable lên nó vào thời điểm thích hợp. Điều này Runnablesẽ được gọi trên chuỗi chính . Cơ chế này được thực hiện với các lớp LooperHandler .

Các Looperlớp duy trì một MessageQueue , trong đó có chứa một danh sách các thông điệp . Một đặc điểm quan trọng của Looper là nó được liên kết với chuỗi mà trong đó chuỗi Looperđược tạo . Sự liên kết này được lưu giữ mãi mãi và không thể bị phá vỡ cũng như không thể thay đổi. Cũng lưu ý rằng một chủ đề không được liên kết với nhiều hơn một chủ đề Looper. Để đảm bảo liên kết này, Looperđược lưu trữ trong bộ lưu trữ cục bộ luồng và nó không thể được tạo trực tiếp thông qua phương thức khởi tạo của nó. Cách duy nhất để tạo nó là gọi phương thức chuẩn bị tĩnh trên Looper. chuẩn bị phương pháp đầu tiên kiểm tra ThreadLocalcủa luồng hiện tại để đảm bảo rằng chưa có một Looper được liên kết với luồng. Sau khi kiểm tra, một mới Looperđược tạo và lưu vào ThreadLocal. Sau khi chuẩn bị Looper, chúng ta có thể gọi phương thức vòng lặp trên đó để kiểm tra các tin nhắn mới và phải Handlerxử lý chúng.

Như tên đã chỉ ra, Handlerlớp chịu trách nhiệm chính trong việc xử lý (thêm, bớt, gửi đi) các thông điệp của luồng hiện tại MessageQueue. Một Handlercá thể cũng được liên kết với một chuỗi. Sự ràng buộc giữa Handler và Thread được thực hiện thông qua LooperMessageQueue. Một Handlerluôn luôn bị ràng buộc vào một Looper, và sau đó liên kết với các chủ đề liên quan với Looper. Không giống như Looper, nhiều cá thể của Trình xử lý có thể được liên kết với cùng một luồng. Bất cứ khi nào chúng tôi gọi bài đăng hoặc bất kỳ phương pháp nào giống nhau trên Handler, một thông báo mới được thêm vào liên kết MessageQueue. Trường đích của thông báo được đặt thành Handlerphiên bản hiện tại . Khi màLooperđã nhận được thông báo này, nó sẽ gọi gửi thư từ (sendMessage) trên trường đích của thông báo, để thông báo chuyển hướng trở lại cá thể Trình xử lý sẽ được xử lý, nhưng trên đúng chuỗi. Mối quan hệ giữa Looper, HandlerMessageQueueđược hiển thị dưới đây:

nhập mô tả hình ảnh ở đây


5
Cảm ơn! nhưng có ích gì nếu trình xử lý đầu tiên đăng thông báo vào hàng đợi thông báo và sau đó xử lý thông báo từ cùng một hàng đợi? tại sao nó không xử lý tin nhắn trực tiếp?
Blake

4
@Blake b / c bạn gửi bài từ một thread (không đề looper) nhưng xử lý các thông điệp trong một thread (Chủ đề looper)
Numan Salati

Tốt hơn nhiều so với những gì đang được ghi lại trên developer.android.com - nhưng sẽ rất tuyệt nếu bạn thấy mã cho sơ đồ mà bạn đã cung cấp.
tfmontague

@numansalati - Trình xử lý không thể đăng thông báo từ chuỗi trình lặp?
CopsOnRoad,

78

Hãy bắt đầu với Looper. Bạn có thể hiểu mối quan hệ giữa Looper, Handler và MessageQueue dễ dàng hơn khi bạn hiểu Looper là gì. Ngoài ra, bạn có thể hiểu rõ hơn Looper là gì trong bối cảnh của khung GUI. Looper được tạo ra để làm 2 việc.

1) Looper biến một luồng bình thường , kết thúc khi run()phương thức của nó trả về, thành một thứ chạy liên tục cho đến khi ứng dụng Android đang chạy , điều này cần thiết trong khung GUI (Về mặt kỹ thuật, nó vẫn kết thúc khi run()phương thức trả về. Nhưng hãy để tôi làm rõ ý của tôi, phía dưới).

2) Looper cung cấp một hàng đợi nơi các công việc cần thực hiện được xếp vào hàng đợi , cũng cần thiết trong khung GUI.

Như bạn có thể biết, khi một ứng dụng được khởi chạy, hệ thống sẽ tạo một chuỗi thực thi cho ứng dụng, được gọi là “chính” và các ứng dụng Android thường chạy hoàn toàn trên một chuỗi theo mặc định là “chuỗi chính”. Nhưng chủ đề chính không phải là một số chủ đề bí mật, đặc biệt . Nó chỉ là một luồng bình thường mà bạn cũng có thể tạo bằng new Thread()mã, có nghĩa là nó kết thúc khi run()phương thức của nó trả về! Hãy nghĩ đến ví dụ dưới đây.

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

Bây giờ, hãy áp dụng nguyên tắc đơn giản này cho ứng dụng Android. Điều gì sẽ xảy ra nếu một ứng dụng Android được chạy trên một chuỗi bình thường? Một chuỗi được gọi là "chính" hoặc "Giao diện người dùng" hoặc bất cứ thứ gì khởi động ứng dụng và vẽ tất cả Giao diện người dùng. Vì vậy, màn hình đầu tiên được hiển thị cho người dùng. Giờ thì sao? Chủ đề chính kết thúc? Không, không nên. Nó sẽ đợi cho đến khi người dùng làm điều gì đó, phải không? Nhưng làm thế nào chúng ta có thể đạt được hành vi này? Chà, chúng ta có thể thử với Object.wait()hoặcThread.sleep(). Ví dụ: luồng chính hoàn thành công việc ban đầu của nó để hiển thị màn hình đầu tiên và ở chế độ ngủ. Nó thức dậy, có nghĩa là bị gián đoạn, khi một công việc mới cần làm được tìm nạp. Cho đến nay rất tốt, nhưng tại thời điểm này chúng ta cần một cấu trúc dữ liệu giống như hàng đợi để chứa nhiều công việc. Hãy nghĩ về trường hợp người dùng chạm vào màn hình nối tiếp nhau và một tác vụ mất nhiều thời gian hơn để hoàn thành. Vì vậy, chúng ta cần phải có một cấu trúc dữ liệu để lưu giữ các công việc được thực hiện theo cách nhập trước - xuất trước. Ngoài ra, bạn có thể tưởng tượng, việc triển khai luồng luôn chạy và quá trình-công việc khi đến bằng cách sử dụng ngắt là không dễ dàng và dẫn đến mã phức tạp và thường không thể hiểu được. Chúng tôi muốn tạo ra một cơ chế mới cho mục đích như vậy và đó là tất cả những gì Looper hướng đến . Các tài liệu chính thức của lớp Loopernói, "Các luồng theo mặc định không có một vòng lặp thông báo liên kết với chúng", và Looper là một lớp "được sử dụng để chạy một vòng lặp thông báo cho một luồng". Bây giờ bạn có thể hiểu ý nghĩa của nó.

Hãy chuyển sang Handler và MessageQueue. Đầu tiên, MessageQueue là hàng đợi mà tôi đã đề cập ở trên. Nó nằm bên trong một Looper, và đó là nó. Bạn có thể kiểm tra nó bằng mã nguồn của lớp Looper . Lớp Looper có một biến thành viên là MessageQueue.

Sau đó, Handler là gì? Nếu có một hàng đợi, thì phải có một phương thức cho phép chúng ta xếp một tác vụ mới vào hàng đợi, phải không? Đó là những gì Handler làm. Chúng ta có thể xếp một nhiệm vụ mới vào hàng đợi (MessageQueue) bằng nhiều post(Runnable r)phương pháp khác nhau . Đó là nó. Đây là tất cả về Looper, Handler và MessageQueue.

Lời cuối cùng của tôi là, về cơ bản Looper là một lớp được tạo ra để giải quyết một vấn đề xảy ra trong khung GUI. Nhưng loại nhu cầu này cũng có thể xảy ra trong các tình huống khác. Thực ra nó là một mẫu khá nổi tiếng cho ứng dụng đa luồng, và bạn có thể tìm hiểu thêm về nó trong "Lập trình đồng thời trong Java" của Doug Lea (Đặc biệt, chương 4.1.4 "Worker Threads" sẽ rất hữu ích). Ngoài ra, bạn có thể tưởng tượng loại cơ chế này không phải là duy nhất trong khung Android, nhưng tất cả các khung GUI có thể cần hơi giống với cơ chế này. Bạn có thể tìm thấy cơ chế gần như tương tự trong Java Swing framework.


4
Câu trả lời tốt nhất. Học được nhiều hơn từ giải thích chi tiết này. Tôi tự hỏi nếu có một số bài đăng trên blog chi tiết hơn.
capt.swag

Có thể thêm tin nhắn vào MessageQueue mà không cần sử dụng Trình xử lý không?
CopsOnRoad

@CopsOnRoad không, chúng không thể được thêm trực tiếp.
Faisal Naseer

Đã làm cho ngày của tôi ... rất nhiều tình yêu cho bạn :)
Rahul Matte

26

MessageQueue: Là lớp cấp thấp nắm giữ danh sách các tin nhắn được gửi đi bởi a Looper. Thông báo không được thêm trực tiếp vào a MessageQueue, mà là thông qua Handlercác đối tượng được liên kết với Looper. [ 3 ]

Looper: Nó lặp lại trên một MessageQueuechứa các tin nhắn sẽ được gửi đi. Nhiệm vụ thực tế của việc quản lý hàng đợi được thực hiện bởi bộ phận Handlerchịu trách nhiệm xử lý (thêm, bớt, gửi đi) các thông báo trong hàng đợi thông báo. [ 2 ]

Handler: Nó cho phép bạn gửi và xử lý MessageRunnablecác đối tượng được liên kết với một luồng MessageQueue. Mỗi cá thể của Trình xử lý được liên kết với một luồng duy nhất và hàng đợi tin nhắn của luồng đó. [ 4 ]

Khi bạn tạo một thư mới Handler, nó sẽ bị ràng buộc với hàng đợi chuỗi / tin nhắn của chuỗi đang tạo nó - từ thời điểm đó, nó sẽ gửi thông báo và khả năng chạy đến hàng đợi thông báo đóthực thi chúng khi chúng ra khỏi hàng đợi thông báo .

Vui lòng xem qua hình ảnh bên dưới [ 2 ] để hiểu rõ hơn.

nhập mô tả hình ảnh ở đây


0

Mở rộng câu trả lời, bởi @K_Anas, với một ví dụ, Như đã nêu

Mọi người đều biết rằng việc cập nhật các thành phần UI trực tiếp từ các luồng không phải luồng chính trong android là bất hợp pháp.

chẳng hạn nếu bạn cố gắng cập nhật giao diện người dùng bằng Chủ đề.

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

ứng dụng của bạn sẽ bị lỗi ngoại trừ.

android.view.ViewRoot $ CalledFromWrongThreadException: Chỉ chuỗi ban đầu đã tạo cấu trúc phân cấp chế độ xem mới có thể chạm vào các chế độ xem của nó.

nói cách khác, bạn cần sử dụng Handlermà giữ tham chiếu đến MainLooper nghĩa là Main Threadhoặc UI Threadvà vượt qua nhiệm vụ như 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() ;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.