1. 前言
安卓在子線程中不能更新UI,所以大部分情況下,我們需要借助Handler切換到主線程中去更新消息.而消息機(jī)制(即Handler那一坨)在安卓中的地位非常非常重要,我們需要詳細(xì)了解其原理.這一塊,學(xué)過(guò)很多次,但是,我覺(jué)得還是再學(xué)億次,寫(xiě)成博客輸出.希望對(duì)大家有所幫助,有一些新的感悟.
2. ThreadLocal工作原理
ThreadLocal主要是可以在不同的線程中存儲(chǔ)不同的數(shù)據(jù),它是將數(shù)據(jù)存儲(chǔ)在線程內(nèi)部的,其他線程無(wú)法訪問(wèn).對(duì)于同一個(gè)ThreadLocal對(duì)象,不同的線程有不同的數(shù)據(jù),這些數(shù)據(jù)互不干擾.比如Handler機(jī)制中的Looper,Looper的作用域是線程,ThreadLocal可以將Looper存儲(chǔ)在線程中,然后其他線程是無(wú)法訪問(wèn)到這個(gè)線程中的Looper的,只供當(dāng)前線程自己內(nèi)部使用.
2.1 ThreadLocal demo
下面簡(jiǎn)單舉個(gè)例子:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final ThreadLocal<Integer> INTEGER_THREAD_LOCAL = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//設(shè)置ThreadLocal里面的數(shù)據(jù)為1
INTEGER_THREAD_LOCAL.set(1);
//獲取ThreadLocal里面的數(shù)據(jù)
Log.w(TAG, "主線程" + INTEGER_THREAD_LOCAL.get());
new Thread(new Runnable() {
@Override
public void run() {
//獲取ThreadLocal里面的數(shù)據(jù),但是需要注意的是,這里獲取的數(shù)據(jù)是子線程中數(shù)據(jù),因?yàn)闆](méi)有進(jìn)行初始化,這里獲取到的數(shù)據(jù)是null
Log.w(TAG, "線程1 " + INTEGER_THREAD_LOCAL.get());
}
}, "線程1").start();
}
}
我先在主線程中將INTEGER_THREAD_LOCAL
的值設(shè)置為1(相當(dāng)于主線程中的INTEGER_THREAD_LOCAL
值為1),然后再開(kāi)啟子線程并在子線程中獲取INTEGER_THREAD_LOCAL
的值.因?yàn)樽泳€程中沒(méi)有給INTEGER_THREAD_LOCAL
附值,所以是null.
2019-05-19 11:12:54.353 12364-12364/com.xfhy.handlerdemo W/MainActivity: 主線程1
2019-05-19 11:12:54.353 12364-12383/com.xfhy.handlerdemo W/MainActivity: 線程1 null
需要注意到的是INTEGER_THREAD_LOCAL
是final static
的,這里的ThreadLocal是同一個(gè)對(duì)象,但是在主線程中獲取到的數(shù)據(jù)和在子線程中獲取到的數(shù)據(jù)卻不一樣. 這里的demo也就證明了: ThreadLocal在不同的線程中存儲(chǔ)的數(shù)據(jù),互不干擾,相互獨(dú)立.
2.2 ThreadLocal源碼理解
我們從ThreadLocal的set方法開(kāi)始深入下去(一般讀源碼是從使用處的API開(kāi)始,這樣會(huì)更輕松地理清思路)
public void set(T value) {
//1. 獲取當(dāng)前線程
Thread t = Thread.currentThread();
//2. 獲取當(dāng)前線程的threadLocals屬性,threadLocals是Thread類(lèi)里面的一個(gè)屬性,是ThreadLocalMap類(lèi)型的,專(zhuān)門(mén)用來(lái)存當(dāng)前線程的私有數(shù)據(jù),這些數(shù)據(jù)由ThreadLocal維護(hù)
ThreadLocalMap map = getMap(t);
//3. 第一次設(shè)置值的時(shí)候map肯定是為null的,初始化了之后map才不為null
//第一次會(huì)去createMap()
if (map != null)
//4. 將當(dāng)前ThreadLocal對(duì)象和value的值存入map中
map.set(this, value);
else
//4. 這里將初始化map,并且將value值放到map中.
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal在設(shè)置數(shù)據(jù)的時(shí)候,首先是獲取當(dāng)前線程的threadLocals屬性,threadLocals是Thread類(lèi)里面的一個(gè)屬性,是ThreadLocalMap類(lèi)型的,專(zhuān)門(mén)用來(lái)存當(dāng)前線程的私有數(shù)據(jù),這些數(shù)據(jù)由ThreadLocal來(lái)維護(hù)的. 當(dāng)?shù)谝淮卧O(shè)置值的時(shí)候,需要初始化map,并將value值放入map中.下面來(lái)看一下這部分代碼
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//下面是ThreadLocalMap的代碼
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
* table是ThreadLocalMap里面存儲(chǔ)數(shù)據(jù)的地方,如果在數(shù)組長(zhǎng)度不夠用的時(shí)候,會(huì)擴(kuò)容.
存儲(chǔ)的方式是靠hash值為數(shù)組的索引,將value放到該索引處.
*/
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化table數(shù)據(jù)數(shù)組
table = new Entry[INITIAL_CAPACITY];
//計(jì)算hash值->存儲(chǔ)數(shù)據(jù)的索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//將value值存入map中,key為T(mén)hreadLocal
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
}
可以看到createMap方法中就是初始化ThreadLocalMap,而ThreadLocalMap的底部其實(shí)是一個(gè)數(shù)組,它是利用hash值來(lái)計(jì)算索引,然后存儲(chǔ)數(shù)據(jù)到該索引處的方式.
此處需要注意的是,我們可以看到ThreadLocal是將數(shù)據(jù)存儲(chǔ)到Thread的一個(gè)threadLocals屬性上面,這個(gè)threadLocals每個(gè)線程獨(dú)有的,那么存儲(chǔ)數(shù)據(jù)肯定互不干擾啊,完美.
3. MessageQueue 消息隊(duì)列
Handler中的消息隊(duì)列,也就是MessageQueue.從名字可以看出這是一個(gè)隊(duì)列,但是它的底層卻是單鏈表結(jié)構(gòu).因?yàn)殒湵斫Y(jié)構(gòu)比較適合插入和刪除操作.這個(gè)MessageQueue的查詢(xún)就是next()方法,它的查詢(xún)伴隨著刪除.
3.1 消息隊(duì)列插入
消息隊(duì)列的插入,對(duì)應(yīng)著的是enqueueMessage方法
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) {
....
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果 1. 鏈表為空 || 2. when是0,表示立即需要處理的消息 || 3. 當(dāng)前需要插入的消息比之前的第一個(gè)消息更緊急,在更短的時(shí)間內(nèi)就需要處理
//滿(mǎn)足上面這3個(gè)條件中的其中一個(gè),那么就是插入在鏈表的頭部
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//從頭部開(kāi)始,直到找出列表的最后一個(gè)元素,方便鏈表插入
for (;;) {
prev = p;
p = p.next;
//找到合適的時(shí)間點(diǎn),插入到這里
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//把新的消息插入在鏈表尾部
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 激活消息隊(duì)列去獲取下一個(gè)消息 這里是一個(gè)native方法
nativeWake(mPtr);
}
}
return true;
}
核心內(nèi)容為消息列表的插入,也就是鏈表的插入,插入數(shù)據(jù)的時(shí)候是有一定規(guī)則的,當(dāng)滿(mǎn)足下面這3個(gè)條件中的其中一個(gè),那么就是插入在鏈表的頭部
- 鏈表為空
- when是0,表示立即需要處理的消息
- 當(dāng)前需要插入的消息比之前的第一個(gè)消息更緊急,在更短的時(shí)間內(nèi)就需要處理
其他情況則是插入在鏈表中的合適的位置,找到一個(gè)合適的時(shí)間點(diǎn).
3.2 消息隊(duì)列查詢(xún)(next)
MessageQueue的next方法,也就是獲取下一個(gè)消息,這個(gè)方法可能會(huì)阻塞,當(dāng)消息隊(duì)列沒(méi)有消息的時(shí)候.直到有消息,然后就會(huì)被喚醒,然后繼續(xù)取消息.
但是這里的阻塞是不會(huì)ANR的,真正導(dǎo)致ANR的是因?yàn)樵趆andleMessage方法中處理消息時(shí)阻塞了主線程太久的時(shí)間.這里的原因,后面再解釋.
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//當(dāng)消息隊(duì)列為空時(shí),這里會(huì)導(dǎo)致阻塞,直到有消息加入消息隊(duì)列,才會(huì)恢復(fù)
//這里是native方法,利用的是linux的epoll機(jī)制阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//這里比較關(guān)鍵 取鏈表頭部,獲取這個(gè)消息
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
.....
}
......
}
}
核心內(nèi)容就是取消息隊(duì)列的第一個(gè)元素(即鏈表的第一個(gè)元素),然后將該Message取出來(lái)之后,將它從消息隊(duì)列中刪除.
4. Looper
Looper在消息機(jī)制中主要扮演著消息循環(huán)的角色,有消息來(lái)了,Looper就取出來(lái),分發(fā).沒(méi)有消息,Looper就阻塞在那里,直到有消息為止.
4.1 Looper初始化
先來(lái)看一下,Looper的構(gòu)造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
這個(gè)構(gòu)造方法是私有化的,只能在內(nèi)部調(diào)用,直接在里面初始化了MessageQueue和獲取當(dāng)前線程.構(gòu)造方法只會(huì)在prepare方法中被調(diào)用.
public static void prepare() {
prepare(true);
}
//sThreadLocal是用`static final`修飾的,意味著sThreadLocal只有一個(gè),但是它卻可以在不同的線程中存儲(chǔ)不同的Looper,妙啊
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
//如果說(shuō)當(dāng)前線程之前初始化過(guò)ThreadLocal,里面有Looper,那么就報(bào)錯(cuò)
//意思就是prepare方法只能調(diào)用一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//初始化ThreadLocal,將一個(gè)Looper存入其中
sThreadLocal.set(new Looper(quitAllowed));
}
private static Looper sMainLooper;
//這個(gè)方法是主線程中調(diào)用的,準(zhǔn)備主線程的Looper.也是只能調(diào)用一次.
public static void prepareMainLooper() {
//先準(zhǔn)備一下
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//將初始化之后的Looper賦值給sMainLooper,sMainLooper是static的,可能是為了方便使用吧
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
prepare方法的職責(zé)是初始化ThreadLocal,將Looper存儲(chǔ)在其中,一個(gè)線程只能有一個(gè)Looper,不能重復(fù)初始化.sThreadLocal是用static final
修飾的,意味著sThreadLocal只有一個(gè),但是它卻可以在不同的線程中存儲(chǔ)不同的Looper.而且官方還提供了主線程初始化Looper的專(zhuān)用方法prepareMainLooper.主線程就是主角,還單獨(dú)把它的Looper存到靜態(tài)的sMainLooper中.
4.2 Looper#loop
下面開(kāi)始進(jìn)入Looper的核心方法loop(),我們知道loop方法就是死循環(huán)不斷得從MessageQueue中去取數(shù)據(jù).看看方法中的一些細(xì)節(jié).
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//1. 首先是獲取當(dāng)前線程的Looper 穩(wěn),不同的線程,互不干擾
final Looper me = myLooper();
//2. 如果當(dāng)前線程沒(méi)有初始化,那肯定是要報(bào)錯(cuò)的
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//3. 取出當(dāng)前線程Looper中存放的MessageQueue
final MessageQueue queue = me.mQueue;
.....
for (;;) {
//4. 從MessageQueue中取消息,當(dāng)然 這里是可能被阻塞的,如果MessageQueue中沒(méi)有消息可以取的話
Message msg = queue.next(); // might block
//5. 如果消息隊(duì)列想退出,并且MessageQueue中沒(méi)有消息了,那么這里的msg肯定是null
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
.....
//6. 注意啦,這里開(kāi)始分發(fā)當(dāng)前從消息隊(duì)列中取出來(lái)的消息
msg.target.dispatchMessage(msg);
......
}
}
loop方法非常重要,它首先取到當(dāng)前線程的Looper,再?gòu)腖ooper中獲取MessageQueue,開(kāi)啟一個(gè)死循環(huán),從MessageQueue的next方法中獲取新的Message.但是在next方法調(diào)用的過(guò)程中是可能被阻塞的,這里是利用了linux的epoll機(jī)制.取到了消息之后分發(fā)下去.分發(fā)給Handler的handleMessage方法進(jìn)行處理. 然后又開(kāi)始了一個(gè)新的輪回,繼續(xù)取新的消息(也可能是阻塞在那里等).
下面來(lái)看一下消息的分發(fā)
//Message里面的代碼
//Message里的target其實(shí)就是發(fā)送該消息的那個(gè)Handler,666
Handler target;
//下一個(gè)消息的引用
Message next;
//Handler里面的代碼
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
兄弟萌,它來(lái)啦,還是那個(gè)熟悉的handleMessage方法,在Looper的loop方法中由Message自己通過(guò)Message里面的target(handler)調(diào)用該Handler自己的handleMessage方法.完成了消息的分發(fā). 如果這里有Callback的話,就通過(guò)Callback接口分發(fā)消息.
5. Handler
Handler的作用其實(shí)就是發(fā)送消息,然后接收消息.Handler中任何的發(fā)送消息的方法最后都會(huì)調(diào)用sendMessageAtTime方法,我們仔細(xì)觀摩一下
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);
}
sendMessageAtTime方法很簡(jiǎn)單,其實(shí)就是將消息插入MessageQueue.而在Message插入MessageQueue的過(guò)程之前,先將Handler的引用存入Message中,方便待會(huì)兒分發(fā)消息事件,機(jī)智機(jī)智!
6. 用一句話總結(jié)一下安卓的消息機(jī)制
在安卓消息機(jī)制中,ThreadLocal拿來(lái)存儲(chǔ)Looper,而MessageQueue是存儲(chǔ)在Looper中的.所以我們可以在子線程中通過(guò)主線程的Handler發(fā)送消息,而Looper(主線程中的)在主線程中取出消息,分發(fā)給主線程的Handler的handleMessage方法.
7. 消息機(jī)制在主線程中的應(yīng)用
7.1 關(guān)于主線程中的死循環(huán)
我們知道ActivityThread其實(shí)就是我們的主線程,首先我們來(lái)看一段代碼,ActivityThread的main方法:
public static void main(String[] args) {
......
//注意看,在main方法的開(kāi)始,在主線程中就準(zhǔn)備好了主線程中的Looper,存入ThreadLocal中.所以我們平時(shí)使用Handler的時(shí)候并沒(méi)有調(diào)用prepare方法也不會(huì)報(bào)錯(cuò)
Looper.prepareMainLooper();
......
//直接在主線程中調(diào)用了loop方法,并且陷入死循環(huán)中,不斷地取消息,不斷地處理消息,無(wú)消息時(shí)就阻塞.
//嘿,你還別說(shuō),這里這個(gè)方法還必須要死循環(huán)下去才好,不然就會(huì)執(zhí)行到下面的throw new RuntimeException語(yǔ)句報(bào)出錯(cuò)誤
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主線程一直處在一個(gè)Looper的loop循環(huán)中,有消息就會(huì)去處理.無(wú)消息,則阻塞.
7.2 主線程死循環(huán)到底是要接收和處理什么消息?
有什么騷東西非要進(jìn)行死循環(huán)才能處理呢?首先我們想想,既然ActivityThread開(kāi)啟了Looper的loop,那么肯定有Handler來(lái)接收和處理消息,我們一探究竟:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING = 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
...
}
名場(chǎng)面,上面就是API 28以前ActivityThread.H的老樣子,為什么是API 28以前?因?yàn)樵贏PI 28中重構(gòu)了H類(lèi),把100到109這10個(gè)用于Activity的消息,都合并為159這個(gè)消息政恍,消息名為EXECUTE_TRANSACTION(抽象為ClientTransactionItem,有興趣了解的看這里)采章。
在H類(lèi)中定義了很多消息類(lèi)型,包含了安卓四大組件的啟動(dòng)和停止.ActivityThread通過(guò)ApplicationThread與AMS進(jìn)行進(jìn)程間通信,AMS完成ActivityThread的請(qǐng)求后會(huì)回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會(huì)向H發(fā)送消息,H收到消息就開(kāi)始在主線程中執(zhí)行,開(kāi)始執(zhí)行諸如Activity的啟動(dòng)停止等動(dòng)作,以上就是主線程的消息循環(huán)模型.
既然我們知道了主線程是這樣啟動(dòng)Activity的,那么我們是不是可以搞點(diǎn)騷操作???俗稱(chēng)黑科技的插件化:我們Hook掉H類(lèi)的mCallback對(duì)象,攔截這個(gè)對(duì)象的handleMessage方法。在此之前陈症,我們把插件中的Activity替換為StubActtivty逛薇,那么現(xiàn)在狼犯,我們攔截到handleMessage方法凡纳,再把StubActivity換回為插件中的Activity.當(dāng)前這只是API 28之前的操作,更多詳情請(qǐng)看這里
8. 主線程為什么沒(méi)有被loop阻塞
既然主線程中的main方法內(nèi)調(diào)用了Looper的loop方法不斷地死循環(huán)取消息,而且當(dāng)消息隊(duì)列為空的時(shí)候還會(huì)被阻塞.那為什么主線程中當(dāng)沒(méi)有消息的時(shí)候怎么不卡呢?
此處引出一國(guó)外網(wǎng)友的回答,短小精湛.問(wèn)題回答原地址
簡(jiǎn)短版答案:
nativePollOnce方法是用來(lái)等待下一個(gè)消息可用時(shí)的,下一個(gè)消息可用則不會(huì)再繼續(xù)阻塞,如果在這個(gè)調(diào)用中花費(fèi)的時(shí)間很長(zhǎng)窃植,那你的主(UI)線程沒(méi)有真正的工作要做,并且等待下一個(gè)事件處理荐糜。沒(méi)必要擔(dān)心阻塞問(wèn)題巷怜。
完整版的答案:
因?yàn)橹骶€程負(fù)責(zé)繪制UI和處理各種事件,所以Runnable有一個(gè)處理所有這些事件的循環(huán)暴氏。循環(huán)由Looper管理延塑,其工作非常簡(jiǎn)單:它處理MessageQueue中的所有消息。消息被添加到隊(duì)列中答渔,例如響應(yīng)輸入事件关带,幀渲染回調(diào)甚至您自己的Handler.post調(diào)用。有時(shí)主線程沒(méi)有工作要做(即隊(duì)列中沒(méi)有消息)研儒,這可能發(fā)生在例如剛完成渲染單幀后(線程剛剛繪制了一幀并準(zhǔn)備好下一幀豫缨,只需等待一段時(shí)間)。 MessageQueue類(lèi)中的兩個(gè)Java方法對(duì)我們來(lái)說(shuō)很有趣:Message next()和boolean enqueueMessage(Message端朵,long)好芭。消息next(),顧名思義冲呢,接收并返回隊(duì)列中的下一條消息舍败。如果隊(duì)列為空(并且沒(méi)有任何內(nèi)容可以返回),則該方法調(diào)用native void nativePollOnce(long敬拓,int)邻薯,該塊將阻塞,直到添加新消息乘凸。此時(shí)你可能會(huì)問(wèn)nativePollOnce如何知道何時(shí)醒來(lái)厕诡。這是一個(gè)非常好的問(wèn)題。將Message添加到隊(duì)列時(shí)营勤,框架會(huì)調(diào)用enqueueMessage方法灵嫌,該方法不僅會(huì)將消息插入隊(duì)列,還會(huì)調(diào)用native static void nativeWake(long)葛作,如果需要喚醒隊(duì)列的話寿羞。 nativePollOnce和nativeWake的核心魔力發(fā)生在native(實(shí)際上是C ++)代碼中。 Native MessageQueue使用名為epoll的Linux系統(tǒng)調(diào)用赂蠢,該調(diào)用允許監(jiān)視IO事件的文件描述符绪穆。 nativePollOnce在某個(gè)文件描述符上調(diào)用epoll_wait
,而nativeWake寫(xiě)入描述符虱岂,這是IO操作之一玖院,epoll_wait
等待。然后內(nèi)核從等待狀態(tài)中取出epoll等待線程第岖,并且線程繼續(xù)處理新消息司恳。如果您熟悉Java的Object.wait()和Object.notify()方法,您可以想象nativePollOnce是Object.wait()和NativeWake for Object.notify()的粗略等價(jià)物绍傲,因?yàn)樗鼈兊膶?shí)現(xiàn)完全不同:nativePollOnce使用epoll扔傅,Object.wait()使用futex Linux調(diào)用。值得注意的是烫饼,nativePollOnce和Object.wait()都不會(huì)浪費(fèi)CPU周期猎塞,因?yàn)楫?dāng)線程進(jìn)入任一方法時(shí),它會(huì)因線程調(diào)度而被禁用杠纵。如果這些方法實(shí)際上浪費(fèi)了CPU周期荠耽,那么所有空閑應(yīng)用程序?qū)⑹褂?00%的CPU,加熱并降低設(shè)備的速度比藻。
翻譯的不是很好,英語(yǔ)好的同學(xué)還是看原版吧,,,,,,,,,