天天看點

Handler核心源碼分析

目錄

​​Handler的使用​​

​​Handler初始化​​

​​發送消息​​

​​處理消息​​

​​MessageQueue的阻塞和喚醒​​

​​阻塞​​

​​喚醒​​

​​Handler對我們開發者的啟發​​

​​亮點一​​

​​亮點二​​

​​Looper什麼時候推出​​

​​Handler常見面試題​​

前言

對于一名開發者來說,閱讀源碼是一項必修的課程。在學習源碼的過程中,我們可以了解到設計模式與源代碼開發者的開發習慣。而在閱讀源碼的過程中,我一直秉承着郭霖大神的那句話“抽絲剝繭、點到即止”,我們沒有必要完全深入每一行代碼,通常我們可能隻需要知道這一行代碼的作用就行了。

大家有沒有發現,我們在Android開發過程中,很少遇到過多線程并發的問題,這個就得益于Android為我們提供的線程間通信工具 Handler了,是以我們要了解它是怎麼實作跨線程通信的。

Handler的使用

我們首先知道Handler是怎麼用的,再去剖析其核心源碼。

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  private TextView mTvMain;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
  }
  
  @Override
  protected void onStart() {
    super.onStart();
    new Thread(() -> {
      try {
        // 模拟耗時操作
        Thread.sleep(5000);
        // 擷取 Message 執行個體對象
        Message msg = Message.obtain();
        // 設定 Message 對象的識别内容
        msg.what = Constants.MSG_UPDATE_TEXT;
        // 通過 Handler 把消息發送出去
        handler.sendMessage(msg);
      } catch (InterruptedException e) {
        Log.e(TAG, "onStart: InterruptedException");
      }
    }).start();
  }
  
  private void initView() {
    mTvMain = findViewById(R.id.tv_main);
  }
  
  @SuppressLint("HandlerLeak")
  private Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
      switch (msg.what) {
        case Constants.MSG_UPDATE_TEXT:
          mTvMain.setText("已完成更新操作");
      }
    }
  };
}      

接下來我們就按照上面代碼案例去剖析Handler源碼,順序依次是Handler初始化、

Handler初始化

Handler核心源碼分析

 最終會調用如下方法:

/*
   * 将此标志設定為 true 以檢測擴充此 Handler 類的匿名類、本地類或成員類。 這種類可能會造成洩漏。
   */
  private static final boolean FIND_POTENTIAL_LEAKS = false;
  
  // 如果我們沒有調用Looper.prepare(),sThreadLocal.get() 将傳回null
  // @UnsupportedAppUsage 不讓我們開發者使用
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  final Looper mLooper;
  final MessageQueue mQueue;
  // @UnsupportedAppUsage 不讓我們開發者使用
  final Handler.Callback mCallback;
  final boolean mAsynchronous;
  public Handler(@Nullable Handler.Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
      final Class<? extends Handler> klass = getClass();
      // 如果我們建立的Handler類對象是匿名類,或者是成員類(内部類)、或者局部類(方法中建立的類)并且該類不是靜态的
      // 就有記憶體洩漏的風險。
      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();
    if (mLooper == null) {
      throw new RuntimeException(
          "Can't create handler inside thread " + Thread.currentThread()
              + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
  }
  
  
  /**
   * 傳回與目前線程關聯的 Looper 對象。 如果調用線程未與 Looper 關聯,則傳回 null。
   *
   * @return Looper對象
   */
  public static @android.annotation.Nullable
  Looper myLooper() {
    return sThreadLocal.get();
  }      

這裡我們重點關注一下 mLooper = Looper.myLooper(); 這行代碼,myLooper的實作是:sThreadLocal.get();這裡我們就要說說 ThreadLocal了,可參考我的另一篇博文:​​并發程式設計基礎(二)—— ThreadLocal及CAS基本原理剖析​​

其實說白了,ThreadLocal 正如其名,它就是一個線程本地副本,我們的線程内部會有一個ThreadLocal.ThreadLocalMap的成員:

Handler核心源碼分析

再來看看具體的get方法:

/**
   * 傳回此線程中 ThreadLocalMap成員以ThreadLocal為key對應的值(Object類型),如果ThreadLocalMap成員為null,
   * 則首先将其初始化,調用 {@link #initialValue} 。
   *
   * @return 傳回目前線程的中 ThreadLocal 對應的值
   */
  public T get() {
    // 擷取目前線程
    Thread t = Thread.currentThread();
    // 擷取目前線程的 ThreadLocal.ThreadLocalMap 成員
    ThreadLocalMap map = getMap(t);
    // 如果map不為空
    if (map != null) {
      // 通過 ThreadLocal 擷取Entry
      ThreadLocalMap.Entry e = map.getEntry(this);// 這裡的this就是目前線程副本 ThreadLocal 的執行個體
      // 如果 Entry 不為空,傳回它的 value 屬性的值
      if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T) e.value;
        return result;
      }
    }
    //否則設定初始值
    return setInitialValue();
  }      

上面的代碼包含了 ThreadLocalMap.Entry:

Handler核心源碼分析

我們發現 Entry 的構造函數包含 ThreadLocal 和 Object,而它又是 ThreadLocalMap  的内部類,那我們就可以了解為 ThreadLocalMap 是以 ThreadLocal 為 key,任意對象為 value 的鍵值對結構(Map結構),即是一一對應的。

既然有 get 方法,那就有 set 方法了,我們發現在 Looper.prepare() 調用了 set:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        // 如果已經給目前線程的 ThreadLocal 設定過 一個Looper,則抛出異常,這就保證了 一個 ThreadLocal 隻對應一個 Looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 否則 new 一個 Looper對象,并設定給目前線程的 ThreadLocal
        sThreadLocal.set(new Looper(quitAllowed));
    }

    // Looper 的構造方法
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }      

 如上代碼通過判斷 sThreadLocal.get() 是否為 null 的邏輯,保證了每一個線程的 sThreadLocal 隻會調用一次 set 方法把 sThreadLocal 和Looper 綁定起來,而 sThreadLocal 又是static final的,那也就是說一旦線程調用了 Looper 的方法, sThreadLocal 就會被指派,并且一旦指派就不可更改,說明 Looper 與 ThreadLocal 是一一對應的。而且我們在 Android 源碼中全局搜尋 MessageQueue 的構造方法,發現隻有在 Looper 的構造方法中調用了

new MessageQueue(quitAllowed),并且 MessageQueue 的構造方法是包管理權限,也就是說我們普通開發者是不能調用的,那也就說明了通過 Looper 構造方法唯一建立 MessageQueue 對象,實作了 Looper 和 MessageQueue 的一一對應。再結合上面我們對 ThreadLocal 的學習,我們知道了 一個線程對應唯一的 ThreadLocal , 那麼就說明 Android的 Handler 機制中,Thread、ThreadLocal、Looper、MessageQueue這四者是一一對應。

發送消息

Handler的主要方法

Handler核心源碼分析

調用流程:

Handler.sendMessage() --> Handler.sendMessageDelayed()--> Handler.sendMessageAtTime()-->  Handler.enqueueMessage()--> MessageQueue.enqueueMessage()

可以看到最終會調用 MessageQueue.enqueueMessage() 将 Message 插入隊列。

Handler核心源碼分析

Handler工作流程圖

那麼接下來我們剖析enquque方法:

Message next; // Message的成員
  
  Message mMessages;// MessageQueue 的成員,可以看作是隊列的隊頭
  boolean enqueueMessage(Message msg, long when) {
        // 省略不是很重要的代碼

        // 加鎖保證線程安全,防止多個線程同時給MessageQueue中插入消息
    synchronized (this) {
            // 省略不是很重要的代碼
      
      // 給要插入消息隊列(MessageQueue)中的消息指定延遲時間,也就是在多久之後處理此消息
      msg.when = when;
      // 把消息隊列的隊頭指派給 p
      Message p = mMessages;
      // 是否喚醒消息隊列
      boolean needWake;
      // 最開始,消息隊列肯定是空的。那如果目前消息隊列中沒有消息,或者我們插入的消息的等待時間是0,那我們就把消息插入,
      // 并讓其指向null;
      // 或者消息隊列中有一個即将處理的消息,而我們插入的消息等待時間小于即将要處理的消息,那就把我們插入的消息放在其前面。
      if (p == null || when == 0 || when < p.when) {
        // 插入的消息的下一個消息指向p,p可能為null
        msg.next = p;
        // 把我們要插入的消息指派給消息隊列的隊頭,實作插入隊列操作
        mMessages = msg;
        // 當消息隊列中沒有消息時,就會阻塞,此時 mBlocked為 true
        needWake = mBlocked;
      }
      // 這種情況是消息隊列中有 N多個消息了
      else {
                // 插入隊列中間。 通常我們不必喚醒消息隊列,除非隊列頭部有屏障,并且消息是隊列中最早的異步消息。
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        
        Message prev;
        // 通過死循環,不停地比較我們要插入的消息與隊列中已有的消息
        for (;;) {
          // 目前消息隊列中用于作比較的消息作為前一個消息
          prev = p;
          // 然後讓 prev指向 p(p指向p.next)
          p = p.next;
          // 從消息隊列中的第一個消息開始周遊,直到消息隊列的末尾即null值,方可跳出循環;
          // 或者要插入的消息等待時間小于目前我們正在做比較的消息,此時也跳出循環。
          if (p == null || when < p.when) {
            break;
          }
        }
        // 插入消息
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
      }
    }
    return true;
  }      
Handler核心源碼分析
Handler核心源碼分析

Message入隊示意圖

處理消息

入隊我們講完了,下來該講出隊了。出隊我們是通過 Looper.loop()實作:

/**
   * 在目前線程中運作消息隊列。 請務必調用 {@link #quit()} 來結束循環。以下代碼省略不必要(看不懂)的代碼進行解析
   */
  public static void loop() {
    // 擷取目前線程的 Looper 對象
    final Looper me = myLooper();
    if (me == null) {
      // 這個異常在我們初學者會經常遇到,因為初學者總是忘記在子線程調用 Looper.prepare()
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 擷取目前線程的 MessageQueue 對象
    final MessageQueue queue = me.mQueue;
    
    for (; ; ) {
      // 取出消息 --- 出隊
      Message msg = queue.next(); // 可能會阻塞,當MessageQueue沒有消息是,會調用nativePollOnce(ptr, -1);一直挂起
      if (msg == null) {
        // 沒有消息表示消息隊列正在推出
        return;
      }
      
      try {
        // target是Message中的 Handler 成員
        msg.target.dispatchMessage(msg);
      } catch (Exception exception) { }
      finally { }
      
      // 回收可能正在使用的消息
      msg.recycleUnchecked();
    }
  }      

loop方法中包含一個死循環,通過 MessageQueue.next() 不停地取出消息:

/**
   * 消息出隊,同樣省略 N多行不是很重要的(看不懂的)代碼
   *
   * 嘗試檢索下一條消息, 如果找到傳回。
   *
   * @return 傳回即将要被處理的消息
   */
  private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
  
  Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
      // 調用 native 方法睡眠 nextPollTimeoutMillis 這麼多毫秒,如果該值為-1,則會無限等待
      nativePollOnce(ptr, nextPollTimeoutMillis);
      synchronized (this) {
        // 自啟動以來的非睡眠正常運作時間毫秒數。
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;// mMessages可以看作是消息隊列的隊頭
        if (msg != null && msg.target == null) {
          // 被一道屏障擋住了,查找隊列中的下一條異步消息。
          do {
            prevMsg = msg;
            msg = msg.next;
          } while (msg != null && !msg.isAsynchronous());
        }
        if (msg != null) {
          if (now < msg.when) {
            // 下一條消息尚未準備好,設定延遲時間以在它準備好時喚醒。
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
          } else {
            // 下一條消息已就緒,等待被處理,mBlocked置為 false
            mBlocked = false;
            if (prevMsg != null) {
              prevMsg.next = msg.next;
            } else {
              mMessages = msg.next;
            }
            msg.next = null;
            // 傳回一個消息等待處理
            return msg;
          }
        } else {
          // 沒有消息的情況
          nextPollTimeoutMillis = -1;
        }
        
        if (pendingIdleHandlerCount < 0
            && (mMessages == null || now < mMessages.when)) {
          // mIdleHandlers是一個ArrayList,通過 addIdleHandler 添加,一般我們不會調用此方法,
          // 是以大多數情況下 mIdleHandlers.size()是0
          pendingIdleHandlerCount = mIdleHandlers.size();
        }
        if (pendingIdleHandlerCount <= 0) {
          // 沒有要運作的空閑 Handler ,循環并等待更多時間 ,是以大多情況下是一直阻塞的,這也就解釋了為什麼我們的
          // Activity 顯示出來之後,我們隻要一直亮屏,它就不會結束,因為ActivityThread的main方法調用了Looper.loop,
          // 使得程式一直挂起。
          mBlocked = true;
          continue;
        }
      }
    }
  }      

最終調用  Handler.dispatchMessage(),而 Handler.dispatchMessage() 就會回調 handleMessage()。

Handler核心源碼分析

從對MessageQueue的源碼剖析過程中,我們可以得出它是一個由單連結清單構成的具有優先級的隊列。為什麼這麼說呢?

單連結清單:Message的next成員也是Message,形成一個單連結清單。

優先級:展現在每次消息入隊的時候,我們最終調用 enqueueMessage(Message msg, long when) 方法,這裡的 when 指的是消息等待處理的時間或者叫做延遲時間,而消息的處理同樣會根據這個等待時間的大小順序來。

隊列:滿足先進先出規則。

MessageQueue的阻塞和喚醒

阻塞

其實在上面的代碼示例中,我已經講過在調用 MessageQueue.next() 時,會調用 nativePollOnce(ptr, nextPollTimeoutMillis) 實作阻塞,當 nextPollTimeoutMillis 是-1時,會一直阻塞。沒明白的同學再好好看看上面的代碼(一定要看注釋)。

喚醒

在MessageQueue.enqueueMessage() 中有這樣一段邏輯:

//  是否需要喚醒
boolean needWake;
// 如果消息隊列沒有消息,那麼 mBlocked 就為 true,那我插入消息時就得喚醒消息隊列
if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    // 如果 mBlocked 為true,即隊列阻塞,則需要喚醒
    needWake = mBlocked;
}

if (needWake) {
    // 調用 native 方法喚醒隊列
    nativeWake(mPtr);
}

// 沒有 Handler 要處理時,或者可以了解為消息隊列中沒有消息時,消息隊列就會阻塞
if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run.  Loop and wait some more.
    mBlocked = true;
    continue;
}      

上面的代碼意思是說,在 MessageQueue 中沒有 Message 時,它就會阻塞,此時我們插入 Message,就會喚醒 Message。

Handler對我們開發者的啟發

亮點一

Handler核心源碼分析
/**
   * 在此處處理系統消息。
   */
  public void dispatchMessage(@NonNull Message msg) {
    // 如果 Message 自己有callback,就調用 Message.callback.run()
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      // 如果我們建立 Handler 時給 Callback 指派了,就走這裡
      if (mCallback != null) {
        // 當 Callback 處理完消息之後,我們可以根據傳回值确定,要不要走最後面的 handleMessage(msg);
        // 這個就與我們的View事件分發機制有些相似點了,根據傳回值決定事件是否繼續往下分發
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }
  
  private static void handleCallback(Message message) {
    message.callback.run();
  }
  
  /**
   * 可以在執行個體化 Handler 時使用回調接口,以避免必須實作自己的 Handler 子類。
   */
  public interface Callback {
    /**
     * @param msg 一個{@link android.os.Message Message} 對象執行個體
     * @return 如果不需要進一步處理,則為 true
     */
    boolean handleMessage(@NonNull Message msg);
  }      

上面代碼就展現面向對象的封裝思想,Message 封裝了自己的 Callback ,如果 Message 是設定了它自己的 Callback,就回調自己的 callback.run();同樣 Handler 封裝了自己的 Callback ,如果 Handler 是設定了它自己的 Callback, 就回調自己的 handleMessage(),并且我們可以根據傳回值決定要不要執行 Handler 子類重寫的 handleMessage()。這樣使得程式很靈活,有點像​​責任鍊模式​​。

亮點二

大家有沒有想過,Handler 調用了 dispatchMessage 之後,就把消息出來完了,那消息是怎麼回收的?

其實在 Looper.loop() 中,最終會調用 Message.recycleUnchecked() 進行所謂的消息回收(其實消息并未被回收),我們來看源碼:

public static final Object sPoolSync = new Object();
  
  /**
   * 回收可能正在使用的消息。 處理排隊的消息時,由 MessageQueue 和 Looper 在内部使用。
   */
  @UnsupportedAppUsage
  void recycleUnchecked() {
    // 将消息标記為正在使用,同時保留在回收對象池中。清除所有其他詳細資訊。
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;


        // 加鎖,避免多個線程回收消息時,消息池的消息混亂    
    synchronized (sPoolSync) {
      if (sPoolSize < MAX_POOL_SIZE) {
        // 消息池的隊頭
        next = sPool;
        // 把目前處理完的消息指派給隊頭
        sPool = this;
        // 消息池的消息量加一
        sPoolSize++;
      }
    }
  }      

然後再看看Message.obtain():

/**
   * 從消息池中傳回一個 Message 執行個體。 避免我們在很多情況下建立新的消息對象。
   */
  public static Message obtain() {
    synchronized (sPoolSync) {
      // 消息池隊頭不為空
      if (sPool != null) {
        // 隊頭指派給要傳回的消息對象
        Message m = sPool;
        // 隊頭指向 m 的下一個節點
        sPool = m.next;
        // m 的下一個節點置空
        m.next = null;
        m.flags = 0; // clear in-use flag
        sPoolSize--;
        return m;
      }
    }
    return new Message();
  }      

我們發現上述代碼跟我們登機之前做安檢的場景很像,Message 就好比是放行李的盒子,Message的成員what、arg1、arg2、objtct就相當于是行李,當我們過完安檢之後,盒子不會被丢棄,而是放在安檢門口以備下一個過安檢的人使用,這好比我們的消息池,其實這裡所用的就是​​享元模式​​。

那麼這樣做有什麼好處,從記憶體優化的角度思考,通過 Message.obtain() 擷取消息大大減小了 new Message() 的調用,也就減少了連續記憶體空間被過度破壞,即不至于過度碎片化,也就是記憶體中連續空間更多了,那OOM出現的機率就小了。可能有同學說GC是幹什麼吃的,那大家有沒有想過,既然 GC會幫我們回收垃圾,釋放記憶體,為什麼還會出現OOM。其實 GC即便用了标記整理算法,使得記憶體空間連續,但是GC線程工作的時候,會STW(stop the world),即其他所有線程都得挂起。而且我們不斷地new 對象,又不斷地觸發 GC,會産生記憶體抖動,進而導緻卡頓,是以從性能優化的角度來講,我們盡量避免不必要的記憶體開銷。

Looper什麼時候推出

我們如果在子線程中建立 Looper 經常會有記憶體洩漏的問題,因為大部分同學都沒有釋放 Looper。那怎麼辦釋放呢?通過調用 Looper.quitSafely() 或者 Looper.quit()

/**
   * 安全退出Looper。
   * 處理完消息隊列中所有剩餘的消息後,立即終止 {@link #loop} 方法。 但是,在 loop 終止之前,将不會傳遞未來到期的待處理的延
   * 遲消息。在要求 Looper 退出後,任何向隊列釋出消息的嘗試都将失敗。 例如,{@link Handler#sendMessage(Message)} 方法
   * 将傳回 false。
   */
  public void quitSafely() {
    mQueue.quit(true);// 安全退出傳的參數是 true,而Looper.quit()傳的參數是 false
  }      

最終會調用到MessageQueue.quit():

/**
   * 退出Looper
   *
   * @param safe 是否需要安全退出
   */
  void quit(boolean safe) {
    // 如果不允許退出,會抛異常,ActivityThread中的Looper就不允許退出。
    if (!mQuitAllowed) {
      throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
      // 如果正在退出,則return
      if (mQuitting) {
        return;
      }
      mQuitting = true;

      if (safe) {
        // 安全退出
        removeAllFutureMessagesLocked();
      } else {
        // 不安全退出
        removeAllMessagesLocked();
      }

      // We can assume mPtr != 0 because mQuitting was previously false.
      nativeWake(mPtr);
    }
  }      

安全退出的實作:

private void removeAllFutureMessagesLocked() {
    // 擷取目前時間
    final long now = SystemClock.uptimeMillis();
    // 消息隊列的隊頭指派給 p
    Message p = mMessages;
    // 如果隊頭存在
    if (p != null) {
      // 如果隊頭延遲時間大于目前時間,移除所有消息
      if (p.when > now) {
        removeAllMessagesLocked();
      } else {// 繼續判斷,取隊列中所有大于目前時間的消息
        Message n;
        for (;;) {
          n = p.next;
          if (n == null) {
            return;
          }
          if (n.when > now) {
            break;
          }
          p = n;
        }
        p.next = null;
        // 将所有所有大于目前時間的消息回收,延遲時間小于目前時間的消息即使消息隊列退出了,仍然會繼續被取出執行
        do {
          p = n;
          n = p.next;
          p.recycleUnchecked();
        } while (n != null);
      }
    }
  }      

不安全退出的實作:

/**
   * 移除消息隊列所有消息,包括延遲時間小于目前時間的消息
   */
  private void removeAllMessagesLocked() {
    Message p = mMessages;
    // 輪循隊列中所有 Message對象,一一緩存到消息池中
    while (p != null) {
      Message n = p.next;
      // 緩存到消息池中
      p.recycleUnchecked();
      p = n;
    }
    // 隊頭置空
    mMessages = null;
  }      

Handler常見面試題

1.一個線程有幾個 Handler?

在一個線程中我們可以建立多個 Handler。

2.一個線程中有幾個 Looper?是如何保證的?

一個線程中隻有一個 Looper;

我們線上程中使用 Looper之前,先要調用 Looper.preprare,而prepare 中用到了 ThreadLocal,ThreadLocal是線程本地副本,是每個線程獨有的成員,線程就是通過它實作線程資料隔離的,并且 ThreadLocal 存儲資料是通過其内部類 ThreadLocalMap中的内部類 Entry 存儲的,Entry 的構造方法中包含兩個成員,ThreadLocal 和 Object,是以在 Handler機制中,可以把 ThreadLocalMap 了解為以 ThreadLocal 為 key,Looper為 value的鍵值對。然後我們發現 Looper中的 ThreadLocal 成員是被 static final 修飾的,那也就是說 Looper 被加載時,它的成員 ThreadLocal 就被初始化了,且不可更改。那就保證了 ThreadLocal 的唯一性,那我隻要再保證,Looper 的唯一性,就可以說明在 Handler機制中,Thread、Looper、ThreadLocal三者是一一對應的 。我們發現 prepare 方法中會先判斷是否給 ThreadLocal 已經設定過值,如果沒有設定過,new Looper()設定給它,否則會抛出異常。這就說明了 ThreadLocal 和 Looper 也是一一對應的了。

3.Handler記憶體洩漏原因? 為什麼其他的内部類沒有說過有這個問題?

4.為何主線程可以new Handler?如果想要在子線程中new Handler 要做些什麼準備?

5.子線程中維護的Looper,消息隊列無消息的時候的處理方案是什麼?有什麼用?

6.既然可以存在多個 Handler 往 MessageQueue 中添加資料(發消息時各個 Handler 可能處于不同線程),那它内部是如何確定線程安全的?取消息呢?

很簡單,加 synchronized 鎖

7.我們使用 Message 時應該如何建立它?

Message.obtain();因為它采用享元模式,重複利用回收的消息,大大減少new Message() 的機率,從記憶體優化的角度看,減少不必要的記憶體開銷,可以有效避免記憶體過度碎片化,進而降低出現OOM的機率。