Handler,Looper,Message是如何運作的

原文地址

Android Handler Internals

為了一個Android應用的響應能力,我們一般需要盡量避免操作阻塞UI線程,當把一些阻塞操作或大規模運算轉移到其他工作線程中時,整個應用的響應能力就自然的提升了,但是我們經常需要去更新UI組件,而這些操作必須在主線程才能進行,所以線程間的通信機制十分的重要,為了解決這個問題,Android提供了一個消息通知機制,這就是Handler。Handler提供了一種非阻塞的機制,可以保證生產者和消費者線程在消息通信時不至于阻塞。

Handler

Handler 是線程間進行消息傳遞的直接接口。生產者和消費者線程通過以下幾個操作來使用Handler進行交互。

  • 在消息隊列中創建,發送,移除消息
  • 在消費者線程中處理消息
The android.os.Handler component

每一個Handler都關聯一個Looper和一個Message Queue。有兩種方式創建一個Handler:

  • 通過默認的構造函數,關聯當前所在的線程的Looper
  • 明確指定使用的Looper

一個Handler如果沒有一個Looper那么就無法運行,因為不能put消息到消息隊列中,同樣也就不能去接受消息進行處理。

public Handler(Callback callback, boolean async) {
    // code removed for simplicity
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException( “Can’t create handler inside thread that has not called Looper.prepare()”);
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

上面的這個片段展示了new一個Handler時所做的邏輯,首先這個Handler會檢測當前線程是否有一個有效的Looper對象,如果沒有,那么就拋一個異常。然后這個Handler接收一個Looper中的消息隊列的引用。
**提示: ** 許多個Handlers關聯到同一個線程時共享同一個消息隊列,因為它們共享同一個Looper。
這個Callback是一個可選的參數,如果提供了,它會通過Looper產生一個消息的分發。

Message

這個 Message 表示一個任意數據的容器,這個生產者線程發送Messages到handler上,這樣會將數據插入到Message Queue中。這個Message提供了三種額外的信息,這些信息用來讓 Handler 和 Message Queue 處理這個消息。

  • what—這個參數是一個標識,用來讓Handler區分不同的消息,進而差異處理
  • time—通知消息隊列什么時候去處理這個消息
  • target—表明哪一個Handler應該處理這個消息
The android.os.Message component

消息的創建通常采取下面的其中一種方式:

public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)

這個消息是從一個消息池中取出來的,這些提供的參數設置了這個消息的屬性,同時Handler也可以直接設置給這個消息的target,可以允許我們鏈式的進行調用就像這樣

mHandler.obtainMessage(MSG_SHOW_IMAGE, mBitmap).sendToTarget();

這個消息池是一個消息對象組成的一個LinkedList,池子的對象最大數是50,當Handler處理了這個消息之后,這個 Message Queue 將這個對象重新放到池子中,并且reset所有的field,這樣能得到節約內存的效果。
當發送一個Runnable給Handler通過 post(Runnable r) ,這時這個Handler實際上隱式的構造了一個新的消息,通過callback 屬性來持有這個 Runnable 對象。

Message m = Message.obtain();
m.callback = r;
Interaction of Producer Thread sending message to a Handler

從這里我們能能看到一個生產者線程和一個Handler的交互,這個生產者創建了一個消息,并且將它發送給了一個Handler,然后這個Handler將這個消息入隊到一個消息隊列中,這樣在消費者線程中Handler從拿到這個消息并且處理,這樣就實現了通信。

Message Queue

這個 Message Queue 是一個不限制長度的消息對象的LinkedList 。它按照時間順序進行插入消息,時間最早的最先被分發。

The android.os.MessageQueue component

MessageQueue 會根據當前的SystemClock.uptimeMillis 來進行一個時間上的調度,消息的時間戳小魚這個值時,這個消息就會被分發,進而被Handler處理。

public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)

發送一個帶有延時的消息會將這個消息的 time 字段設置為 SystemClock.uptimeMillis() + delayMillis
Sending a message with a delay sets the Message’s time field as SystemClock.uptimeMillis() + delayMillis.如果將消息發送到隊列的前端,time屬性會被設置為0,在下一次loop時,消息就會被處理。使用這個方法需要注意可能造成一些排序的問題。
Handler一般適合UI組件綁定在一起,并且引用一個Activity對象,這個引用可能會潛在的導致內存泄露,比如以下這個場景:

public class MainActivity extends AppCompatActivity {
    private static final String IMAGE_URL = "https://www.android.com/static/img/android.png";

    private static final int MSG_SHOW_PROGRESS = 1;
    private static final int MSG_SHOW_IMAGE = 2;

    private ProgressBar progressIndicator;
    private ImageView imageView;
    private Handler handler;

    class ImageFetcher implements Runnable {
        final String imageUrl;

        ImageFetcher(String imageUrl) {
            this.imageUrl = imageUrl;
        }

        @Override
        public void run() {
            handler.obtainMessage(MSG_SHOW_PROGRESS).sendToTarget();
            InputStream is = null;
            try {
                // Download image over the network
                URL url = new URL(imageUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                conn.setRequestMethod("GET");
                conn.setDoInput(true);
                conn.connect();
                is = conn.getInputStream();

                // Decode the byte payload into a bitmap
                final Bitmap bitmap = BitmapFactory.decodeStream(is);
                handler.obtainMessage(MSG_SHOW_IMAGE, bitmap).sendToTarget();
            } catch (IOException ignore) {
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException ignore) {
                    }
                }
            }
        }
    }

    class UIHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SHOW_PROGRESS: {
                    imageView.setVisibility(View.GONE);
                    progressIndicator.setVisibility(View.VISIBLE);
                    break;
                }
                case MSG_SHOW_IMAGE: {
                    progressIndicator.setVisibility(View.GONE);
                    imageView.setVisibility(View.VISIBLE);
                    imageView.setImageBitmap((Bitmap) msg.obj);
                    break;
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressIndicator = (ProgressBar) findViewById(R.id.progress);
        imageView = (ImageView) findViewById(R.id.image);

        handler = new UIHandler();

        final Thread workerThread = new Thread(new ImageFetcher(IMAGE_URL));
        workerThread.start();
    }
}

這個例子中,這個Activity開啟了一個新的工作線程來下載圖片,在工作線程完成之前銷毀這個Activity就會導致內存泄漏。這個代碼里有兩個強引用,一個是工作線程和UIHandler,另一個是UIHandler和views。這個引用會導致在GC時不能正常的回收內存。
現在我們在看一下另一個例子:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Ping";

    private Handler handler;

    class PingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "Ping message received");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler = new PingHandler();
        
        final Message msg = handler.obtainMessage();
        handler.sendEmptyMessageDelayed(0, TimeUnit.MINUTES.toMillis(1));
    }
}

事件發生的順序是這樣的:

  • 一個PingHandler被創建
  • 這個Activity發送了一個延時消息給Handler,這個消息被插入到消息隊列中。
  • 這個Activity在消息分發之前被銷毀
  • 這個消息被分發并且被PingHandler處理,打印一個log

雖然這并不是瞬間顯示,但同樣的這個Activity發生了泄露,當Activity被銷毀后,這個Handler引用需要對GC可見,但是,這里依然保留了一個Handler的引用。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

這個片段表明所有的消息發送到Handler最后都會調用 enqueueMessage 方法,這個Handler的引用顯示地指定給了 msg.target ,這也告訴了Looper當從 MessageQueue 中拿到消息的時候,應該哪個Handler處理這個消息,這個消息被加入到了MessageQueue,這樣MessageQueue也會持有一個這個Message的引用,除此之外,MessageQueue 也和一個Looper關聯在一起,一個顯式的Looper一直存在直到被終止,而主的Looper的生命周期和整個應用一樣長,這個Handler的引用一直存在只要這個Message不被MessageQueue回收,一旦被回收,這個消息所有的屬性包括 target 引用都會被清除。
在這個例子中,存在一個非靜態成員類隱式的引用外面的類,準確的說,就是這個PingHandler 并不是被定義成一個靜態類,所以存在一個Activity的隱式引用
使用一個WeakReference 或者是靜態類可以避免這個Handler的泄露,當Activity被銷毀時,這個WeakReference 會使得GC回收這個對象,同樣的靜態的內部類也能防止一個隱式的引用到外面的類中。
所以這里我們修改一下例子中的UIHandler :

static class UIHandler extends Handler {
    private final WeakReference<ImageFetcherActivity> mActivityRef;
    
    UIHandler(ImageFetcherActivity activity) {
        mActivityRef = new WeakReference(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        final ImageFetcherActivity activity = mActivityRef.get();
        if (activity == null) {
            return
        }
        
        switch (msg.what) {
            case MSG_SHOW_LOADER: {
                activity.progressIndicator.setVisibility(View.VISIBLE);
                break;
            }
            case MSG_HIDE_LOADER: {
                activity.progressIndicator.setVisibility(View.GONE);
                break;
            }
            case MSG_SHOW_IMAGE: {
                activity.progressIndicator.setVisibility(View.GONE);
                activity.imageView.setImageBitmap((Bitmap) msg.obj);
                break;
            }
        }
    }
}

現在這個UIHandler 的構造函數中持有這個被包裝在WeakReference中的Activity,這樣在Activity銷毀的時候回收這個引用。當和Activity中的UI組件交互時,我們需要一個activity的強引用,既然這里使用了弱引用,那么就要注意訪問Activity,因為極有可能被GC回收掉,所以需要先檢查一下是否這個activity引用還存在,如果已經被回收,那么這個消息就需要被忽略。
除了引用被回收的情況,還有一種是activity已經被銷毀了,但是引用依然存在,這種情況也有可能導致應用crash,為了解決這個問題,我們還需要檢測這個activity當前的狀態,所以上面的邏輯還需要修改一下:

static class UIHandler extends Handler {
    private final WeakReference<ImageFetcherActivity> mActivityRef;
    
    UIHandler(ImageFetcherActivity activity) {
        mActivityRef = new WeakReference(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        final ImageFetcherActivity activity = mActivityRef.get();
        if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
            removeCallbacksAndMessages(null);
            return
        }
        
        switch (msg.what) {
            case MSG_SHOW_LOADER: {
                activity.progressIndicator.setVisibility(View.VISIBLE);
                break;
            }
            case MSG_HIDE_LOADER: {
                activity.progressIndicator.setVisibility(View.GONE);
                break;
            }
            case MSG_SHOW_IMAGE: {
                activity.progressIndicator.setVisibility(View.GONE);
                activity.imageView.setImageBitmap((Bitmap) msg.obj);
                break;
            }
        }
    }
}

現在我們可以概括一下MessageQueue,Handler,和生產者線程之間的關系

Interaction between MessageQueue, Handlers, and Producer Threads

上面這張圖中,多個線程發送消息給不同的Handlers,但是每個Handler都關聯著同一個Looper,所以所有的消息都被發送到同一個MessageQueue中,這十分重要,因為Android自身就是創建了許多的Handlers 綁定在主Looper上:

  • The Choreographer: 發送垂直同步和幀更新的消息
  • The ViewRoot:發送輸入和窗口事件,配置更改等
  • The InputMethodManager:發送鍵盤觸摸指令時間
  • 一些其他的消息

Debugging Tips:
你可以 debug/dump所有的消息分發通過給一個Looper上加一個LogPrinter

final Looper looper = getMainLooper();
looper.setMessageLogging(new LogPrinter(Log.DEBUG, "Looper"));

同樣地,你也可以 debug/dump 所有在MessageQueue 中的消息,通過在Handler上加一個LogPrinter

handler.dump(new LogPrinter(Log.*DEBUG*, "Handler"), "");

Looper

Looper 從Message Queue中拿消息,并且分發給對應target的Handler,一旦一個消息通過了分發的柵欄,那么它就能被Looper在下一次loop中拿到,當沒有符合條件的消息的時候,Looper會阻塞住,當存在消息有效時,Looper又被被喚醒。
一個Looper只能綁定一個線程,將別的Looper強行綁定在一起會造成異常,Looper中的靜態的ThreadLocal 的對象保證了一個Looper只能attach到一個線程上。
調用 Looper.quit 會立刻終止,并且會丟棄掉Message Queue中已經通過柵欄的所有消息。調用 Looper.quitSafely 會在丟棄延遲消息之前讓所有已經準備分發的消息被處理。

Overall flow of Handler interacting with MessageQueue and Looper

Looper會在線程的run方法中設置,其中有一個靜態方法 Looper.prepare() 會檢查是否已經存在一個Looper對象關聯這個線程,這里實際上是通過Looper內部的 ThreadLocal 對象來檢測是否已經存在,如果發現沒有,那么就自然的創建一個Looper對象,以及一個MessageQueue 。

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException(“Only one Looper may be created per thread”);
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

這里默認傳進來一個true,有了Looper之后,現在Handler就能接收消息,并且添加到消息隊列中,執行一個靜態方法 Looper.loop () 會開始從隊列中取消息。每一次 loop 循環會獲取下一個消息,然后分發給target的Handler,最后將消息回收到池子中。Looper.loop 會一直循環直到這個Looper被結束。

public static void loop() {
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg);
        msg.recycleUnchecked();
    }
}

在日常使用中,我們并不需要創建一個線程并綁定一個Looper在上面,Android提供了一個方便的類 --- HandlerThread,這個類繼承于Thread,并且內部有一個Loope。先看一個例子

private final Handler handler;
private final HandlerThread handlerThread;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate();
    handlerThread = new HandlerThread("HandlerDemo");
    handlerThread.start();
    handler = new CustomHandler(handlerThread.getLooper());
}
@Override
protected void onDestroy() {
    super.onDestroy();
    handlerThread.quit();
}

這個 onCreate() 方法構造了一個新的HandlerThread,當這個HandlerThread 開始的時候,它調用了 Looper的perpare方法,將一個Looper對象關聯起來,然后整套流程就開始正常的運作。
在activity 被銷毀的時候,我們需要去關掉這個HandlerThread,這也會讓內部的Looper結束。

后記

在Android的整個生命周期中,Handler無疑是一個重要角色。它創造了一種半同步半異步的一種模式,減少了線程間的通信消耗,保證了主線程的穩定性。深入的理解整個Handler及其組件是如何工作的可以極大的方便我們解決困難的問題。我們一般用Handler只是用來更新UI,但是實際上Handler在其他地方發揮著巨大的作用,比如 IntentServiceCamera2等API,在這些API中,更著眼于多線程的操作。

參考文獻

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容