Handler機制原理

LiuJian-Android發表於2018-02-24

為了避免ANR,我們會通常把 耗時操作放在子執行緒裡面去執行,因為子執行緒不能更新UI,所以當子執行緒需要更新的UI的時候就需要藉助到Android的訊息機制,也就是Handler機制。

1.Android的訊息機制概述

1、Handler傳送訊息僅僅是呼叫MessageQueue的enqueueMessage向插入一條資訊到MessageQueue
2、Looper不斷輪詢呼叫MeaasgaQueue的next方法
3、如果發現message就呼叫handler的dispatchMessage,ldispatchMessage被成功呼叫,接著呼叫handlerMessage()

2.Android的訊息機制分析

2.1 MessageQueue的工作原理

訊息佇列,儲存一組訊息,以佇列的形式對外提供插入和刪除的工作,採用單列表的資料結構儲存訊息。
插入訊息:通過enqueueMessage(Message msg, long when)實現
讀取訊息:通過next()實現,通過for(;;)方法無限迴圈,如果佇列中沒有訊息,next()一直阻塞在這裡,當有新訊息到來時,next方法會返回這條訊息並將其從單連結串列中移除

2.2 Looper的工作原理

訊息輪詢,Looper以無限迴圈的形式去查詢是否有新訊息,如果有的話就處理,沒有就一直等待。執行緒預設沒有Looper,如果需要使用Handler就必須為執行緒建立Looper。每個執行緒只有一個Looper。
構造方法:建立一個MessageQueue即訊息佇列,然後將當前執行緒的物件儲存起來

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}
複製程式碼

注意: 建立出來一個Handler但是沒有建立Looper的話就會報錯。

"Can't create handler inside thread that has not called Looper.prepare()"); ,
複製程式碼

解決辦法就是new Handler的時候加上Looper.prepare();

主要方法

  • 建立
    Looper.prepare() : 為當前執行緒建立一個Looper
    prepareMainLooper() : 可以在任何地方獲取到主執行緒(ActivityThread)的Looper
  • 開啟: Looper.loop() : 開啟訊息輪詢
    loop方法是一個死迴圈,唯一跳出迴圈的方式是MessageQueue的next方法返回null。Looper必須退出,否則loop方法將會無限迴圈下去。loop方法會呼叫MessageQueue的next方法來獲取新訊息,而next方法是一個阻塞操作,當沒有訊息時,next方法會一直阻塞在那裡,這也導致了loop方法一直阻塞字啊那裡。如果MessageQueue的next方法返回了新訊息,Looper就會處理這條訊息:msg.garget.dispatchMessage(msg),這裡的msg。target是傳送這條訊息的Handler物件,這樣Handler傳送的訊息最終又交給了dispatchMessage方法處理。但是這裡不同的是,Handler的dispatchMessage方法是在建立Handler時所使用的Looper中執行的,遮掩給就成功的將程式碼邏輯切換到指定的執行緒中去執行了。
  • 退出
    quit() : 直接退出Looper
    quitSafely() : 設定一個標記,只有當目前已有訊息處理完畢之後才會執行退出操作。
    注意: Looper退出後,通過Handler傳送的訊息會失敗,這個時候Handler的send方法會返回false。在子執行緒中,如果手動建立了Looper,那麼在所有事情完成以後應該呼叫quit來終止訊息迴圈,否則這個子執行緒就會一直處於等待狀態。而如果推出Looper以後,這個額執行緒就會立刻終止,因此建議不需要的時候終止Looper

2.3Handler的工作原理

傳送和處理訊息,將一個任務切換到指定的執行緒去執行。Handler建立時會採用當前執行緒的Looper來構建內部的訊息迴圈系統。
傳送訊息:通過send()和post()方法傳送訊息,其實post方法最終還是呼叫send()。Handler傳送訊息的過程僅僅是向訊息佇列中插入了一條訊息,MessageQueue的next方法機會返回這條訊息給Looper,Looper收到訊息後開始處理了,最終訊息由Looper交由Handler處理,即Handler的dispatchMessage方法會被呼叫,這時Handler進入了處理訊息的階段。
處理訊息: 分三種情況

  • 1、如果是post傳送來的message,那麼就讓這個message所持有的Runnable執行run方法,非常簡單。 Message的Callback 是一個Runnable物件,Handler的post的過載的函式不管引數多少,肯定都是有Runnable的。
private static void handleCallback(Message message) {
    message.callback.run();
}
複製程式碼
  • 2、如果是利用Handler(Callback callback) 建構函式例項化的Handler,也就是建構函式裡面傳入了一個CallBack的物件,那麼就執行這個Callback的handlerMessage。 利用這個介面和Handler的一個建構函式,我們可以這麼建立Handler handler=new Handler(callback)來建立Handler;備註寫明瞭這個介面的作用:可以建立一個Handler的例項但是不需要派生Handler的子類。對比我們日常中最經常做的,就是派生一個Handler的子類,複寫handleMessage方法,而通過上面的程式碼,我們有了一種新的建立Handler方式,那就是不派生子類,而是通過Callback來實現。 這種方式非常少用。看一下Handler裡面的Callback這個介面的設計
public interface Callback {
    public boolean handleMessage(Message msg);
}
複製程式碼
  • 3、如果是send方法傳送的,那麼就執行handleMessage,這個方法我們非常熟悉了,google的給的備註的也說了,子類必須實現方法以接受這些Message。這也就是我們最常見的最常用的方式了。
/**
  * Subclasses must implement this to receive messages.
  */
public void handleMessage(Message msg) {
}
複製程式碼

特殊構造:

public Handler(Looper looper){
    this(looper,null,false);
}
複製程式碼

2.4 Message的工作原理

訊息物件的實體,Message物件的內部實現是連結串列,最大長度是50,用於快取訊息物件。Message物件本身存在於一個訊息池中,達到重複利用訊息物件的目的,以減少訊息物件的建立,所以建議使用obtainMessage方法來獲取訊息物件。

Message message = myHandler.obtainMessage();
複製程式碼

2.5 ThreadLoack的工作原理

可以再不同的執行緒中互不干擾的儲存並提供資料,通過ThreadLocal可以輕鬆獲取每個執行緒的Looper。目的是保證每一個執行緒只建立唯一一個Looper

3.主執行緒的訊息迴圈

Android的主執行緒就是ActivityThread,主執行緒的入口方法為main,在main方法中系統會通過Looper.prepareMainLooper()來建立主執行緒的Looper以及MessageQueue,並通過Looper.loop()來開啟主執行緒的訊息迴圈。主執行緒的訊息迴圈開啟以後,ActivityThread還需要一個Handler來和訊息佇列進行互動,這個Handler就是ActivityThread.H,他內部定義了一組訊息型別,主要包括了四大元件的啟動和停止過程。ActivityThread通過ApplicationThread和AMS進行程式間通訊,AMS以程式間通訊的方式完成Activity的請求後會呼叫ApplicationThread中的Binder方法,然後ApplicationThread會向H傳送訊息,H收到訊息後會將ApplicationThread中的邏輯切換到ActivityThread中去執行,即切換到主執行緒中去執行,這個過程就是主執行緒的訊息迴圈模型。

4.Handler使用記憶體洩漏問題及解決方案

非靜態內部類(包括匿名內部類)預設就會持有外部類的引用,當非靜態內部類物件的生命週期比外部類物件的生命週期長時,就會導致記憶體洩露。 非靜態內部類導致的記憶體洩露在Android開發中有一種典型的場景就是使用Handler,很多開發者在使用Handler是這樣寫的:

public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 做相應邏輯
            }
        }
    };
}

複製程式碼

也許有人會說,mHandler並未作為靜態變數持有Activity引用,生命週期可能不會比Activity長,應該不一定會導致記憶體洩露呢,顯然不是這樣的!mHandler會作為成員變數儲存在傳送的訊息msg中,即msg持有mHandler的引用,而mHandler是Activity的非靜態內部類例項,即mHandler持有Activity的引用,那麼我們就可以理解為msg間接持有Activity的引用。msg被髮送後先放到訊息佇列MessageQueue中,然後等待Looper的輪詢處理(MessageQueue和Looper都是與執行緒相關聯的,MessageQueue是Looper引用的成員變數,而Looper是儲存在ThreadLocal中的)。那麼當Activity退出後,msg可能仍然存在於訊息對列MessageQueue中未處理或者正在處理,那麼這樣就會導致Activity無法被回收,以致發生Activity的記憶體洩露。 通常在Android開發中如果要使用內部類,但又要規避記憶體洩露,一般都會採用靜態內部類+弱引用的方式。

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相應邏輯
                }
            }
        }
    }
}
複製程式碼

mHandler通過弱引用的方式持有Activity,當GC執行垃圾回收時,遇到Activity就會回收並釋放所佔據的記憶體單元。這樣就不會發生記憶體洩露了。上面的做法確實避免了Activity導致的記憶體洩露,傳送的msg不再已經沒有持有Activity的引用了,但是msg還是有可能存在訊息佇列MessageQueue中,所以更好的是在Activity銷燬時就將mHandler的回撥和傳送的訊息給移除掉。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}
複製程式碼

相關文章