Một cuộc hành trình trên Main Thread của Android - PSVM
Từ PSVM đến looper và handler. Có một bài viết trên codinghorror nói về việc tại sao chúng ta lại cần phải đọc source code. Một trong những mặt tốt của Android là nó có tính chất mở. Khi đối diện với những lỗi liên quan đến cách mà chúng ta tương tác với main thread, tôi đã quyết định tìm hiểu ...
Từ PSVM đến looper và handler.
Có một bài viết trên codinghorror nói về việc tại sao chúng ta lại cần phải đọc source code. Một trong những mặt tốt của Android là nó có tính chất mở.
Khi đối diện với những lỗi liên quan đến cách mà chúng ta tương tác với main thread, tôi đã quyết định tìm hiểu xem main thread thật sự nó là cái gì. Bài viết này sẽ nói về phần đầu tiên trong chuyến hành trình của tôi.
public class BigBang { public static void main(String... args) { // The Java universe starts here. } }
Mọi ứng dụng Java đều bắt đầu bởi lời gọi đến hàm public static void main() (PSVM). Điều này được áp dụng cho chương trình Java Desktop, JEE servlet, và ứng dụng Android.
Khi hệ thống Android khởi động, nó bắt đầu một tiến trình gọi là ZygoteInit. Tiến trình này là 1 máy ảo Dalvik với nhiệm vụ tải những class phổ thông nhất của Android SDK lên một thread, và sau đó nó chờ.
Khi bắt đầu một ứng dụng Android, hệ thống Android sẽ mở ra tiến trình ZygoteInit. Thread trong nhánh con lúc này sẽ ngừng chờ và gọi đến ActivityThread.main().
Dựa theo Wikipedia thì zygote là 1 tế bào sinh học được thụ tinh.
Trước khi đi tiếp thì chúng ta cần xem qua class Looper.
Sử dụng looper là cách đúng đắn để dành một thread để xử lý các message theo thứ tự.
Mỗi looper có 1 hàng chờ gồm những Message object (một MessageQueue).
Looper có một hàm loop() có nhiệm vụ xử lý từng message trong hàng chờ và sẽ block nếu hàng chờ trống. Hàm Looper.loop() được implement tương tự như thế này:
void loop() { while(true) { Message message = queue.next(); // blocks if empty. dispatchMessage(message); message.recycle(); } }
Mỗi looper được liên kết với một thread. Để tạo một looper mới và liên kết nó với thread hiện tại, bạn cần phải gọi hàm Looper.prepare(). Các looper được chứa trong một static object ThreadLocal trong class Looper.
Class HandlerThread sẽ làm mọi việc thay cho bạn:
HandlerThread thread = new HandlerThread("SquareHandlerThread"); thread.start(); // bắt đầu thread. Looper looper = thread.getLooper();
Code của nó nhìn tương tự thế này:
class HandlerThread extends Thread { Looper looper; public void run() { Looper.prepare(); // Tạo 1 looper và giữ nó trong 1 ThreadLocal. looper = Looper.myLooper(); // Lấy lại instance của looper từ ThreadLocal để dùng sau. Looper.loop(); // Loop mãi mãi. } }
Handler là bạn đồng hành của một looper.
Một handler có 2 mục đích:
- Gửi message đến hàng chờ của looper từ bất kỳ thread nào.
- Xử lý message được phát bởi 1 looper trên thread liên kết với looper đó.
// . Mỗi handler được liên kết với 1 looper. Handler handler = new Handler(looper) { public void handleMessage(Message message) { // Xử lý message phát ra trên thread liên kết với looper đó. if (message.what == DO_SOMETHING) { // làm gì đó } } }; // Khởi tạo một message liên kết với handler trên Message message = handler.obtainMessage(DO_SOMETHING); // Thêm message vào hàng chờ của looper // Có thể được gọi từ mọi thread. handler.sendMessage(message);
Bạn có thể liên kết nhiều handler tới 1 looper. Looper sẽ gửi message đến cho message.target.
Một cách phổ biến và đơn giản hơn để dùng handler là post 1 Runnable:
// Tạo 1 message giữ reference đến runnable và thêm nó vào hàng chờ của looper handler.post(new Runnable() { public void run() { // Chạy trên thread liên kết với looper được liên kết với handler trên. } });
Một handler cũng có thể được khởi tạo mà không cung cấp bất cứ looper nào:
// ĐỪNG LÀM VẬY Handler handler = new Handler();
Constructor không có argument của handler sẽ gọi đến Looper.myLooper() và lấy ra looper liên kết với thread hiện tại. Thread đó có thể không phải là thread mà bạn muốn handler được liên kết cùng.
Trong phần lớn các trường hợp thì bạn chỉ muốn tạo một handler để post lên main thread:
Handler handler = new Handler(Looper.getMainLooper());
Hãy cùng xem lại ActivityThread.main(). Dưới đây là việc mà nó thực sự làm:
public class ActivityThread { public static void main(String... args) { Looper.prepare(); // Bạn có thể lấy main looper bất kỳ lúc nào bằng lời gọi đến Looper.getMainLooper() Looper.setMainLooper(Looper.myLooper()); // Gửi message đầu tiên đến cho looper // { ... } Looper.loop(); } }
Giờ thì bạn đã hiểu tại sao thread này được gọi là main thread rồi chứ