在andorid中雨膨,系統(tǒng)的行為擂涛、用戶(hù)的輸入等事件都會(huì)被包裝為一個(gè)消息,
進(jìn)行消息發(fā)送聊记、處理
關(guān)于消息的處理撒妈,就離不開(kāi)Handler恢暖、Message、Loop
在平時(shí)使用時(shí)狰右,Handler多用于多線(xiàn)程之間通信杰捂。
- 那么Handler如何實(shí)現(xiàn)多線(xiàn)程通信?
- 多線(xiàn)程之間為何不會(huì)互相干擾棋蚌?
- 為什么不使用用wait/notify嫁佳?
Handler多線(xiàn)程通信
先看一下普通使用案例
public class MyActiivty extends Activity {
private Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what=100) {
// TODO
}
}
}
}
static class MyThread extends Thread() {
@Overtide
public void run() {
super.run();
Message message = Message.obtain();
message.what = 100;
myHandler.sendMessage();
}
}
}
上面就是一個(gè)簡(jiǎn)單的在子線(xiàn)程發(fā)送消息,回到主線(xiàn)程處理消息的過(guò)程附鸽,
通過(guò)在子線(xiàn)程構(gòu)造一個(gè)message對(duì)象脱拼,在主線(xiàn)程中獲取到該message對(duì)象,來(lái)處理消息坷备。
所以其實(shí)Handler處理多線(xiàn)程通信是通過(guò)共享Message對(duì)象內(nèi)存來(lái)實(shí)現(xiàn)的熄浓。
內(nèi)存是不區(qū)分線(xiàn)程的,這種通信原理就是在子線(xiàn)程與主線(xiàn)程共享message內(nèi)存
所以 那么Handler如何實(shí)現(xiàn)多線(xiàn)程通信省撑?
通過(guò) 內(nèi)存共享 實(shí)現(xiàn)赌蔑。
在多線(xiàn)程時(shí),Handler又是如何保證消息如何在正確的線(xiàn)程發(fā)送的呢竟秫,或者說(shuō)是如何保證執(zhí)行的線(xiàn)程是正確的了娃惯。
這就要引入我們的Loop、消息隊(duì)列概念了肥败。
handler處理消息模型:
<img src='../../../images/looper.png' style="zoom:20%" />
handler負(fù)責(zé)發(fā)送趾浅、處理消息
looper負(fù)責(zé)一直輪詢(xún)消息
messageQueue消息隊(duì)列,負(fù)責(zé)存放馒稍、取出消息
Looper
講到looper負(fù)責(zé)一直輪詢(xún)消息皿哨,但是好像在上面的代碼中,都沒(méi)有使用到looper纽谒。
其實(shí)是在主線(xiàn)程中证膨,系統(tǒng)已經(jīng)默認(rèn)為我們創(chuàng)建了looper,
在A(yíng)ctivityThread.java的main方法中(ActivityThread即為主線(xiàn)程)
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我們可以看到鼓黔,調(diào)用了Loop.prepareMainLooper()央勒、Looper.loop()函數(shù),
而且在Looper.loop()后面就拋出異常澳化,
也就是說(shuō)主線(xiàn)程中l(wèi)oop一旦停止輪詢(xún)崔步,則會(huì)拋出異常閃退。正常情況時(shí)缎谷,loop就是一直在輪詢(xún)刷晋。
查看Looper的這兩個(gè)函數(shù)
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
在prepareMainLooper中可以看到,不允許調(diào)用兩次,否則會(huì)拋出異常眼虱。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
loop函數(shù)中,首先獲取通過(guò)myLooper()函數(shù)獲取looper對(duì)象席纽,如果looper對(duì)象為空捏悬,則拋出異常,提示必須在當(dāng)前線(xiàn)程先執(zhí)行Looper.prepare()
然后獲取looper對(duì)象持有的messageQueue润梯,
然后就是for(;;)無(wú)限循環(huán)过牙,獲取messageQueue下一條消息
獲取到message后調(diào)用msg.target.dispatchMessage(msg);
將這條消息發(fā)送出去。
最后執(zhí)行msg.recycleUnchecked()纺铭,相當(dāng)于一個(gè)回收利用寇钉。
我們看一下myLooper函數(shù)
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
說(shuō)明looper是存放在ThreadLocal中的。
關(guān)于ThreadLocal舶赔,在之前已經(jīng)大致講過(guò)了扫倡。
ThreadLocal講解(https://wangchongwei.github.io/blog/2020/08/java-ThreadLocal%E8%A7%A3%E6%9E%90.html)
在每一個(gè)線(xiàn)程,都存在一個(gè)對(duì)應(yīng)且唯一的值
我們可以看一下prepare函數(shù)
public static void prepare() {
prepare(true);
}
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到與prepareMainLooper的不同竟纳,因?yàn)閜repareMainLooper是在主線(xiàn)程調(diào)用撵溃,而主線(xiàn)程很自由一個(gè),
所以直接使用sMainLooper來(lái)保存主線(xiàn)程的looper锥累,而且主線(xiàn)程中prepare(false)缘挑;標(biāo)示不允許looper退出。
而在子線(xiàn)程時(shí)桶略,就是將looper對(duì)象保存到sThreadLocal中语淘,sThreadLocal.get()不為null時(shí),會(huì)拋出異常际歼。
也就是說(shuō)子線(xiàn)程中prepare只允許調(diào)用一次惶翻,保證了每個(gè)線(xiàn)程中的looper對(duì)象唯一性
然后看到子線(xiàn)程和主線(xiàn)程的另一個(gè)差異prepare(false) && prepare(true)
因?yàn)閍ndorid,所有事件如:用戶(hù)的操作蹬挺、ui的渲染等都是作為消息發(fā)送的维贺,而這些都是在主線(xiàn)程操作的,所以在主線(xiàn)程中是不允許退出loop循環(huán)巴帮,否則拋出異常溯泣。
而在子線(xiàn)程中prepare(true),允許退出榕茧,其實(shí)在子線(xiàn)程中新建handler垃沦、looper時(shí),當(dāng)我們不需要再使用用押,需要終止loop循環(huán)肢簿。
此時(shí)需要調(diào)用:
public void quitSafely() {
mQueue.quit(true);
}
MessageQueue
在上面中已經(jīng)講過(guò)Looper,looper中持有一個(gè)messageQueue
final MessageQueue queue = me.mQueue;
mQueue 在Looper的私有構(gòu)造函數(shù)中被初始化
接下來(lái)我們看一下MessageQueue
隊(duì)列是一種數(shù)據(jù)結(jié)果,F(xiàn)IFO先進(jìn)先出
MessageQueue 是一個(gè)消息隊(duì)列池充,默認(rèn)也是先進(jìn)先出桩引,有序執(zhí)行
之前說(shuō)了,MessageQueue主要用于存放收夸、取出消息坑匠。
在Looper中主要用到了messagequeue的next函數(shù),用于取出下一條消息
我們先看一下存放消息
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) {
// 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;
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
如果當(dāng)前線(xiàn)程已經(jīng)退出卧惜,mQuitting為true厘灼,則拋出異常。
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}
當(dāng)全局變量mMessages為空咽瓷,或者當(dāng)前傳入的when為0设凹, 或者當(dāng)前when小于全局變量mMessages.when(即時(shí)間在前)
其實(shí)判斷的是兩種狀態(tài),1:messageQueue隊(duì)列為空 2:添加的消息執(zhí)行時(shí)間在前
此時(shí)將該消息置于隊(duì)首茅姜,
needWake = mBlocked闪朱;
如果mBlocked為true,needWake也為true匈睁,就是如果之前阻塞則喚醒监透,反之無(wú)需喚醒
再看不滿(mǎn)足上面情況下時(shí),即消息隊(duì)列中已添加過(guò)消息,而且要添加的消息.when在上一次添加的消息之后
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;
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;
}
for循環(huán)航唆,遍歷鏈表胀蛮,當(dāng)找到節(jié)點(diǎn)為null即遍歷完 || 傳入的when小于遍歷節(jié)點(diǎn)的when(即傳入消息的時(shí)間在遍歷節(jié)點(diǎn)時(shí)間之前時(shí))
終止循環(huán),將msg.next -> p
原來(lái)
prev.next -> n.next -> ... -> n.next -> p -> ...
現(xiàn)在
prev.next -> n.next -> ... -> n.next -> msg.next -> p -> ...
也就是說(shuō)糯钙,message鏈表是按照when排序的粪狼,when越小,在越靠近鏈頭
為何要根據(jù)when排序了任岸,其實(shí)是因?yàn)閙essage執(zhí)行時(shí)間是要按時(shí)間排序再榄,要執(zhí)行時(shí)間越小,代表時(shí)間越早享潜,所以放在鏈頭
以上是消息隊(duì)列,入隊(duì)函數(shù)剑按,再看一下出隊(duì)函數(shù)
@UnsupportedAppUsage
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();
}
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 {
// 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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
其中有一段代碼可以先不看艺蝴,
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());
}
這一塊涉及到消息的同步屏障猬腰,放到下面再講猜敢,我們先只看出隊(duì)時(shí)的邏輯
next函數(shù)就是取出下一條消息盒延。
開(kāi)啟for循環(huán)
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
如果nextPollTimeoutMillis不等于0時(shí),會(huì)阻塞鼠冕。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
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 {
// 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;
}
當(dāng)當(dāng)前message不為空時(shí):
如果當(dāng)前時(shí)間小于msg.when添寺,即沒(méi)到執(zhí)行時(shí)間畦贸,則阻塞線(xiàn)程到msg.when時(shí)間
將msg.next賦值給全局變量mMessages楞捂,再將msg.next指向null
然后返回msg這一個(gè)節(jié)點(diǎn)趋厉,如此不會(huì)返回一個(gè)鏈表
如果msg為空,說(shuō)明隊(duì)列為空君账,沒(méi)有消息乡数,此時(shí)賦值nextPollTimeoutMillis = -1;下一次循環(huán)時(shí),就會(huì)阻塞净赴。
MessageQueue 添加消息玖翅、取出消息是線(xiàn)程安全的嗎?
是金度,是線(xiàn)程安全的猜极。如何保證線(xiàn)程安全的?
通過(guò)鎖丢胚,存放消息以及取出消息時(shí)都有設(shè)置synchronized (this)酬姆,
synchronized 后面修飾的是this,同一個(gè)對(duì)象在多線(xiàn)程環(huán)境調(diào)用函數(shù)時(shí)骨宠,只會(huì)有一個(gè)線(xiàn)程獲取到鎖,進(jìn)行操作层亿。
synchronized 是內(nèi)置鎖,JVM已經(jīng)內(nèi)置處理了鎖的獲取以及釋放為什么不使用用wait/notify方灾?
在上述代碼可以看到使用了阻塞碌更、鎖,阻塞是直接調(diào)用native 函數(shù)來(lái)阻塞嘿棘,
其實(shí)在內(nèi)部已經(jīng)使用了wait/notif旭绒。
Message
上面講了消息機(jī)制中的Handler、Looper重父、MessageQueue忽匈;
現(xiàn)在我們?cè)僦v一下消息的本體Message
首先通過(guò)我們?cè)谏厦娴姆治觯梢灾繫essage在數(shù)據(jù)結(jié)構(gòu)上看歪沃,是一個(gè)鏈表嫌松,而且是只有next指針,所以是個(gè)單鏈表液走。
Message中沒(méi)有什么復(fù)雜操作贾陷,都是一些賦值函數(shù)
有兩個(gè)地址可以注意下
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
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的回收函數(shù)不是將對(duì)象置為空,而是將Message中的變量都還原為默認(rèn)值巷懈。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
Message還提供obtain函數(shù)顶燕,不會(huì)直接new 一個(gè)Message對(duì)象,而是共享之前的對(duì)象涌攻,改變對(duì)象的內(nèi)部屬性恳谎。
所以我們?cè)趯?shí)際使用中都是使用Message.obtain()來(lái)構(gòu)建message對(duì)象,而不是一直使用new 婚苹,這樣可以避免頻繁的生成鸵膏、回收,避免內(nèi)存抖動(dòng)。
這種設(shè)計(jì)被成為 * 享元設(shè)計(jì)模式 *
Message 同步屏障
上面講的消息message鏈表是根據(jù)when時(shí)間排序赞咙,那如果有緊急的消息必須馬上處理呢糟港,這個(gè)時(shí)候不可能等其他先執(zhí)行而必須是馬上執(zhí)行的事件時(shí),怎么辦速和?
這個(gè)時(shí)候就可以用到 同步屏障
我們可以看一下上面講到的代碼
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());
}
原理就是將一個(gè)消息狀態(tài)標(biāo)示符isAsynchronous設(shè)置為false剥汤,此時(shí)發(fā)送消息時(shí)吭敢,handler不會(huì)綁定到msg上,msg.target就為null欲低,
在for循環(huán)中首先對(duì)msg.target做檢測(cè)畜晰,當(dāng)msg對(duì)象不為空,而msg.target為空時(shí)腊瑟,此時(shí)就是message鏈表中有同步屏障,
此時(shí)不會(huì)依次按鏈表來(lái)取出消息魔策,而是通過(guò)遍歷取出message鏈表中的同步消息進(jìn)行處理河胎。
當(dāng)所有的同步消息都執(zhí)行完畢游岳,就會(huì)remove同步屏障,又會(huì)回到之前的按序取消息的方式胚迫。
設(shè)置與去除同步屏障的方法在MessageQueue類(lèi)中
// 設(shè)置同步屏障
postSyncBarrier()
// 去除同步屏障
removeSyncBarrier()
但這個(gè)兩個(gè)方法都是使用了hide注解访锻,我們是無(wú)法直接調(diào)用的,只能系統(tǒng)內(nèi)部使用河哑。
總結(jié)
handler消息機(jī)制大概流程:
生成Looper對(duì)象龟虎,生成Handler對(duì)象,Lopper.looper循環(huán)
在Handler構(gòu)造函數(shù)內(nèi)佳吞,獲取到上面生成的looper對(duì)象棉安,通過(guò)ThreadLocal保存到對(duì)應(yīng)的線(xiàn)程垂券,與MessageQueue綁定
在需要發(fā)送消息的地方調(diào)用handler.sendMessage(),在sendMessage時(shí),將message與handler綁定算芯,將message.target賦值為當(dāng)前handler
同時(shí)凳宙,sendMessage時(shí),調(diào)用messageQueue.enqueueMessage將message放入消息隊(duì)列届囚。
同時(shí),Looper.loop()在循環(huán)一直取出消息message泥耀,然后通過(guò)message.target獲取到handler對(duì)象痰催,最終回調(diào)到handler.handlerMessage函數(shù)迎瞧。
這樣消息從產(chǎn)生到處理流程就走完了。
總結(jié)提問(wèn):
- Looper.loop()一直在循環(huán)缝裁,為什么不會(huì)導(dǎo)致應(yīng)用卡死(ANR)?
答:loop()循環(huán)與ANR是兩個(gè)不相關(guān)的事情足绅,loop只是循環(huán)事件,ANR是處理事件耗時(shí)胎食,導(dǎo)致無(wú)法響應(yīng)用戶(hù)的下一次輸入。
系統(tǒng)的ANR彈窗都是通過(guò)消息機(jī)制發(fā)送衩匣,并彈出提示窗的琅捏。