Handler 源碼解析——Handler的建立

前言

Android 提供了Handler和Looper來來知足線程間的通訊,而前面咱們所說的IPC指的是進程間的通訊。這是兩個徹底不一樣的概念。java

Handler先進先出原則,Looper類用來管理特定線程內消息的交換(MessageExchange);android

一、爲何會有Handler機制?

咱們剛說Handler機制的主要做用是將某一任務切換到特定的線程來執行,咱們作項目可能都遇到過ANR(Application Not Response),這就是由於執行某項任務的時間太長而致使程序沒法響應。這種狀況咱們就須要將這項耗時較長的任務移到子線程來執行,從而消除ANR。而咱們都知道Android規定訪問UI只能在主線程中進行,若是在子線程中訪問UI,那麼程序就會拋出異常。而Android提供Handler就是爲了解決在子線程中沒法訪問UI的矛盾。數組

二、Handler源碼解析

子線程中建立Handler爲啥會報錯?

首先,咱們先看一個例子,咱們在子線程中建立一個Handler。bash

new Thread(new Runnable() {
      @Override
      public void run() {
        new Handler(){
          @Override
          public void handleMessage (Message message){
            super.handleMessage(message);
          }
        };
      }
    },"MyThread").start();
複製代碼

咱們運行時會發現,會拋出異常:Can't create handler inside thread that has not called Looper.prepare()app

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.<init>(Handler.java:204) at android.os.Handler.<init>(Handler.java:118) at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1$1.<init>(MainActivity.java:21) at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1.run(MainActivity.java:21) at java.lang.Thread.run(Thread.java:764) 複製代碼

到底是爲何會拋出異常呢?下面咱們經過Handler源碼來看看。async

Handler的構造方法

當咱們建立Handler對象的時候調用的是下面的方法:ide

/**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public Handler() { this(null, false); } 複製代碼

咱們看到註釋中說:++默認的構造函數將這個Handler與當前的線程的Looper關聯,若是當前線程沒有Looper,那麼這個程序將沒法接收消息,所以會拋出異常。++ 到底是怎麼拋出異常的呢?咱們繼續往下看:函數

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();//註釋1
        if (mLooper == null) {
            throw new RuntimeException(//註釋2
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
複製代碼

咱們看到在這裏開始有個if語句,因爲FIND_POTENTIAL_LEAKS默認值是false因此咱們不須要去管它,註釋1處,這裏調用了Looper.myLooper(),咱們看看它的源碼:oop

Looper.myLooper()

/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製代碼

這個方法的做用就是返回與當前線程相關連的Looper對象。這裏又調用了ThreadLocal.get(),ui

ThreadLocal

ThreadLocal是一個線程內部的數據存儲類,經過它能夠在指定的線程中存儲數據,數據存儲之後只有在特定的線程中能夠獲取到存儲的數據,對於其餘線程來講則沒法獲取到。ThreadLocal用一句大白話來說解,++就是看上去只new了一份,但在每一個不一樣的線程中卻能夠擁有不一樣數據副本的神奇類。++ 其本質是ThreadLocal中的Values類維護了一個Object[],而每一個Thread類中有一個ThreadLocal.Values成員,當調用ThreadLocal的set方法時,實際上是根據必定規則把這個線程中對應的ThreadLocal值塞進了Values的Object[]數組中的某個index裏。這個index老是爲ThreadLocal的reference字段所標識的對象的下一個位置。 下面咱們來看它的get方法。

ThreadLocal.get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

在該方法的第二行調用了getMap方法。就是去獲取當前線程的ThreadLocalMap對象。可是這個對象是在何時建立的呢?

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
複製代碼

由於拋出異常了因此咱們猜想這個值多是null。說到這裏可能有些迷茫,咱們回頭看看註釋1處那裏緊接着,咱們看到若是獲取到的myLooper爲空(至於爲何會返回爲空咱們看完後面回過頭來看就很明白了)的的話,就會拋出咱們前面看到的異常。

......
        mLooper = Looper.myLooper();//註釋1
        if (mLooper == null) {
            throw new RuntimeException(//註釋2
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        ......
複製代碼

異常中說若是在子線程中建立Handler必需要調用Looper.prepare()方法,那麼咱們想確定是在調用該方法的時候作了一些操做,可能跟後面消息的接收和處理相關,經過後面的源碼咱們會發現其實這個方法是對當前線程建立一個Looper對象,咱們來看源碼:

Looper.prepare()

&emsp;public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//註釋3
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
複製代碼

咱們調用是沒有傳參的prepare,他會調用內部的傳參的prepare方法。咱們看到註釋3處又調用了ThreadLocal.get(),假設咱們第一次調用Looper.prepare(),那麼這個值確定是空的。若是不是空的話前面Looper.myLooper()就不會爲空,也就不會拋出異常了。(那麼當在一個線程中第二次調用該方法的時候,他的返回值就不會是空,系統會拋出異常一個線程只能建立一個Looper。也就是說一個子線程中Looper.prepare()只能調用一次。)因此這裏確定走ThreadLocal.set()方法,而且新建了一個Looper對象做爲入參:

ThreadLocal.set()

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
複製代碼

咱們看到第二行仍是調用了getMap因爲第一次調用,因此這個返回值仍是空的。因此應該走了createMap(),下面咱們看看它的源碼:

&emsp;void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
複製代碼

咱們看到這裏纔對當前線的ThreadLocal進行了賦值。這個方法中咱們看到ThreadLocal已Map的結構存儲了當前線程對應的Looper。以線程爲Entry也就是Key,以Looper爲Value。咱們回過來再看,Looper.myLooper()。

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//註釋4
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

複製代碼

其實發現他就是去獲取當前線程的Looper。當沒有調用Looper.prepare()來建立Looper時,當前線程的ThreadLocalMap對象爲空,因此前面的ThreadLocal.get()方法會調用setInitialValue這個方法,

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
複製代碼

咱們看到這個方法返回了value,value是經過一個方法返回的,下面咱們看看這個方法:

protected T initialValue() {
        return null;
    }

複製代碼

這個方法單純的就是返回null,因此也就是至關於ThreadLocal.get()返回null===>Looper.myLooper()返回null。因此Handler會在註釋2處拋出異常。

總結

回過頭來,咱們仔細想一想爲何會拋出異常來?就是由於: Handler對象是基於Looper的,每一個Handler必須有一個Looper,這個Looper是在調用Looper.prepare()的時候建立的,這個Looper會以Map的形式存儲在當前線程的ThreadLocal中。噹噹掉用Looper.myLooper()方法就是去在當前線程的ThreadLocal中拿到當前線程的Looper對象。

相關文章
相關標籤/搜索