Looper, Handler and HandlerThread in android

MultiThreading và Task running là những khái niệm quen thuộc trong lập trình. Trong Java java.util.concurrent là package chứa các utility class hữu ích trong việc lập trình song song (concurrent programming) và Fork/Join framework là một framework hiện thực của ExecutorService interface giúp tận dụng tối đa khả năng của bộ đa xử lý multiple processors. Trong Android, LooperHandler và HandlerThread là cách giải quyết các vấn đề liên quan tới lập trình bất đồng bộ.

Đặt vấn đề

Trong javaThread thông thường (Normal Thread) mang ý nghĩa chỉ sử dụng một lầnnghĩa là khởi tạo và chết đi sau khi thực thi hàm run().
Bản thân Thread là con dao 2 lưỡi, chúng ta có thể tăng tốc việc thực thi bằng cách phân phối nhiệm vụ (task) giữa các Thread, tuy nhiên có thể làm chậm việc thực thi các taskkhi mà số lượng Thread vượt quá số có thể. Vì vậy cách tốt nhất là phải có 1 số lượng tối ưu Thread và tái sử dụng chúng cho việc thực thi task. (Tham khảo thêm Sử dụng ThreadPoolExecutor trong android). Trong bài viết này, chúng ta sẽ cùng nhau tái sử dụng Thread với Looper và Handler.

Tái sử dụng Thread

  1. Giữ Thread hoạt động bởi 1 vòng lặp trong hàm run() của nó dùng Looper
  2. Các task được thực thi nối tiếp nhau bởi Thread và được duy trì trong một hàng đợi (MessageQueue)
  3. Hủy Thread sau khi hoàn thành.

Looper, Handler, HandlerThread

Hệ thống Android trãi qua các bước:

  1. MessageQueue là hàng đợi chứa nhiệm vụ (messages) cần được thực thi.
  2. Handler enqueues task từ MessageQueue dùng Looper và đồng thời thực thi chúng khi nhận từ MessageQueue.
  3. Looper là 1 worker giữ cho Thread tồn tại, nó sẽ vòng qua MessageQueue và gởi các message tới các Handler tương ứng để thực thi.
  4. Cuối cùng Thread bị hủy từ lời gọi hàm quit() của Looper.

Note: Mỗi Thread chỉ có thể có một Looper duy nhất và có thế có nhiều Handler liên kết với nó.

Tạo Looper và MessageQueue cho Thread

Cách tự tạo và tự quản lý:

Với cách này, bạn sẽ tự tạo 1 Thread với Looper và quản lý chúng. Mặc định, Handler sẽ liên kết (implicitly)với Thread mà nó đc khởi tạo qua Looper, tuy nhiên bạn có thể buộc nó vào 1 Thread khác bằng cách cung cấp rõ trong constructor lúc khởi tạo.

class LooperThread extends Thread {
      public Handler mHandler; 
 
      public void run() { 
         // Creating Looper and MessageQueue for this Thread
          Looper.prepare();
 
          mHandler = new Handler() { 
              public void handleMessage(Message msg) { 
                 // process incoming messages here
                 // this will run in non-ui/background thread
              } 
          }; 
         // Associate the Thread with its Looper and MessageQueue
          Looper.loop();
      } 
  }
  1. Thread nhận 1 Looper và MessageQueue bằng cách gọi Looper.prepare() ngay khi bắt đầu thực thi (run()).
  2. Looper.prepare() sẽ xác định Thread đang gọi, tạo 1 đối tượng Looper và MessageQueue và kết nối chúng với Thread bên trong ThreadLocal.
  3. Looper.loop() cần được gọi để bắt đầu kết nối.

Trong ví dụ trên, hãy cùng xem xét cách tạo Handler cho Thread và cách mà Handlernhận, gởi message:

handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // process incoming messages here
        // this will run in the thread, which instantiates it
    }
};

Handler gởi message tới MessageQueue bởi Handler có thể thông qua 2 cách:

  1. Message: là class chứa các phương thức hữu ích khác nhau để gởi dữ liệu.
Message msg = Message.obtain();
msg.obj = "Send message From Me";
handler.sendMessage(msg);
  1. Runnable:
new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        // this will run in the main thread
    }
});

Ở ví dụ này, chúng ta tạo một Handler và cung cấp Looper liên kết với Main Thread. Khi post Runnable, nó sẽ được thêm vào MessageQueue của Main Thread và sau đó được thực thi trong Main Thread.

Tự tạo một Thread và cung cấp LooperMessageQueue đôi khi gây khó khăn cho lập trình viên. Vì vậy, Android cung cấp cho chúng ta HandlerThread (kế thừa từ Thread) để đơn giản hóa việc thực thi task. Bên trong nó là những thứ tương tự mà chúng ta đã làm ở trên nhưng đã được tối ưu. Do vậy, hãy nên sử dụng HandlerThread.

Cách tạo dùng HandlerThread

private class MyHandlerThread extends HandlerThread {

    Handler handler;

    public MyHandlerThread(String name) {
        super(name);
    }

    @Override
    protected void onLooperPrepared() {
        // Only instantiates the Handler when looper is prepared
        // So, Handler can be associated with that Looper
        handler = new Handler(getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // process incoming messages here
                // this will run in non-ui/background thread
            }
        };
    }
}

Note:

  1. Looper chỉ prepared sau khi hàm start() của HandlerThread được thực thi.
  2. Một Handler có thể được liên kết với một HandlerThread chỉ khi Looper của nó được prepared.
  3. HandlerThread cần gọi hàm quit() để giải phóng resource và dừng quá trình thực thi.

Một cách khác để khởi tạo HandlerThread

HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());

https://viblo.asia/p/looper-handler-and-handlerthread-in-android-jvElaLDAZkw

Related posts