最通俗易懂的 Handler 源碼解析

簡介

在 Android 中 UI 線程是不安全的,若是在子線程中嘗試進行更新 UI 操做,程序就有可能會崩潰;固然若是在 UI 線程中作耗時的操做,系統就會彈出 ANR 彈窗提示該程序無響應,十分影響用戶體驗。html

Android 系統中提供了 Handler,這樣咱們就可使用 Handler 在子線程中發送消息來更新 UI;也能夠將耗時操做交給子線程處理,等子線程處理完後再使用 Handler 發送消息來回到主線程。java

能夠看到 Handler 的主要做用是進行線程間通訊的,本文將從源碼的角度分析下 Handler,以便更好的理解 Handler 的工做流程。android

咱們先來回顧下 Handler 經常使用的方式:c++

// 在主線程中建立 Handler 來處理子線程發送的消息
private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
    switch (msg.what) {
      case 0:
        //TODO: 處理消息
        break;
    }
  }
};

// 使用方式一:在子線程中發送消息
new Thread(new Runnable() {
  @Override
  public void run() {
    Message message = new Message();
    message.what = 0;
    message.obj = "測試消息";
    // 子線程中發送消息
    handler.sendMessage(message);
  }
}).start();

// 使用方式二:handler.post()
handler.post(new Runnable() {
  @Override
  public void run() {
    // 運行在子線程中...
  }
});
複製代碼

Handler

private Handler handler = new Handler();
複製代碼

經過上面示例代碼能夠看到,在使用 Handler 時首先須要建立 Handler 對象,咱們先來看下 Handler 的構造方法。git

//frameworks/base/core/java/android/os/Handler.java

/* 構造方法一 */
public Handler() {
  this(null, false);
}
/* 構造方法二 */
public Handler(Callback callback) {
  this(callback, false);
}
/* 構造方法三 */
public Handler(Looper looper) {
  this(looper, null, false);
}
/* 構造方法四 */
public Handler(Looper looper, Callback callback) {
  this(looper, callback, false);
}
/* 構造方法五 */
public Handler(boolean async) {
  this(null, async);
}
/* 構造方法六 */
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;
}
/* 構造方法七 */
public Handler(Looper looper, Callback callback, boolean async) {
  mLooper = looper;
  mQueue = looper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}
複製代碼

能夠看到 Handler 有不少構造方法,咱們通常經常使用的是「構造方法一」和「構造方法三」。github

咱們在「構造方法六」中能夠看到:算法

//frameworks/base/core/java/android/os/Handler.java

/* 構造方法六 */
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;
}
複製代碼

這裏調用了 Looper.myLooper() 方法,當 mLooper 爲空時會拋出異常,提示咱們須要先調用 Looper.prepare() 方法,我接下來看下 Looper 中的這兩個方法。編程

Looper

//frameworks/base/core/java/android/os/Looper.java

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

final MessageQueue mQueue;
final Thread mThread;
複製代碼

從上面源碼中能夠看到 Looper 有 4 個成員變量:segmentfault

  • sThreadLocal:保存的是當前線程的 Looper。
  • sMainLooper:Application 中主線程中的 Looper。
  • mQueue:當前線程中的 MessageQueue。
  • mThread:建立 Looper 的線程。

myLoop()

//frameworks/base/core/java/android/os/Looper.java

/* Handler 構造方法六中調用的方法 */
public static Looper myLooper() {
  // 返回當前線程中的 looper
  return sThreadLocal.get();
}
複製代碼

能夠看到 myLooper() 邏輯很簡單,調用了 ThreadLocal 的 get() 方法。ThreadLocal 咱們稍後再分析。數組

prepare()

在 Handler 構造方法六中能夠看到,若是 myLoop() 的結果爲空會直接拋出異常,提示須要先調用 prepare() 方法,接下來分析下 prepare() 方法。

//frameworks/base/core/java/android/os/Looper.java

/* Handler 構造方法六中調用的方法 */
public static void prepare() {
  prepare(true);
}
/* 帶參數的 prepare 方法 */
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));
}
/* Looper 構造方法 */
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
複製代碼

prepare() 方法中調用了 prepare(quitAllowed) 方法,這裏判斷了 Looper 是否爲空。

若是當前線程已經建立了 Looper 直接拋出異常,也就是說一個線程中只能建立一個 Looper,常用 Handler 的小夥伴應該對這個異常很熟悉。

若是當前線程沒有建立 Looper 會直接調用 Looper(quitAllowed) 的構造方法,建立一個 Looper 並建立一個 MessageQueue,而後保存一下當前線程的信息。

MessageQueue

接下來咱們分析下 MessageQueue 的具體實現。

//frameworks/base/core/java/android/os/Looper.java

final MessageQueue mQueue;

/* Looper 構造方法 */
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
複製代碼

咱們看下 MessageQueue 的構造方法:

//frameworks/base/core/java/android/os/MessageQueue.java

private native static long nativeInit();

MessageQueue(boolean quitAllowed) {
  mQuitAllowed = quitAllowed;
  mPtr = nativeInit();
}
複製代碼

MessageQueue 的構造方法邏輯仍是很簡單的。這裏調用了一個 native 方法 nativeInit() 在 native 層進行了初始化,感興趣的能夠去查看 native 源碼,文件以下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
  NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
  if (!nativeMessageQueue) {
    jniThrowRuntimeException(env, "Unable to allocate native queue");
    return 0;
  }

  nativeMessageQueue->incStrong(env);
  return reinterpret_cast<jlong>(nativeMessageQueue);
}
複製代碼

分析到這裏 Handler 的建立流程已經分析完了,目前能夠看到 Handler 建立時建立了以下內容:

Handler 建立過程

如圖所示,在建立 Handler 以前須要先調用 Looper.prepare(),該方法會初始化 Looper,建立 MessageQueue 和 ThreadLocal。

第二步當咱們建立 Handler 時會調用 Looper 中的 myLoop() 方法獲取到 Looper 和 MessageQueue 保存到 Handler 中。

ThreadLocal

咱們如今來分析下 ThreadLocal 的做用。

ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal.set(new Looper(quitAllowed)); // 設置變量信息
sThreadLocal.get(); // 讀取變量信息
複製代碼

ThreadLocal 提供了線程本地變量,它能夠保證訪問到的變量屬於當前線程,每一個線程都保存有一個變量副本,每一個線程的變量都不一樣,而同一個線程在任什麼時候候訪問這個本地變量的結果都是一致的。

ThreadLocal 至關於提供了一種線程隔離,將變量與線程相綁定。而當線程結束生命週期時,全部的線程本地實例都會被 GC 回收掉。一般 ThreadLocal 定義爲 private static 類型。

nextHashCode()

接下來分析下 ThreadLocal 的具體實現。

//java/lang/ThreadLocal.java

private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode =
    new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
複製代碼

ThreadLocal 經過 threadLocalHashCode 來標識每個 ThreadLocal 的惟一性。threadLocalHashCode 經過 CAS 操做進行更新,每次 hash 操做的增量爲 0x61c88647。

set()

咱們來看看 ThreadLocal 的 set() 方法。

//java/lang/ThreadLocal.java

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

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

能夠看到經過 Thread.currentThread() 方法獲取了當前的線程引用,並傳給了 getMap(Thread) 方法獲取一個 ThreadLocalMap 的實例。

getMap(Thread) 方法中直接返回 Thread 實例的成員變量 threadLocals。它的定義在 Thread 內部,訪問級別爲 package 級別:

//java/lang/Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼

到了這裏,能夠看出,每一個 Thread 裏面都有一個 ThreadLocal.ThreadLocalMap 成員變量,也就是說每一個線程經過 ThreadLocal.ThreadLocalMapThreadLocal 相綁定,這樣能夠確保每一個線程訪問到的 ThreadLocal 變量都是本線程的。

咱們往下繼續分析。獲取了 ThreadLocalMap 實例之後,若是它不爲空則調用 ThreadLocalMap.ThreadLocalMap.set() 方法設值;若爲空則調用 ThreadLocal.createMap() 方法 new 一個 ThreadLocalMap 實例並賦給 Thread.threadLocals。

ThreadLocalMap

下面咱們分析一下 ThreadLocalMap 的實現,能夠看到 ThreadLocalMap 有一個常量和三個成員變量:

//java/lang/ThreadLocal.ThreadLocalMap

private static final int INITIAL_CAPACITY = 16;

private Entry[] table;

private int size = 0;

private int threshold; // Default to 0
複製代碼

其中 INITIAL_CAPACITY 表明這個 Map 的初始容量;table 是一個 Entry 類型的數組,用於存儲數據;size 表明表中的存儲數目; threshold 表明須要擴容時對應 size 的閾值。

Entry 類是 ThreadLocalMap 的靜態內部類,用於存儲數據。它的源碼以下:

//java/lang/ThreadLocal.ThreadLocalMap

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
複製代碼

Entry 類繼承了 WeakReference<ThreadLocal<?>>,即每一個 Entry 對象都有一個 ThreadLocal 的弱引用(做爲 key),這是爲了防止內存泄露。一旦線程結束,key 變爲一個不可達的對象,這個 Entry 就能夠被 GC 回收了。

ThreadLocalMap 類有兩個構造函數,其中經常使用的是 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue):

//java/lang/ThreadLocal.ThreadLocalMap

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
複製代碼

構造函數的第一個參數就是本 ThreadLocal 實例(this),第二個參數就是要保存的線程本地變量。構造函數首先建立一個長度爲 16 的 Entry 數組,而後計算出 firstKey 對應的哈希值,而後存儲到 table 中,並設置 size 和 threshold。

注意一個細節,計算 hash 的時候裏面採用了 hashCode & (size - 1) 的算法,這至關於取模運算 hashCode % size 的一個更高效的實現(與 HashMap 中的思路相同)。正是由於這種算法,咱們要求 size 必須是 2 的指數,由於這可使得 hash 發生衝突的次數減少。

  • set()

接下來咱們來看 ThreadLocalMap.set() 方法的實現:

//java/lang/ThreadLocal.ThreadLocalMap

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
複製代碼

若是衝突了,就會經過 nextIndex 方法再次計算哈希值:

//java/lang/ThreadLocal.ThreadLocalMap

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}
複製代碼

到這裏,咱們看到 ThreadLocalMap 解決衝突的方法是 線性探測法(不斷加 1),而不是 HashMap 的 鏈地址法,這一點也能從 Entry 的結構上推斷出來。

  • getEntry()

咱們繼續看 ThreadLocalMap.getEntry() 的源碼:

//java/lang/ThreadLocal.ThreadLocalMap

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
複製代碼

邏輯很簡單,hash 之後若是是 ThreadLocal 對應的 Entry 就返回,不然調用 getEntryAfterMiss 方法,根據線性探測法繼續查找,直到找到或對應 entry 爲 null,並返回。

因爲篇幅有限,更多細節不是本文討論的重點,感興趣的小夥伴能夠去查看源碼。

經過上面分析能夠看到 ThreadLocal 的工做原理以下:

ThreadLocal 工做原理

如圖所示,ThreadLocal 中有一個 ThreadLocalMap 其中以 ThreadLocal 做爲 Key,以須要保存的值做爲 Value。這樣不一樣的線程訪問同一個 ThreadLocal 時,獲取到的值也就是各個線程存儲時對應的值了。

ActivityThread

咱們已經分析了 Handler 的建立流程,也就下面代碼執行的過程:

private Handler handler = new Handler();
複製代碼

在 Handler 的構造方法中能夠看到:

//frameworks/base/core/java/android/os/Handler.java

/* 構造方法六 */
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;
}
複製代碼

若是 Looper.myLooper() 獲取到的 Looper 爲空就直接拋出異常了,可是咱們在 Activity 中建立 Handler 時並不會拋出異常。

這是由於 Activity 在建立過程當中已經調用了 Looper.prepareMainLooper() 源碼以下:

//frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
  SamplingProfilerIntegration.start();
  CloseGuard.setEnabled(false);
  Environment.initForCurrentUser();

  final File configDir = 
    Environment.getUserConfigDirectory(UserHandle.myUserId());
  TrustedCertificateStore.setDefaultUserDirectory(configDir);
  
  // 這裏調用了 prepareMainLooper() 方法
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  
  // 而後調用了 loop() 方法
  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼

咱們來看下 Looper.prepareMainLooper() 方法的具體實現。

Looper.prepareMainLooper()

在 Looper 類中還能夠看到一個 prepareMainLooper() 方法。

//frameworks/base/core/java/android/os/Looper.java

/* 初始化一個 main looper */
public static void prepareMainLooper() {
  prepare(false);
  synchronized (Looper.class) {
    if (sMainLooper != null) {
      throw new IllegalStateException("The main Looper has already been prepared.");
    }
    sMainLooper = myLooper();
  }
}
/* 返回 main looper */
public static Looper getMainLooper() {
  synchronized (Looper.class) {
    return sMainLooper;
  }
}
複製代碼

能夠看到 prepareMainLooper() 方法中首先調用了 prepare(false) 建立了一個不能夠退出的 Looper,而後檢查 MainLooper 是否已經建立,最後保存了一下 MainLooper 的引用。原來 prepareMainLooper() 中已經調用了 prepare() 方法。

Looper.loop()

繼續分析 Looper.loop() 方法。

//frameworks/base/core/java/android/os/Looper.java

public static void loop() {
  // 從 ThreadLocal 中取出當前線程的 Looper 對象
  final Looper me = myLooper();
  if (me == null) {
    // Looper 沒有調用 Looper.prepare() 進行初始化,拋出異常
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }
  // 從 Looper 對象中取出消息隊列
  final MessageQueue queue = me.mQueue;
  // ...
  for (;;) { // 死循環
    // 不斷的取出消息
    Message msg = queue.next();
    if (msg == null) { // 沒有消息直接返回
      return;
    }
    // ...
    try {
      // 取到消息,回調到 Handler 中的 dispatchMessage()
      msg.target.dispatchMessage(msg);
    } finally {
      // ...
    }
    // ...
    // 消息已經分發,進行回收操做
    msg.recycleUnchecked();
  }
}
複製代碼

能夠看到 Looper.loop() 就是不斷的從 MessageQueue 中取出消息,而後回調到 Handler.dispatchMessage() 來處理消息。

//frameworks/base/core/java/android/os/Handler.java

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg); // 處理 post 消息,稍後再分析
  } else {
    if (mCallback != null) {
      // 回調到 Handler.handleMessage() 方法
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}
複製代碼

能夠看到,最後回調到咱們最開始建立的 Handler 中了。

private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
    switch (msg.what) {
      case 0:
        //TODO: 處理消息
        break;
    }
  }
};
複製代碼

分析到這裏能夠看到 Handler 的大概工做原理以下:

Handler 基本流程

如圖所示,能夠看到咱們以前建立 Handler 以前其實已經作了兩步,第一步調用 Looper.prepare() 方法,建立 Looper 同時建立 MessageQueue 和 ThreadLocal。第二步調用 Looper.loop() 方法,不斷地讀取 MessageQueue 中的消息。第三步建立 Handler,Handler 的做用就是向 MessageQueue 中放入消息。

Handler.sendMessage()

咱們經常使用的發消息的方法以下:

//frameworks/base/core/java/android/os/Handler.java

public final boolean sendMessage(Message msg) {
  return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what) {
  return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
  Message msg = Message.obtain();
  msg.what = what;
  return sendMessageDelayed(msg, delayMillis);
}
複製代碼

能夠看到上面無論哪一種發消息的方式,最後都調用了 sendMessageDelayed() 方法。

//frameworks/base/core/java/android/os/Handler.java

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);
}

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

sendMessageDelayed() 方法最後調用了 MessageQueue.enqueueMessage()

MessageQueue.enqueueMessage()

咱們接着來看 enqueueMessage() 方法的實現:

//frameworks/base/core/java/android/os/MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
  if (msg.target == null) {
    throw new IllegalArgumentException("Message must have a target.");
  }
  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(TAG, e.getMessage(), e);
      msg.recycle();
      return false;
    }

    msg.markInUse();
    msg.when = when;
    Message p = mMessages;
    boolean needWake;
    if (p == null || when == 0 || when < p.when) {
      // 若是消息隊列裏面沒有消息,或者消息的執行時間比裏面的消息早,
      // 就把這條消息設置成第一條消息;
			// 通常不會出現這種狀況,由於系統必定會有不少消息。
      msg.next = p;
      mMessages = msg;
      needWake = mBlocked;
    } else {
      // 若是消息隊列裏面有消息
      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;
}
複製代碼

分析到這裏能夠看到,咱們經過調用 Handler.sendMessage() 最後將 Message 添加到了 MessageQueue 的消息隊列中。

在前面 Looper.loop() 方法中分析過,loop() 方法中有一個死循環一直在讀取消息,當讀取到剛纔添加的消息後會回調到 Handler.dispatchMessage() 方法。

到這裏 Handler 的工做流程你們應該已經很清楚了,以下圖所示:

Handler 工做流程

假設在 Thread 1 中建立了 Handler,那麼 Thread 2 向 Thread 1 發送消息的過程如上圖所示。Handler 機制就像是一個傳送機器,Looper 就是傳送輪一直在不停的旋轉,MessageQueue 就是傳送帶跟着Looper 旋轉來運輸 Message,Handler 就是機械手在 Thread 2 中將 Message 放到傳送帶 MessageQueue 上,傳送到 Thread 1 後再將 Message 拿下來通知 Thread 1 進行處理。

Handler.post()

瞭解了 Handler 工做流程,咱們繼續來分析下另外一種使用方式 Handler.post()

//frameworks/base/core/java/android/os/Handler.java

public final boolean post(Runnable r) {
  return  sendMessageDelayed(getPostMessage(r), 0);
}
複製代碼

能夠看到 post() 也是調用了 sendMessageDelayed() 方法,上面已經分析過了,這裏再也不贅述。咱們來看下 getPostMessage(r) 方法的實現。

//frameworks/base/core/java/android/os/Handler.java

private static Message getPostMessage(Runnable r) {
  Message m = Message.obtain();
  m.callback = r;
  return m;
}
複製代碼

原來這裏建立了一個 Message,將 Runnable 放入了 Message 的 callback 上。

那 Message 最後怎麼處理的呢?

在分析中 Looper.loop() 方法中有這麼一句 msg.target.dispatchMessage(msg);

//frameworks/base/core/java/android/os/Handler.java

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg); // 處理 post 消息,稍後再分析
  } else {
    if (mCallback != null) {
      // 回調到 Handler.handleMessage() 方法
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}
複製代碼

handleCallback() 就是處理 Handler.post() 發送的消息的。咱們接着看,見證奇蹟的時刻。

//frameworks/base/core/java/android/os/Handler.java

private static void handleCallback(Message message) {
  message.callback.run();
}
複製代碼

如此簡單,就是拿到 Runnable 調用了 run() 方法。

Looper 中死循環爲何不會致使應用卡死?

這個問題涉及到線程,先來講下進程與線程相關知識。

進程

首先每一個 App 都是運行在進程中的,進程由 Zygote 進程 fork 出來,進程承載了 App 上運行的個各類組件,如:Activity、Service 等。進程對於上層應用是徹底透明的,大多數狀況下一個 App 運行在一個進程中,其餘狀況暫不討論。

線程

線程在應用中十分常見,好比下面代碼:

new Thread(new Runnable() {
  
  @Override
  public void run() {
  
  }
}).start();
複製代碼

每次執行上面代碼都會建立一個線程。線程與當前 App 進程之間共享資源,從 Linux 系統角度來講進程與線程除了是否共享資源外,並無本質的區別,都是一個 task_struct 結構體,在CPU看來進程或線程無非就是一段可執行的代碼。

CPU 採用 CFS 調度算法,保證每一個 task 都儘量公平的享有 CPU 時間片。

死循環

對於線程來講,既然是一段可執行的代碼,當可執行的代碼執行完後,線程的生命週期就該終止了,線程也就退出。

而對於主線程,咱們是毫不但願運行一段時間本身就退出的。

那麼如何保證能一直存活呢?簡單的作法就是讓可執行的代碼一直執行下去,死循環就能夠保證不被退出。例如:loop() 方法中就是採用 for(;;) 死循環的方式。固然這裏並不是簡單的死循環,無消息時會休眠。

真正卡死的主線程的操做,是在生命週期回調方法 onCreate()、onStart()、onResume() 等中操做時間過長,會致使 UI 渲染掉幀,甚至 ANR。

loop()

若是僅僅使用死循環會一直佔用 CPU,致使 CPU 一直處於工做狀態。即便不會形成應用卡死,也會十分耗電。而事實上 loop() 中的死循環在沒有消息的狀況下是處於休眠狀態的,並無一直在運行。

//frameworks/base/core/java/android/os/Looper.java

public static void loop() {
  // ...
  for (;;) { // 死循環
    // 不斷的取出消息
    Message msg = queue.next();
    // ...
  }
}
複製代碼

在 loop() 方法中調用了 MessageQueue.next() 方法,咱們來看下這個方法的具體實現:

//frameworks/base/core/java/android/os/MessageQueue.java

Message next() {
  // ...
  for (;;) {
    // ...
    nativePollOnce(ptr, nextPollTimeoutMillis);
    // ...
  }
}
複製代碼

MessageQueue.next() 方法調用了 native 方法 nativePollOnce(),此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生時喚醒主線程。

原來這裏採用的是 epoll 機制,消息到達時經過往 pipe 管道寫端寫入數據來喚醒主線程工做。

任務切換

在介紹 epoll 機制以前先來了解下任務切換,操做系統爲了支持多任務,實現了進程調度的功能,會把進程分爲「運行」和「等待」等幾種狀態。

運行狀態是進程得到 CPU 使用權,正在執行代碼的狀態;等待狀態是阻塞狀態,進程會釋放 CPU 使用權,程序會從運行狀態變爲等待狀態,等接收到數據後變回運行狀態從新得到 CPU 使用權。

操做系統會分時執行各個運行狀態的進程,因爲速度很快,看上去就像是同時執行多個任務。

任務切換

如上圖,系統內核空間有兩個隊列,一個是運行隊列,一個是等待隊列。運行隊列存放的是正在執行的進程,等待隊列存放的是正在阻塞的進程。當接收到數據時,系統內核會喚醒等待隊列中須要執行的進程,將該進程移到運行隊列中;同理,當運行中的進程阻塞時,系統內核也會將進程移到等待隊列中。

從歷史發展角度看,必然會先出現一種不過高效的方法,人們再加以改進,最後留下來的纔是最優的方法。只有先理解了不過高效的方法,纔可以理解 epoll 的本質。

select 機制

select 機制的設計思路很簡單,假設進程 A 中同時監聽 socket 1 和 socket 2,那麼在調用 select 以後,操做系統會把進程 A 分別加入這兩個 socket 的等待隊列中。

select 機制

當任何一個 socket 收到數據後,中斷程序將喚醒進程 A。將進程 A 從等待隊列中移除,加入到工做隊列中。當進程 A 被喚醒後,它知道至少有一個 socket 接收了數據。只須要遍歷一遍 socket 列表,就能夠獲得就緒的 socket。

select 機制的缺點就是,每次喚醒進程都須要遍歷一遍等待隊列才能找到須要喚醒的進程,找到喚醒的進程後還須要遍歷一遍 socket 列表才能找到就緒的 socket。爲了 性能的考慮 Linux 中將 select 最大的監聽數量限制爲 1024 個,也就是 fd_set 列表的數量 fd_size 最大爲 1024。

poll 機制

因爲 select 機制的監聽數量最大爲 1024,poll 機制進行了升級使用 pollfd 替換 fd_set,pollfd 是鏈表結構這樣就沒有了數量限制,可是在數量過大後性能仍是會降低。

poll 機制

epoll 機制

epoll 是在 2.6 內核中提出的,是以前的 select 和 poll 的加強版本。相對於 select 和 poll 來講,epoll 更加靈活,沒有描述符限制。

epoll 機制

如圖所示,在使用 epoll 後內核中會建立一個 eventpoll 對象,eventpoll 對象中有 rdlist(就緒列表) 和 wq(等待隊列)。

假設內核中運行着進程 A 與進程 B,當進程 A 使用 epoll 機制時,會將進程 A 加入到 eventpoll 對象的 wq 等待隊列中。當 rdlist 爲空時阻塞等待隊列中進程 A,當 rdlist 不爲空時喚醒等待隊列中進程 A。由於有 rdlist 就序列表,進程 A 被喚醒後也能夠知道哪些 socket 發生了變化。

參考資料

個人 Github

github.com/jeanboydev/…

個人公衆號

歡迎關注個人公衆號,分享各類技術乾貨,各類學習資料,職業發展和行業動態。

Android 波斯灣

技術交流羣

歡迎加入技術交流羣,來一塊兒交流學習。

QQ 技術交流羣

QQ 技術交流羣
相關文章
相關標籤/搜索