在 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() {
// 運行在子線程中...
}
});
複製代碼
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 中的這兩個方法。編程
//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
//frameworks/base/core/java/android/os/Looper.java
/* Handler 構造方法六中調用的方法 */
public static Looper myLooper() {
// 返回當前線程中的 looper
return sThreadLocal.get();
}
複製代碼
能夠看到 myLooper()
邏輯很簡單,調用了 ThreadLocal 的 get() 方法。ThreadLocal 咱們稍後再分析。數組
在 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 的具體實現。
//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 以前須要先調用 Looper.prepare(),該方法會初始化 Looper,建立 MessageQueue 和 ThreadLocal。
第二步當咱們建立 Handler 時會調用 Looper 中的 myLoop() 方法獲取到 Looper 和 MessageQueue 保存到 Handler 中。
咱們如今來分析下 ThreadLocal 的做用。
ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal.set(new Looper(quitAllowed)); // 設置變量信息
sThreadLocal.get(); // 讀取變量信息
複製代碼
ThreadLocal 提供了線程本地變量,它能夠保證訪問到的變量屬於當前線程,每一個線程都保存有一個變量副本,每一個線程的變量都不一樣,而同一個線程在任什麼時候候訪問這個本地變量的結果都是一致的。
ThreadLocal 至關於提供了一種線程隔離,將變量與線程相綁定。而當線程結束生命週期時,全部的線程本地實例都會被 GC 回收掉。一般 ThreadLocal 定義爲 private static 類型。
接下來分析下 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。
咱們來看看 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.ThreadLocalMap
與 ThreadLocal
相綁定,這樣能夠確保每一個線程訪問到的 ThreadLocal 變量都是本線程的。
咱們往下繼續分析。獲取了 ThreadLocalMap 實例之後,若是它不爲空則調用 ThreadLocalMap.ThreadLocalMap.set() 方法設值;若爲空則調用 ThreadLocal.createMap() 方法 new 一個 ThreadLocalMap 實例並賦給 Thread.threadLocals。
下面咱們分析一下 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 發生衝突的次數減少。
接下來咱們來看 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 的結構上推斷出來。
咱們繼續看 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 中有一個 ThreadLocalMap 其中以 ThreadLocal 做爲 Key,以須要保存的值做爲 Value。這樣不一樣的線程訪問同一個 ThreadLocal 時,獲取到的值也就是各個線程存儲時對應的值了。
咱們已經分析了 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()
方法。
//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()
方法。
//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 以前其實已經作了兩步,第一步調用 Looper.prepare() 方法,建立 Looper 同時建立 MessageQueue 和 ThreadLocal。第二步調用 Looper.loop() 方法,不斷地讀取 MessageQueue 中的消息。第三步建立 Handler,Handler 的做用就是向 MessageQueue 中放入消息。
咱們經常使用的發消息的方法以下:
//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()
。
咱們接着來看 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 的工做流程你們應該已經很清楚了,以下圖所示:
假設在 Thread 1 中建立了 Handler,那麼 Thread 2 向 Thread 1 發送消息的過程如上圖所示。Handler 機制就像是一個傳送機器,Looper 就是傳送輪一直在不停的旋轉,MessageQueue 就是傳送帶跟着Looper 旋轉來運輸 Message,Handler 就是機械手在 Thread 2 中將 Message 放到傳送帶 MessageQueue 上,傳送到 Thread 1 後再將 Message 拿下來通知 Thread 1 進行處理。
瞭解了 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()
方法。
這個問題涉及到線程,先來講下進程與線程相關知識。
首先每一個 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。
若是僅僅使用死循環會一直佔用 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 機制的設計思路很簡單,假設進程 A 中同時監聽 socket 1 和 socket 2,那麼在調用 select 以後,操做系統會把進程 A 分別加入這兩個 socket 的等待隊列中。
當任何一個 socket 收到數據後,中斷程序將喚醒進程 A。將進程 A 從等待隊列中移除,加入到工做隊列中。當進程 A 被喚醒後,它知道至少有一個 socket 接收了數據。只須要遍歷一遍 socket 列表,就能夠獲得就緒的 socket。
select 機制的缺點就是,每次喚醒進程都須要遍歷一遍等待隊列才能找到須要喚醒的進程,找到喚醒的進程後還須要遍歷一遍 socket 列表才能找到就緒的 socket。爲了 性能的考慮 Linux 中將 select 最大的監聽數量限制爲 1024 個,也就是 fd_set 列表的數量 fd_size 最大爲 1024。
因爲 select 機制的監聽數量最大爲 1024,poll 機制進行了升級使用 pollfd 替換 fd_set,pollfd 是鏈表結構這樣就沒有了數量限制,可是在數量過大後性能仍是會降低。
epoll 是在 2.6 內核中提出的,是以前的 select 和 poll 的加強版本。相對於 select 和 poll 來講,epoll 更加靈活,沒有描述符限制。
如圖所示,在使用 epoll 後內核中會建立一個 eventpoll 對象,eventpoll 對象中有 rdlist(就緒列表) 和 wq(等待隊列)。
假設內核中運行着進程 A 與進程 B,當進程 A 使用 epoll 機制時,會將進程 A 加入到 eventpoll 對象的 wq 等待隊列中。當 rdlist 爲空時阻塞等待隊列中進程 A,當 rdlist 不爲空時喚醒等待隊列中進程 A。由於有 rdlist 就序列表,進程 A 被喚醒後也能夠知道哪些 socket 發生了變化。
歡迎關注個人公衆號,分享各類技術乾貨,各類學習資料,職業發展和行業動態。
歡迎加入技術交流羣,來一塊兒交流學習。