Android進階之Handle和Looper消息機制原理和源碼分析(不走彎路)
本文轉載自微信公眾號「Android開發編程」,作者Android開發編程 。轉載本文請聯系Android開發編程公眾號。
前言
App中一般多會有多個線程,多線程之間難免需要進行通信。在我們平時開發中線程通信用的最多的就是Handler,例如子線程進行數據處理,在主線程中進行UI更新。
當然了除了Handler這種通信方式外,線程間的通信還有其他幾種方式:管道Pip、共享內存、通過文件及數據庫等。
我們主要來看下Handler以及其實現原理
一、Looper死循環詳解
1、死循環為什么不會導致應用卡死ANR
線程默認沒有Looper的,如果需要使用Handler就必須為線程創建Looper。
我們經常提到的主線程,也叫UI線程,它就是ActivityThread,ActivityThread被創建時就會初始化Looper,這也是在主線程中默認可以使用Handler的原因。
- public static void main(String[] args) {
- Looper.prepareMainLooper();//創建Looper和MessageQueue對象,用于處理主線程的消息
- ActivityThread thread = new ActivityThread();
- thread.attach(false);//建立Binder通道 (創建新線程)
- if (sMainThreadHandler == null) {
- sMainThreadHandler = thread.getHandler();
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- Looper.loop();
- //如果能執行下面方法,說明應用崩潰或者是退出了...
- throw new RuntimeException("Main thread loop unexpectedly exited");
- }
這個死循環會不會導致應用卡死,即使不會的話,它會慢慢的消耗越來越多的資源嗎?
①對于線程即是一段可執行的代碼,當可執行代碼執行完成后,線程生命周期便該終止了,線程退出。而對于主線程,我們是絕不希望會被運行一段時間,自己就退出,那么如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder線程也是采用死循環的方法,通過循環方式不同與Binder驅動進行讀寫操作,當然并非簡單地死循環,無消息時會休眠。但這里可能又引發了另一個問題,既然是死循環又如何去處理其他事務呢?通過創建新線程的方式。真正會卡死主線程的操作是在回調方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,looper.loop本身不會導致應用卡死。
②主線程的死循環一直運行是不是特別消耗CPU資源呢?其實不然,這里就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據來喚醒主線程工作。這里采用的epoll機制,是一種IO多路復用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。所以說,主線程大多數時候都是處于休眠狀態,并不會消耗大量CPU資源
2、主線程的消息循環機制是什么
主線程進入死循環之前便創建了新binder線程,在代碼ActivityThread.main()中:
- public static void main(String[] args) {
- //創建Looper和MessageQueue對象,用于處理主線程的消息
- Looper.prepareMainLooper();
- //創建ActivityThread對象
- ActivityThread thread = new ActivityThread();
- //建立Binder通道 (創建新線程)
- thread.attach(false);
- Looper.loop(); //消息循環運行
- throw new RuntimeException("Main thread loop unexpectedly exited");
- }
- Activity的生命周期都是依靠主線程的Looper.loop,當收到不同Message時則采用相應措施:一旦退出消息循環,那么你的程序也就可以退出了。從消息隊列中取消息可能會阻塞,取到消息會做出相應的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現象。
- thread.attach(false)方法函數中便會創建一個Binder線程(具體是指ApplicationThread,Binder的服務端,用于接收系統服務AMS發送來的事件),該Binder線程通過Handler將Message發送給主線程。「Activity 啟動過程」比如收到msg=H.LAUNCH_ACTIVITY,則調用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,創建Activity實例,然后再執行Activity.onCreate()等方法;
- 再比如收到msg=H.PAUSE_ACTIVITY,則調用ActivityThread.handlePauseActivity()方法,最終會執行Activity.onPause()等方法。
- 主線程的消息又是哪來的呢?當然是App進程中的其他線程通過Handler發送給主線程
二、Handler機制原理詳解
Handler機制,主要牽涉到的類有如下四個,它們分工明確,但又相互作用
Message:消息
Hanlder:消息的發起者
Looper:消息的遍歷者
MessageQueue:消息隊列
1、 Looper.prepare()
- public static void prepare() {
- prepare(true);
- }
- private static void prepare(boolean quitAllowed) {
- // 規定了一個線程只有一個Looper,也就是一個線程只能調用一次Looper.prepare()
- if (sThreadLocal.get() != null) {
- throw new RuntimeException("Only one Looper may be created per thread");
- }
- // 如果當前線程沒有Looper,那么就創建一個,存到sThreadLocal中
- sThreadLocal.set(new Looper(quitAllowed));
- }
從上面的代碼可以看出,一個線程最多只有一個Looper對象。當沒有Looper對象時,去創建一個Looper,并存放到sThreadLocal中,sThreadLocal是一個static的ThreadLocal對象,關于它的詳細使用,以后有機會再介紹,這里只要知道,它存儲了Looper對象的副本,并且可以通過它取得當前線程在之前存儲的Looper的副本。如下圖:
接下來看Looper的構造方法:
- private Looper(boolean quitAllowed) {
- // 創建了MessageQueue,并供Looper持有
- mQueue = new MessageQueue(quitAllowed);
- // 讓Looper持有當前線程對象
- mThread = Thread.currentThread();
- }
這里主要就是創建了消息隊列MessageQueue,并讓它供Looper持有,因為一個線程最大只有一個Looper對象,所以一個線程最多也只有一個消息隊列。然后再把當前線程賦值給mThread。
MessageQueue的構造方法沒有什么可講的,它就是一個消息隊列,用于存放Message。
所以Looper.prepare()的作用主要有以下三點:
- 創建Looper對象
- 創建MessageQueue對象,并讓Looper對象持有
- 讓Looper對象持有當前線程
2、new Handler()
Handler有很多構造方法,主要是提供自定義Callback、Looper等,我們先從最簡單的無參構造方法看起:
- public Handler() {
- this(null, false);
- }
- public Handler(Callback callback, boolean async) {
- // 不相關代碼
- ......
- //得到當前線程的Looper,其實就是調用的sThreadLocal.get
- mLooper = Looper.myLooper();
- // 如果當前線程沒有Looper就報運行時異常
- if (mLooper == null) {
- throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
- }
- // 把得到的Looper的MessagQueue讓Handler持有
- mQueue = mLooper.mQueue;
- // 初始化Handler的Callback,其實就是最開始圖中的回調方法的2
- mCallback = callback;
- mAsynchronous = async;
- }
首先,調用了Looper.myLooper,其實就是調用sThreadLocal.get方法,會得到當前線程調用sThreadLocal.set保存的Looper對象,讓Handler持有它。接下來就會判斷得到的Looper對象是否為空,如果為空,就會報
"Can't create handler inside thread that has not called Looper.prepare(),這不就是我們之前在沒有調用Looper.prepare就在子線程中創建Handler時報的錯誤嘛。的確,當我們沒有調用Looper.prepare(),則當前線程中是沒有Looper對象的。
然后,讓Handler持有得到的Looper對象的MessageQueue和設置處理回調的Callback對象(最開始圖中的回調方法2)。
到這里,默認的Handler的創建過程就結束了,主要有以下幾點:
- 創建Handler對象
- 得到當前線程的Looper對象,并判斷是否為空
- 讓創建的Handler對象持有Looper、MessageQueu、Callback的引用
3、Looper.loop()
- public static void loop() {
- // 得到當前線程的Looper對象
- final Looper me = myLooper();
- if (me == null) {
- throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
- }
- // 得到當前線程的MessageQueue對象
- final MessageQueue queue = me.mQueue;
- // 無關代碼
- ......
- // 死循環
- for (;;) {
- // 不斷從當前線程的MessageQueue中取出Message,當MessageQueue沒有元素時,方法阻塞
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- // Message.target是Handler,其實就是發送消息的Handler,這里就是調用它的dispatchMessage方法
- msg.target.dispatchMessage(msg);
- // 回收Message
- msg.recycleUnchecked();
- }
- }
首先還是判斷了當前線程是否有Looper,然后得到當前線程的MessageQueue。接下來,就是最關鍵的代碼了,寫了一個死循環,不斷調用MessageQueue的next方法取出MessageQueue中的Message,注意,當MessageQueue中沒有消息時,next方法會阻塞,導致當前線程掛起,后面會講到。
拿到Message以后,會調用它的target的dispatchMessage方法,這個target其實就是發送消息時用到的Handler。所以就是調用Handler的dispatchMessage方法,代碼如下:
- public void dispatchMessage(Message msg) {
- // 如果msg.callback不是null,則調用handleCallback
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- // 如果 mCallback不為空,則調用mCallback.handleMessage方法
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- // 調用Handler自身的handleMessage,這就是我們常常重寫的那個方法
- handleMessage(msg);
- }
- }
可以看出,這個方法就是從MessageQueue中取出Message以后,進行分發處理。
首先,判斷msg.callback是不是空,其實msg.callback是一個Runnable對象,是Handler.post方式傳遞進來的參數,后面會講到。而hanldeCallback就是調用的Runnable的run方法。
然后,判斷mCallback是否為空,這是一個Handler.Callback的接口類型,之前說了Handler有多個構造方法,可以提供設置Callback,如果這里不為空,則調用它的hanldeMessage方法,注意,這個方法有返回值,如果返回了true,表示已經處理 ,不再調用Handler的handleMessage方法;如果mCallback為空,或者不為空但是它的handleMessage返回了false,則會繼續調用Handler的handleMessage方法,該方法就是我們經常重寫的那個方法。
關于從MessageQueue中取出消息以后的分發,如下面的流程圖所示:
所以Looper.loop的作用就是:
從當前線程的MessageQueue從不斷取出Message,并調用其相關的回調方法。
4、發送消息
使用Handler發送消息主要有兩種,一種是sendXXXMessage方式,還有一個postXXX方式,不過兩種方式最后都會調用到sendMessageDelayed方法,所以我們就以最簡單的sendMessage方法來分析。
我們先來看Handler的sendMessage方法:
- public final boolean sendMessage(Message msg)
- {
- return sendMessageDelayed(msg, 0);
- }
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- {
- if (delayMillis < 0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- // 這里拿到的MessageQueue其實就是創建時的MessageQueue,默認情況是當前線程的Looper對象的MessageQueue
- // 也可以指定
- MessageQueue queue = mQueue;
- if (queue == null) {
- RuntimeException e = new RuntimeException(
- this + " sendMessageAtTime() called with no mQueue");
- Log.w("Looper", e.getMessage(), e);
- return false;
- }
- // 調用enqueueMessage,把消息加入到MessageQueue中
- return enqueueMessage(queue, msg, uptimeMillis);
- }
- 主要實現是調用enqueueMessage來實現的,看看該方法:
- private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
- // 把當前Handler對象,也就是發起消息的handler作為Message的target屬性
- msg.target = this;
- if (mAsynchronous) {
- msg.setAsynchronous(true);
- }
- // 調用MessageQueue中的enqueueMessage方法
- return queue.enqueueMessage(msg, uptimeMillis);
- }
首先,把當前Handler作為Message的target屬性,方便Looper從MessageQueue中取出Message時進行消息處理。然后調用了MessageQueue的enqueueMessage方法,把handler發送的消息加入到MessageQueue,供Looper去取出來處理。我們記下來看看。
MessageQueue的enqueueMessage方法:
- MessageQueue的enqueueMessage方法:
- boolean enqueueMessage(Message msg, long when) {
- if (msg.target == null) {
- throw new IllegalArgumentException("Message must have a target.");
- }
- // 一個Message,只能發送一次
- if (msg.isInUse()) {
- throw new IllegalStateException(msg + " This message is already in use.");
- }
- synchronized (this) {
- if (mQuitting) {
- IllegalStateException e = new IllegalStateException(
- msg.target + " sending message to a Handler on a dead thread");
- Log.w("MessageQueue", e.getMessage(), e);
- msg.recycle();
- return false;
- }
- // 標記Message已經使用了
- msg.markInUse();
- msg.when = when;
- // 得到當前消息隊列的頭部
- Message p = mMessages;
- boolean needWake;
- // 我們這里when為0,表示立即處理的消息
- if (p == null || when == 0 || when < p.when) {
- // 把消息插入到消息隊列的頭部
- msg.next = p;
- mMessages = msg;
- needWake = mBlocked;
- } else {
- // 根據需要把消息插入到消息隊列的合適位置,通常是調用xxxDelay方法,延時發送消息
- needWake = mBlocked && p.target == null && msg.isAsynchronous();
- Message prev;
- for (;;) {
- prev = p;
- p = p.next;
- if (p == null || when < p.when) {
- break;
- }
- if (needWake && p.isAsynchronous()) {
- needWake = false;
- }
- }
- // 把消息插入到合適位置
- msg.next = p; // invariant: p == prev.next
- prev.next = msg;
- }
- // 如果隊列阻塞了,則喚醒
- if (needWake) {
- nativeWake(mPtr);
- }
- }
- return true;
- }
首先,判斷了Message是否已經使用過了,如果使用過,則直接拋出異常,這是可以理解的,如果MessageQueue中已經存在一個Message,但是還沒有得到處理,這時候如果再發送一次該Message,可能會導致處理前一個Message時,出現問題。
然后,會判斷when,它是表示延遲的時間,我們這里沒有延時,所以為0,滿足if條件。把消息插入到消息隊列的頭部。如果when不為0,則需要把消息加入到消息隊列的合適位置。
最后會去判斷當前線程是否已經阻塞了,如果阻塞了,則需要調用本地方法去喚醒它。
以上是sendMessage的全部過程,其實就是把Message加入到MessageQueue的合適位置。那我們來簡單看看post系列方法:
- public final boolean post(Runnable r)
- {
- return sendMessageDelayed(getPostMessage(r), 0);
- }
- private static Message getPostMessage(Runnable r) {
- // 構造一個Message,并讓其callback執行傳來的Runnable
- Message m = Message.obtain();
- m.callback = r;
- return m;
- }
可以看到,post方法只是先調用了getPostMessage方法,用Runnable去封裝一個Message,然后就調用了sendMessageDelayed,把封裝的Message加入到MessageQueue中。
所以使用handler發送消息的本質都是:把Message加入到Handler中的MessageQueue中去。
三、Handler 是如何能夠線程切換
Handler創建的時候會采用當前線程的Looper來構造消息循環系統,Looper在哪個線程創建,就跟哪個線程綁定,并且Handler是在他關聯的Looper對應的線程中處理消息的。
那么Handler內部如何獲取到當前線程的Looper呢—–ThreadLocal。
ThreadLocal可以在不同的線程中互不干擾的存儲并提供數據,通過ThreadLocal可以輕松獲取每個線程的Looper。當然需要注意的是:
①線程是默認沒有Looper的,如果需要使用Handler,就必須為線程創建Looper。我們經常提到的主線程,也叫UI線程,它就是ActivityThread;
②ActivityThread被創建時就會初始化Looper,這也是在主線程中默認可以使用Handler的原因。
系統為什么不允許在子線程中訪問UI?這是因為Android的UI控件不是線程安全的,如果在多線程中并發訪問可能會導致UI控件處于不可預期的狀態,那么為什么系統不對UI控件的訪問加上鎖機制呢?缺點有兩個:①首先加上鎖機制會讓UI訪問的邏輯變得復雜 ②鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些線程的執行。所以最簡單且高效的方法就是采用單線程模型來處理UI操作。
四、Handler造成內存泄露
1、引起內存泄露原因
Java使用有向圖機制,通過GC自動檢查內存中的對象(什么時候檢查由虛擬機決定),如果GC發現一個或一組對象為不可到達狀態,則將該對象從內存中回收。也就是說,一個對象不被任何引用所指向,則該對象會在被GC發現的時候被回收;另外,如果一組對象中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個對象A和B互相持有引用,但沒有任何外部對象持有指向A或B的引用),這仍然屬于不可到達,同樣會被GC回收。
Android中使用Handler造成內存泄露的原因
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- mImageView.setImageBitmap(mBitmap);
- }
- }
上面是一段簡單的Handler的使用。當使用內部類(包括匿名類)來創建Handler的時候,Handler對象會隱式地持有一個外部類對象(通常是一個Activity)的引用(不然你怎么可能通過Handler來操作Activity中的View?)。而Handler通常會伴隨著一個耗時的后臺線程(例如從網絡拉取圖片)一起出現,這個后臺線程在任務執行完畢(例如圖片下載完畢)之后,通過消息機制通知Handler,然后Handler把圖片更新到界面。然而,如果用戶在網絡請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由于這時線程尚未執行完,而該線程持有Handler的引用(不然它怎么發消息給Handler?),這個Handler又持有Activity的引用,就導致該Activity無法被回收(即內存泄露),直到網絡請求結束(例如圖片下載完畢)。另外,如果你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,那么在你設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收。
2、解決方法
①在關閉Activity的時候停掉你的后臺線程。線程停掉了,就相當于切斷了Handler和外部連接的線,Activity自然會在合適的時候被回收;
②如果你的Handler是被delay的Message持有了引用,那么使用相應的Handler的removeCallbacks()方法,把消息對象從消息隊列移除就行了;
③將Handler聲明為靜態類,由于Handler不再持有外部類對象的引用,導致程序不允許你在Handler中操作Activity中的對象了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference)
- private static class MyHandler extends Handler {
- WeakReference<MainActivity> mActivity;
- MyHandler(MainActivity mActivity){
- this.mActivity = new WeakReference<MainActivity>(mActivity);
- }
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what){
- case IMAGE_FAILURE:
- Toast.makeText(mActivity.get()
- , "Image Failure", Toast.LENGTH_LONG).show();
- break;
- }
- }
- }
總結
Handler消息機制主要的四個類的功能:
- Message:信息的攜帶者,持有了Handler,存在MessageQueue中,一個線程可以有多個。
- Hanlder:消息的發起者,發送Message以及消息處理的回調實現,一個線程可以有多個Handler對象。
- Looper:消息的遍歷者,從MessageQueue中循環取出Message進行處理,一個線程最多只有一個。
- MessageQueue:消息隊列,存放了Handler發送的消息,供Looper循環取消息,一個線程最多只有一個。

























