從源碼的角度談談面試常客Handler的內部原理

前言

咱們都知道,在進行Android應用開發的時候,主線程(又稱爲UI線程)不能進行網絡請求一類的耗時操做,必須開啓一個子線程來處理;可是在子線程裏面又不能進行更新UI的操做,更新UI必須在主線程裏操做。那麼當子線程進行完耗時操做時如何通知主線程更新UI吶?這個時候Handler就孕育而生了。java

Handler被稱之爲Android內部消息機制,他的做用是在子線程進行完耗時操做的時發送消息通知主線程來更新UI。網絡

使用

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == 1){
            Toast.makeText(JavaDemo.this, "更新UI操做", Toast.LENGTH_SHORT).show();
        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {


    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                handler.sendEmptyMessage(1);
            } catch (InterruptedException e) {
                e.printStackTrace();

            }
        }
    }).start();
}
複製代碼

這裏採用實例化一個Thread線程並經過線程阻塞sleep()模擬耗時操做。咱們能夠從上面代碼看到,當線程完成耗時操做以後,咱們使用sendEmptyMessage()將消息發送給主線程中的handler,覆寫主線程中handler的handMessage()方法並在這個方法裏進行更新UI的操做。這裏須要注意一點,其實咱們這麼寫handler是錯誤的,會引起內存泄露的問題,具體如何引發的咱們後面再分析。mvc

源碼分析

handler的源碼主要是由LooperMessageQueueHandlerThreadLocal幾個部分組成。下面一個一個來進行分析。async

Looper:ide

Looper主要由兩部分東西組成,prepare()和loop()。首先來看prepare()oop

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));
}
複製代碼

能夠看到,在prepare()中主要作了兩件事。第一,判斷ThreadLocal中是否可以取出Looper對象,若是不爲空,則拋出**"一個線程只能有一個Looper"**的異常。這就表明說在一個線程裏有且僅能夠建立一個Looper,若是屢次調用prepare()方法建立Looper則程序會拋出異常。若是發現線程之中沒有Looper,那麼便會new一個Looper將其set進入ThreadLocal當中去。那麼這個ThreadLocal又是什麼?源碼分析

ThreadLocal:

ThreadLocal被稱爲線程內部存儲類。他有一個特色就是在A線程裏面進行set()存儲的數據,只能在A線程get()取出。ui

final ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
threadLocal.set(true);

new Thread("thread1"){
    @Override
    public void run() {
        
        Log.i("thread1",threadLocal.get() + "");
    }
}.start();
複製代碼

咱們看到上面的例子,在主線程中將true放入了ThreadLocal中,以後在子線程試圖從中取出,結果發現此時報null。可見,在主線程中存儲的數據必須在主線程才能夠取出。那麼咱們再從ThreadLocal內部代碼看看爲何會出現這種操做。this

public void set(T value) {
	Thread currentThread = Thread.currentThread();
	Values values = values(currentThread);
	if (values == null) {
		values = initializeValues(currentThread);
	}
	values.put(this, value);
}
複製代碼

咱們看到set()方法中,首先會去獲取當前線程currentThread,以後經過values從當前線程中獲取數據。判斷這個數據是否爲空**「if (values == null)」,爲空則調用initializeValues()方法賦初值,不然將獲取到的value值put()進入values中「values.put(this, value)」**。spa

接着來看get()方法。

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }
複製代碼

從代碼中能夠看到,get()方法的操做其實和set()差很少,都是先獲取當前線程,若是values不爲空則將值返回,若是爲空則先賦初值而後再返回初始值。因爲set()和get()方法都涉及到了從currentThread()中獲取數據,這也就解釋了爲何在一個線程中存儲數據必需要在相同線程中才能取的到的緣由。上述只是對ThreadLocal這個類作簡單的分析,其實這個類內部還有不少東西,因爲篇幅緣由再加上ThreadLocal並不是這篇文章重點,因此這裏咱們只是簡單敘述,有機會專門寫一篇來說解ThreadLocal。

上面說了在判斷ThreadLocal中取出來的數據爲空時會去new一個Looper,並把他添加進ThreadLocal中,那咱們來看看new出的這個Looper的構造方法。

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

構造方法很是簡單,裏面實例化一個消息隊列MessageQueue,而且還會獲取當前線程。也就是說消息隊列此時已經和當前線程綁定,其做用的區域爲當前實例化Looper的線程 。咱們再來看loop()。

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}


public static void loop() {
    final Looper me = myLooper();
    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;
        }

        
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

       

        msg.recycleUnchecked();
    }
}

   
複製代碼

這裏對代碼進行了一些刪減。能夠看到首先會調用myLooper()去獲取一個Looper對象。而從myLooper()的源碼看到從ThreadLocal裏取出在prepare()中存入的Looper對象。先判斷對象是否爲空,若爲空,則拋出異常告訴程序在調用loop()方法以前必需要有一個Looper。這也就說在使用的時候,prepare()方法必需要在loop()方法以前被調用

以後經過Looper對象獲取消息隊列MessageQueue,進入一個死循環for( ; ; ),調用MessageQueue的next()方法,不斷從消息隊列裏獲取消息Message,若是獲取的消息爲空,則return跳出循環,若是不爲空,則將msg消息交給msg.target.dispatchMessage(msg)去處理,那麼這個dispatchMessage()又是什麼,其實這個就是handler,不過咱們後面再分析。最後調用recycleUnchecked()方法回收。到此Looper源碼分析完成。

Handler:

通常使用handler的時候,咱們都會先new實例化一個handler對象,那麼咱們就從handler的構造方法講起。

public Handler(Callback callback, boolean async) {
    
    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;
}
複製代碼

從構造方法咱們能夠看到,仍然是先調用myLooper()方法,從ThreadLocal中取出Looper對象,以後判斷對象是否爲空,爲空則拋異常,不爲空則獲取MessageQueue消息隊列,這樣Handler也就和消息隊裏進行了綁定。

以後在使用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 queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
}

複製代碼

能夠看到裏面層層遞進,sendMessage()裏面調用sendMessageDelayed(),sendMessageDelayed()又調用了sendMessageAtTime(),最終發現其實全部的發送消息方法最後都會來到sendMessageAtTime()方法裏,因而着重看這個方法。這個方法裏先獲取消息隊列MessageQueue,而後將隊列queue、發送的消息msg以及延時時間uptimeMillis一塊兒傳入到enqueueMessage()裏去。

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

在這個方法裏,又調用queue.enqueueMessage()方法將發射的消息傳入到消息隊列MessageQueue當中去。也就是說從handler中發送的消息其實最後全都送到了MessageQueue當中去,而以前在分析loop的時候咱們看到,在loop裏面又調用了MessageQueue的next()方法,把裏面的消息所有交給dispatchMessage去處理。因此最後咱們來看看dispatchMessage()方法

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製代碼

在handler裏面咱們找到了這個方法。這個方法會根據是否有callback而去調用不一樣方法,若是有回調則調用handleCallback(),若是沒有回調則調用handleMessage();

public void handleMessage(Message msg) {
}
複製代碼

咱們能夠看到handleMessage()中是一個空方法,這就表明只要覆寫了這個方法全部的一切就所有由咱們本身來寫邏輯了。

handler內部總體流程:

咱們在使用handler的時候,若在主線程,因爲主線程已經有一個Looper了,因此不須要在建立一個Looper**(一個線程中有且僅能夠有一個Looper,否則會報錯)**。若在子線程,則先調用Looper.prepare()建立一個Looper對象,以後再實例化一個Handler對象,這個Handler會和Looper中的MessageQueue進行綁定,並將sendMessage()發送的消息存儲到這個綁定的消息隊列當中去。而後咱們調用Looper.loop()方法,不斷的從消息隊列MessageQueue當中取出消息交給dispatchMessage()去處理。dispatchMessage()最後調用handleMessage(),全部的邏輯都交給咱們本身去處理。到此,Handler內部原理所有講解完成。

內存泄露:

在文章開篇我寫了一個例子來演示handler如何使用,而且在最後說這麼使用會形成內存泄漏。那麼如今來說講爲何這麼寫會形成內存泄露。

在java中非靜態內部類和匿名內部類都會隱式持有當前類的外部類,因爲Handler是非靜態內部類因此其持有當前Activity的隱式引用,若是Handler沒有被釋放,其所持有的外部引用也就是Activity也不可能被釋放,當一個對象不須要再使用了,原本該被回收時,而有另一個正在使用的對象持有它的引用從而致使它不能被回收,這致使本該被回收的對象不能被回收而停留在堆內存中,這就產生了內存泄漏。

解決辦法:

方法一:經過邏輯代碼處理來進行保護。

咱們在關閉Activity的時候停掉你的後臺線程。線程中止掉了,就等於切斷了handler與外部的鏈接,那麼Activity在退出的時候就會被回收了。

若是你在寫一個驗證碼之類的倒數計時器,用到delay方法去發送消息的時候,咱們在銷燬Activity的時候,應該在onDestroy()方法裏面調用removeCallbacks()將消息移除掉便可。

方法二:將Handler聲明爲靜態類

靜態內部類不會持有外部類的對象,而且爲了不在靜態內部類中可以使用到Activity對象,這裏咱們採用弱引用的方式來持有Activity對象。這裏順帶說下弱引用(WeakReference),弱引用所持有的對象,無論Java內存是否滿了只要調用了GC就必定會被回收掉。因此咱們將最先使用的handler代碼改造一下。

static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}
複製代碼

可見到咱們寫了一個靜態類來繼承Handler,而且在構造方法裏面用弱引用持有了傳遞進來的Activity對象,在handleMessage()方法裏面從弱引用中取出activity對象,若是activity對象不爲空,則直接進行更新UI的操做。

到此,handler全部的內容講解完畢!

相關文章
相關標籤/搜索