本文主要三點(diǎn)目的
第一.面試18連問(wèn)
第二.epoll機(jī)制源碼三分鐘搞定
第三.手寫(xiě)可以跨進(jìn)程的Handler通信方案断序,沒(méi)錯(cuò)跨進(jìn)程。既可以線(xiàn)程通信荒澡,又可以跨進(jìn)程通信**
面試連環(huán)18連問(wèn):
1.Handler被設(shè)計(jì)出來(lái)的原因?有什么用?
2.Handler,Message,MessageQueue,Looper的關(guān)系描焰,一對(duì)多。相互是如何創(chuàng)建的栅螟?
3.Handler,Message,MessageQueue,Looper的作用荆秦,一個(gè)流程
4.消息如何存放的?消息如何消費(fèi)的力图?
5.Handler:為什么能切換線(xiàn)程
Message:什么數(shù)據(jù)結(jié)構(gòu)步绸?對(duì)象池?
MessageQueue:什么數(shù)據(jù)結(jié)構(gòu)?
Looper:如何保證唯一
6.為什么死循環(huán)不會(huì)ANR---Looper
7.延時(shí)消息如何處理的-------messageeQueue
8.喚醒和阻塞吃媒,Epoll機(jī)制是如何瓤介?
9.內(nèi)存泄漏的鏈路
10.消息屏障是干嘛的?
11.IdleHandler是啥赘那?有什么使用場(chǎng)景刑桑?
1.Handler基本使用
一般情況下,在主線(xiàn)程中我們綁定了Handler募舟,并在事件觸發(fā)上面創(chuàng)建新的線(xiàn)程用于完成某些耗時(shí)的操作祠斧,當(dāng)子線(xiàn)程中的工作完成之后,會(huì)對(duì)Handler發(fā)送一個(gè)完成的信號(hào)胃珍,而Handler接收到信號(hào)后梁肿,就進(jìn)行主UI界面的更新操作蜓陌。
Handler被設(shè)計(jì)出來(lái)的原因?有什么用吩蔑?
Handler機(jī)制主要為了解決以下2個(gè)問(wèn)題
- 不要阻塞UI線(xiàn)程钮热;
- 不要在UI線(xiàn)程之外訪問(wèn)UI組件,即不能在子線(xiàn)程訪問(wèn)UI組件烛芬,只能在UI線(xiàn)程訪問(wèn)隧期。
第一步: 線(xiàn)程:先把線(xiàn)程構(gòu)建出來(lái)
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Menlo; background-color: rgb(43, 43, 43);">new Thread(){
@Override
public void run() {
Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop();
}
}.start();</pre>
2、Looper
問(wèn)題: Looper是怎么創(chuàng)建的赘娄?
在線(xiàn)程里面創(chuàng)建的仆潮。然后保存在ThreadLocal中。
對(duì)于Looper主要是prepare()和loop()兩個(gè)方法遣臼。
首先看prepare()方法:主要是通過(guò)ThreadLocal綁定Looper
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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)); }</pre>
同一個(gè)線(xiàn)程中不能多次調(diào)用prepare方法性置,否則會(huì)拋出異常。如上面
ThreadLocal :在Looper中構(gòu)建出來(lái)的
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">public final class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();</pre>
ThreadLocal是一個(gè)靜態(tài)的揍堰,注意鹏浅。而且是final。final具體使用
https://blog.csdn.net/mysimplelove/article/details/79568261
ThreadLocal保證了一個(gè)線(xiàn)程中只有一個(gè)Looper實(shí)例
ThreadLocal里面是一個(gè)map屏歹,存放key和value
ThreadLocal的作用:在線(xiàn)程里面保存數(shù)據(jù)
線(xiàn)程是默認(rèn)沒(méi)有Looper的隐砸,線(xiàn)程需要通過(guò)Looper.prepare()、綁定Handler到Looper對(duì)象蝙眶、
Looper.loop()來(lái)建立消息循環(huán)looper相當(dāng)于事件驅(qū)動(dòng)季希,心跳機(jī)制!
- MessageQueue: MessageQueue是怎么創(chuàng)建出來(lái)的幽纷?
通過(guò)looper
MessagerQueue:單鏈表的數(shù)據(jù)結(jié)構(gòu)
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); }</pre>
在構(gòu)造方法中式塌,創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)。
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); }</pre>
Loop方法:里面一個(gè)For循環(huán)友浸,阻塞隊(duì)列珊搀,不斷的取消息
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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(); 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);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}</pre>
looper方法:調(diào)用dispatchMessager方法:dispatchMessager方法再調(diào)用handlemessage方法
msg.target:就是Handler對(duì)象∥补剑看到message境析,持有handler的引用
Message 源碼:Message的數(shù)據(jù)結(jié)構(gòu)
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;">public final class Message implements Parcelable { <pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas, monospace;"> @UnsuportedAppUsage
/package/ Handler target;</pre> </pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;"> public int what; public int arg2; // sometimes we store linked lists of these things
/package/ Message next;</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}</pre>
Looper主要作用:
1、 與當(dāng)前線(xiàn)程綁定派诬,保證一個(gè)線(xiàn)程只會(huì)有一個(gè)Looper實(shí)例劳淆,同時(shí)一個(gè)Looper實(shí)例也只有一個(gè)MessageQueue。
2默赂、 loop()方法沛鸵,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。
好了曲掰,我們的異步消息處理線(xiàn)程已經(jīng)有了消息隊(duì)列(MessageQueue)疾捍,也有了在無(wú)限循環(huán)體中取出消息的哥們,現(xiàn)在缺的就是發(fā)送消息的對(duì)象了栏妖,于是:Handler登場(chǎng)了乱豆。
Handler是怎么創(chuàng)建的:手動(dòng)創(chuàng)建,自己創(chuàng)建的
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">private Handler handler = new Handler(Looper.getMainLooper()) {</pre>
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass();
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 that has not called Looper.prepare()");
} mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async; }</pre>
發(fā)送消息:把消息添加到了MessageQueue隊(duì)列里面,把MSG添加到了MessagerQueue里面吊趾。所有的消息都是通過(guò)sendMessageAtTime();
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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); }</pre>
MessageQueue的enqueueMessage方法:存消息源碼
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">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; }
boolean hasMessages(Handler h, int what, Object object) {
if (h == null) {
return false;
}</pre>
存放消息:
注釋1:p是隊(duì)列頭部宛裕,滿(mǎn)足3個(gè)條件則把消息放到隊(duì)列頭部
1.隊(duì)列中沒(méi)有消息,p==null 2.入隊(duì)的消息沒(méi)有延時(shí) 3.入隊(duì)的消息的延時(shí)比隊(duì)列頭部的消息延時(shí)短
注釋2:消息插入到鏈表中论泛,需要移動(dòng)鏈表揩尸,對(duì)比消息的延時(shí),插入到合適的位置
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true; if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}</pre>
MessageQueue的next()方法
面試官: 最新的一條消息屁奏, 還沒(méi)到時(shí)間如何觸發(fā)岩榆?
msg != null 我們看下這部分,如果當(dāng)前時(shí)間小于頭部時(shí)間(消息隊(duì)列是按時(shí)間順序排列的)
那就更新等待時(shí)間nextPollTimeoutMillis,等下次再做比較
如果時(shí)間到了坟瓢,就取這個(gè)消息并返回朗恳。
如果沒(méi)有消息,nextPollTimeoutMillis被賦為-1载绿,這個(gè)循環(huán)又執(zhí)行到nativePollOnce繼續(xù)阻塞!油航!
什么時(shí)候喚醒崭庸?
第一種答案:nativePollOnce到時(shí)間了自己?jiǎn)拘?/p>
喚醒有2個(gè)
自動(dòng)喚醒:nativePollOnce
手動(dòng)喚醒:nativeWakeUp
Android 中 MessageQueue 的 nativePollOnce - just_yang - 博客園 (cnblogs.com)
第二種答案: 和sleep一樣,阻塞一定得時(shí)間R昵簟E孪怼!Aぁ函筋!
阻塞時(shí)間由timeoutMillis來(lái)指定———————————————那什么時(shí)候會(huì)阻塞呢??jī)煞N情況:
1奠伪、有消息跌帐,但是當(dāng)前時(shí)間小于消息執(zhí)行時(shí)間,也就是代碼中的這一句:
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
這時(shí)候阻塞時(shí)間就是消息時(shí)間減去當(dāng)前時(shí)間绊率,然后進(jìn)入下一次循環(huán)谨敛,阻塞。
2滤否、沒(méi)有消息的時(shí)候脸狸,也就是上述代碼的最后一句:
if (msg != null) {}
else {
// No more messages.
nextPollTimeoutMillis = -1;
}
-1就代表一直阻塞。
epoll機(jī)制是怎么樣子的?
1.looper方法里面 沒(méi)有消息的時(shí)候藐俺,調(diào)用nativePollOnce()炊甲,休眠泥彤,block阻塞 。直到添加新消息
也就是獲取下一個(gè)消息,這個(gè)方法可能會(huì)阻塞,當(dāng)消息隊(duì)列沒(méi)有消息的時(shí)候.直到有消息,然后就會(huì)被喚醒,然后繼續(xù)取消息.
//當(dāng)消息隊(duì)列為空時(shí),這里會(huì)導(dǎo)致阻塞,直到有消息加入消息隊(duì)列,才會(huì)恢復(fù)//這里是native方法,利用的是Linux管道(Pipe)機(jī)制阻塞nativePollOnce(ptr, nextPollTimeoutMillis);
2.阻塞之后什么時(shí)候喚醒卿啡。 消息來(lái)的時(shí)候吟吝,調(diào)用nativeWake方法喚醒next()方法。
將Message添加到隊(duì)列時(shí)牵囤,框架會(huì)調(diào)用enqueueMessage()方法爸黄,里面也有個(gè)死的for循環(huán),該方法不僅會(huì)將消息插入隊(duì)列揭鳞,還會(huì)調(diào)用native static void nativeWake(long)
Handler如果沒(méi)有消息處理是阻塞的還是非阻塞的炕贵?阻塞
總結(jié):存消息,需要喚醒野崇,msgque中的方法 称开。取消息。looper乓梨。需要阻塞鳖轰,休眠。他們都是通過(guò)native.
深度底層:
如果需要喚醒隊(duì)列的話(huà)扶镀。 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等待線(xiàn)程,并且線(xiàn)程繼續(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)線(xiàn)程進(jìn)入任一方法時(shí)渠缕,它會(huì)因線(xiàn)程調(diào)度而被禁用。如果這些方法實(shí)際上浪費(fèi)了CPU周期褒繁,那么所有空閑應(yīng)用程序?qū)⑹褂?00%的CPU亦鳞,加熱并降低設(shè)備的速度。
源碼如下:
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">Message next() { 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 (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);
}
}
}</pre>
移除消息:通過(guò)MessageQueue
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null); }</pre>
實(shí)例寫(xiě)法:
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler2 = new Handler();</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">
<pre style="margin: 8px 0px; font-family: Consolas;">
Looper.loop();</pre>
}
}).start();</pre>
[圖片上傳失敗...(image-1fb98f-1650683445363)]
流程總結(jié):
準(zhǔn)備過(guò)程:prepare===創(chuàng)建Looper對(duì)象,創(chuàng)建messageQueque對(duì)象燕差。
使用的時(shí)候遭笋。發(fā)送消息的流程:handler.sendmesssage()------messageque存放msg
取消息的流程:得到looper------得到messageque-------得到msg,然后通過(guò)handle調(diào)用dispatch方法徒探。然后調(diào)用handler方法瓦呼,我們需要重寫(xiě)的
這幾個(gè)角色是如何協(xié)同工作的呢?簡(jiǎn)單概括為下面四個(gè)步驟:
- handler發(fā)送消息到message queue测暗,這個(gè)消息可能是一個(gè)message央串,可能是一個(gè)runnable
- looper負(fù)責(zé)從message queue取消息
- looper把消息dispatch給handler
- handler處理消息(handleMessage或者執(zhí)行runnable)
handler和looper的關(guān)系有點(diǎn)類(lèi)似于生產(chǎn)者和消費(fèi)者的關(guān)系,handler是生產(chǎn)者碗啄,生產(chǎn)消息然后添加到message queue质和;looper是消費(fèi)者,從message queue取消息****稚字。(生產(chǎn)者消費(fèi)者模式)
[圖片上傳失敗...(image-8001d8-1650683445359)]
總結(jié)原理:
1饲宿、首先Looper.prepare()在本線(xiàn)程中保存一個(gè)Looper實(shí)例,然后該實(shí)例中保存一個(gè)MessageQueue對(duì)象胆描;因?yàn)長(zhǎng)ooper.prepare()在一個(gè)線(xiàn)程中只能調(diào)用一次瘫想,所以MessageQueue在一個(gè)線(xiàn)程中只會(huì)存在一個(gè)。
2昌讲、Looper.loop()會(huì)讓當(dāng)前線(xiàn)程進(jìn)入一個(gè)無(wú)限循環(huán)国夜,不端從MessageQueue的實(shí)例中讀取消息,然后回調(diào)msg.target.dispatchMessage(msg)方法短绸。
3车吹、Handler的構(gòu)造方法,會(huì)首先得到當(dāng)前線(xiàn)程中保存的Looper實(shí)例鸠按,進(jìn)而與Looper實(shí)例中的MessageQueue想關(guān)聯(lián)。
4饶碘、Handler的sendMessage方法目尖,會(huì)給msg的target賦值為handler自身,然后加入MessageQueue中扎运。
5.報(bào)錯(cuò)Can't create handler inside thread that has not called Looper.prepare()瑟曲,沒(méi)有調(diào)用Looper.loop().UI線(xiàn)程默認(rèn)調(diào)用了 ,在ActivityThread類(lèi)里面的Looper.prepare()和Looper.loop()方法。Looper.prepare()和Looper.loop()方法豪治。
6.面試:****Handler和Looper是什么關(guān)系洞拨?
hander創(chuàng)建的時(shí)候需要傳入一個(gè)looper,looper在哪负拟,handler在哪烦衣。默認(rèn)不傳,在哪個(gè)線(xiàn)程new,就是哪個(gè)線(xiàn)程的Looper
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">Looper looper = Looper.myLooper(); handler = new Handler(looper) {</pre>
handler構(gòu)造方法里面的looper為什么不直接new花吟?不能保證唯一性
handler不是獨(dú)立存在的秸歧,一個(gè)handler,一定有一個(gè)專(zhuān)屬的線(xiàn)程衅澈,一個(gè)消息隊(duì)列键菱,和一個(gè)looper與之關(guān)聯(lián)。
Handler的作用就是:調(diào)度消息和runnable對(duì)象去被執(zhí)行今布;使動(dòng)作在不同的線(xiàn)程中被執(zhí)行经备。
handler不僅可以分發(fā)消息,還可以分發(fā)runable部默。把runable封裝成消息
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
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");
}
}
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;
}
面試18連問(wèn):
**1.****Handler造成Activity泄漏****侵蒙,用弱引用真的有用么? **
產(chǎn)生原因:第一內(nèi)部類(lèi)持有activity的引用 ,
第二:引用鏈關(guān)系:ThreadLocal---->Looper------> messageque------->msg---------handler----activity
handler造成內(nèi)存泄漏是因?yàn)樵贏ctivity銷(xiāo)毀的時(shí)候還有未執(zhí)行完的任務(wù)
解決辦法:
靜態(tài)static可以解決內(nèi)存泄漏
使用弱引用也可以解決內(nèi)存泄漏甩牺,但是需要等到handler的中任務(wù)都執(zhí)行完蘑志,才會(huì)釋放activity內(nèi)存,不如直接static釋放的快
為何handler要定義為static的同時(shí)贬派,還要用WeakReference 包裹外部類(lèi)的對(duì)象急但?
這是因?yàn)槲覀冃枰褂猛獠款?lèi)的成員,可以通過(guò)"activity. "獲取變量方法等搞乏,如果直接使用強(qiáng)引用波桩,顯然會(huì)導(dǎo)致activity泄露。
handler造成內(nèi)存泄漏有 兩種方案:一種是業(yè)務(wù)邏輯上请敦,在activity銷(xiāo)毀的時(shí)候移除所有未執(zhí)行的任務(wù)镐躲。
一種是從GC上,通過(guò)static的Handler或者弱引用解決侍筛。但是單獨(dú)的使用弱引用性能不是太高萤皂。
最好的辦法:要把handler也置空,比如在線(xiàn)程延遲3000之后延時(shí)消息匣椰。
問(wèn)題: 為什么內(nèi)部類(lèi)會(huì)持有外部類(lèi)的引用裆熙?
2.為什么Loop里面的Looper()方法用死循環(huán)?
要處理4大組件里面的事情禽笑,并不希望立馬退出入录。
3.****主線(xiàn)程的Looper.loop一直在無(wú)限循環(huán)是嗎?****為什么Looper中的Loop()方法不能導(dǎo)致主線(xiàn)程卡死?
主線(xiàn)程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢佳镜?
1).并不是僚稿,這里就涉及到Linux pipe/epoll機(jī)制,簡(jiǎn)單說(shuō)就是在主線(xiàn)程的MessageQueue沒(méi)有消息時(shí)蚀同,便阻塞在loop的queue.next()中的nativePollOnce()方法里缅刽,此時(shí)主線(xiàn)程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生唤崭,通過(guò)往pipe管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線(xiàn)程工作拷恨。這里采用的epoll機(jī)制,是一種IO多路復(fù)用機(jī)制谢肾,可以同時(shí)監(jiān)控多個(gè)描述符腕侄,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮髀瑁举|(zhì)是同步I/O冕杠,即讀寫(xiě)是阻塞的。所以說(shuō)酸茴,主線(xiàn)程大多數(shù)時(shí)候都是處于休眠狀態(tài)分预,并不會(huì)消耗大量CPU資源。
2).耗時(shí)操作本身并不會(huì)導(dǎo)致主線(xiàn)程卡死, 導(dǎo)致主線(xiàn)程卡死的真正原因是耗時(shí)操作之后的觸屏操作, 沒(méi)有在規(guī)定的時(shí)間內(nèi)被分發(fā)薪捍。
Looper 中的 loop()方法, 他的作用就是從消息隊(duì)列MessageQueue 中不斷地取消息(調(diào)用messageQue的next()方法), 然后將事件分發(fā)出去笼痹。
3).ANR和死循環(huán)沒(méi)有關(guān)系。2個(gè)完全不是個(gè)東西酪穿。
只要有消息處理凳干,沒(méi)有消息處理會(huì)阻塞。
問(wèn)題:阻塞不會(huì)導(dǎo)致ANR嗎被济?
不會(huì)救赐。ANR和阻塞2個(gè)事情,ANR是因?yàn)闆](méi)用 消息及時(shí)處理只磷。ANR有消息沒(méi)用及時(shí)處理:因?yàn)槁裾◤椀膯?wèn)題
4.handler是怎么做到線(xiàn)程的切換的经磅?
Handler的dispatchMessage方法是在創(chuàng)建Handler時(shí)所使用的Looper中執(zhí)行的,這樣就成功地將代碼邏輯切換到指定的線(xiàn)程中去執(zhí)行了钮追。
服務(wù)端開(kāi)發(fā)里面的消息隊(duì)列本身也是這個(gè)原理预厌,隊(duì)列對(duì)所有線(xiàn)程都是可見(jiàn)的,大家都可以往里面 enqueue 消息
用了主線(xiàn)程都looper元媚。 Handler 是在他關(guān)聯(lián)的 Looper 對(duì)應(yīng)的線(xiàn)程中處理消息的轧叽。(主線(xiàn)程的looper)
真正的原因:messagequeue的,隊(duì)列可以實(shí)現(xiàn)內(nèi)存共享惠毁。然后looper覺(jué)得是發(fā)哪個(gè)線(xiàn)程犹芹!
因此 Looper 所處的線(xiàn)程也就決定了你 Handler 提交任務(wù)執(zhí)行所在的線(xiàn)程崎页。
5.一對(duì)多的關(guān)系和多對(duì)一的關(guān)系鞠绰,它們之間的關(guān)系?
一個(gè)線(xiàn)程有多少個(gè)handler飒焦?
一個(gè)線(xiàn)程有多少個(gè)looper蜈膨?
如何保證只有一個(gè)looper屿笼?
一個(gè)handler,一個(gè)messageque翁巍,可以對(duì)應(yīng)多個(gè)線(xiàn)程
[圖片上傳失敗...(image-9519cc-1650683445375)]
既然有多對(duì)一驴一,或者一多的問(wèn)題,多個(gè)handle可以發(fā)消息給msgqueue灶壶。怎么保證消息安全的肝断?
加了鎖,synizch()驰凛;存和取都加了胸懈。
6.發(fā)送延時(shí)消息是怎么處理的
handler.postDelay并不是先等待一定的時(shí)間再放入到MessageQueue中,而是直接進(jìn)入MessageQueue恰响,以MessageQueue的時(shí)間順序排列和喚醒的方式結(jié)合實(shí)現(xiàn)的趣钱。
延時(shí)消息的存在就讓這個(gè)隊(duì)列有些特殊性了,并不能完全保證先進(jìn)先出胚宦,而是需要根據(jù)時(shí)間來(lái)判斷首有,所以Android中采用了鏈表的形式來(lái)實(shí)現(xiàn)這個(gè)隊(duì)列,也方便了數(shù)據(jù)的插入
根據(jù)消息隊(duì)列入隊(duì)規(guī)制枢劝,如果隊(duì)列中沒(méi)消息井联,那么不管要入隊(duì)的消息有沒(méi)有延時(shí),都放到隊(duì)列頭呈野。如果隊(duì)列不空低矮,那么要跟隊(duì)列頭的消息比較一下延時(shí),如果要入隊(duì)的消息延時(shí)短被冒,則放隊(duì)列頭军掂,否則,放到隊(duì)列中去昨悼,需要移動(dòng)鏈表蝗锥。
Message是一個(gè)單鏈表結(jié)構(gòu),所以我們可以看到率触,當(dāng)?shù)谝粋€(gè)元素是空的時(shí)候终议,加入的msg是在隊(duì)首。當(dāng)when是0的時(shí)候葱蝗,前邊已經(jīng)說(shuō)了穴张,只有在sendMessageAtFrontOfQueue方法時(shí)才會(huì)傳入,也需要加入隊(duì)首两曼。當(dāng)這個(gè)msg的when比隊(duì)首的msg的when小時(shí)(前邊已經(jīng)分析過(guò)了皂甘,when表示這個(gè)msg需要執(zhí)行的時(shí)間點(diǎn)),也需要加入隊(duì)首悼凑。
如果不是這三種情況偿枕,就對(duì)Message這個(gè)鏈表進(jìn)行遍歷璧瞬,根據(jù)msg的when找到對(duì)應(yīng)的位置插入即可。
如果隊(duì)列中只有這個(gè)消息渐夸,那么消息不會(huì)被發(fā)送婆咸,而是計(jì)算到時(shí)喚醒的時(shí)間食听,先將Looper 阻塞,到時(shí)間就喚醒它。但如果此時(shí)要加入新消息鹉动,該消息隊(duì)列的對(duì)頭跟delay 時(shí)間相比更長(zhǎng)借卧,則插入到頭部睦裳,按照觸發(fā)時(shí)間進(jìn)行排序赫悄,隊(duì)頭的時(shí)間最小、隊(duì)尾的時(shí)間最大
總結(jié):
sendMessageDelayed是通過(guò)阻塞來(lái)達(dá)到了延時(shí)發(fā)送消息的結(jié)果态坦,那么會(huì)不會(huì)阻塞新添加的Message盐数?
Handler在發(fā)送消息的時(shí)候,MessageQueue里的消息是按照發(fā)送時(shí)間點(diǎn)從小到大排列的伞梯,
如果最近的Message未到達(dá)發(fā)送的時(shí)間則阻塞玫氢。
新加入的數(shù)據(jù)會(huì)根據(jù)時(shí)間點(diǎn)的大小判斷需要插入的位置,同時(shí)還需要判斷是否需要喚醒線(xiàn)程去發(fā)送當(dāng)前的隊(duì)首的消息谜诫。
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;">public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
</pre>
### 7.如果在當(dāng)前線(xiàn)程內(nèi)使用Handler postdelayed 兩個(gè)消息漾峡,一個(gè)延遲5s,一個(gè)延遲10s
然后使當(dāng)前線(xiàn)程sleep 5秒喻旷,以上消息的執(zhí)行時(shí)間會(huì)如何變化生逸?
答:照常執(zhí)行
擴(kuò)展:sleep時(shí)間<=5 對(duì)兩個(gè)消息無(wú)影響,5< sleep時(shí)間 <=10 對(duì)第一個(gè)消息有影響且预,第一個(gè)消息會(huì)延遲到sleep后執(zhí)行槽袄,sleep時(shí)間>10 對(duì)兩個(gè)時(shí)間都有影響,都會(huì)延遲到sleep后執(zhí)行锋谐。
總結(jié):如果sleep的時(shí)間<=延時(shí)的時(shí)間遍尺,是沒(méi)有影響的。大于就有影響
問(wèn)題: 假如發(fā)了一個(gè)延時(shí)消息5s,然后這個(gè)時(shí)間沒(méi)有其他消息了涮拗。怎么處理乾戏?
通過(guò)延時(shí),阻塞三热。
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;">if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue; }</pre>
聊下Handler postDelay的底層原理鼓择。如果滅屏前調(diào)用SystemClock.uptimeMillis然后滅屏,等10秒亮屏就漾,在打印SystemClock.uptimeMillis呐能,這兩個(gè)的時(shí)間差是10s嗎,為什么从藤。
舉例:
1).先 一個(gè)消息sleep.3s, 然后另外一個(gè)消息postDelay1s 催跪,
分析: 會(huì)先sleep3s 。然后再進(jìn)行操作夷野。發(fā)送延時(shí)1s的消息懊蒸,但是messageque取的時(shí)候,有一個(gè)條件:
假如開(kāi)始開(kāi)機(jī)時(shí)間是1s悯搔。
因?yàn)閙sg.when=2s
然后運(yùn)行next的方法骑丸,開(kāi)機(jī)時(shí)間是1+3=4s. 然后現(xiàn)在時(shí)間(now)>執(zhí)行時(shí)間(msg.when)---->把開(kāi)始沒(méi)有執(zhí)行完的消息,立馬執(zhí)行
[圖片上傳失敗...(image-9c6cd5-1650683445357)]
https://www.imooc.com/article/21997
8.消息屏障
Handler的Messgae種類(lèi)分為三種:
- 普通消息:同步消息
- 異步消息
- 屏障消息
其中普通消息又稱(chēng)為同步消息妒貌,我們平時(shí)發(fā)的消息基本都是同步消息
一般來(lái)說(shuō)通危,MessageQueue里面的所有Message是按照時(shí)間從前往后有序排列的。
同步屏障消息就是在消息隊(duì)列中插入一個(gè)屏障灌曙,在屏障之后的所有普通消息都會(huì)被擋著菊碟,不能被處理。
不過(guò)異步消息卻例外在刺,屏障不會(huì)擋住異步消息逆害,
因此可以認(rèn)為,屏障消息就是為了確保異步消息的優(yōu)先級(jí)蚣驼,設(shè)置了屏障后魄幕,只能處理其后的異步消息,同步消息會(huì)被擋住颖杏,除非撤銷(xiāo)屏障纯陨。
同步屏障: 往消息隊(duì)列插入一個(gè)同步屏障消息,這時(shí)候消息隊(duì)列中的同步消息不會(huì)被處理留储,而是優(yōu)先處理異步消息翼抠。這里很好理解,
同步屏障的處理代碼在MessageQueue
的next
方法:
刪除屏障消息的方法很簡(jiǎn)單获讳,就是不斷遍歷消息隊(duì)列机久,知道找到屏障消息,退出循環(huán)的條件有兩個(gè)赔嚎,一是p.target == null膘盖,說(shuō)明是屏障消息,二是p.arg1 == token尤误,也說(shuō)明p是屏障消息侠畔,因?yàn)樵谄琳舷⑷腙?duì)的時(shí)候,設(shè)置過(guò) msg.arg1 = token损晤。找到屏障消息后软棺,把它從消息隊(duì)列中刪除并回收。調(diào)用MessageQueue.removeSyncBarrier 方法可以移除指定的消息屏障
所以消息屏障和異步消息的作用很明顯尤勋,在設(shè)置消息屏障后喘落,異步消息具有優(yōu)先處理的權(quán)利茵宪。
這時(shí)候我們回顧將消息添加到消息隊(duì)列中時(shí),可以發(fā)現(xiàn)瘦棋,其實(shí)并不是每一次添加消息時(shí)稀火,都會(huì)喚醒線(xiàn)程。 當(dāng)該消息插入到隊(duì)列頭時(shí)赌朋,會(huì)喚醒該線(xiàn)程凰狞; 當(dāng)該消息沒(méi)有插入到隊(duì)列頭,但隊(duì)列頭是屏障沛慢,且該消息是隊(duì)列中 靠前的一個(gè)異步消息赡若,則會(huì)喚醒線(xiàn)程,執(zhí)行該消息团甲;
同步屏障和異步消息有具體的使用場(chǎng)景嗎逾冬?
消息屏障的應(yīng)用:
1).UI相關(guān)的操作優(yōu)先級(jí)最高,比如消息隊(duì)列有很多沒(méi)處理完的任務(wù)躺苦,這時(shí)候啟動(dòng)一個(gè)Activity粉渠,當(dāng)然要優(yōu)先處理Activity啟動(dòng),然后再去處理其他的消息圾另,同步屏障的設(shè)計(jì)堪稱(chēng)一絕吧霸株。
2).view刷新機(jī)制
3)關(guān)于Handler有一個(gè)需求,一個(gè)消息要立刻執(zhí)行集乔,要怎么做
9.可以在子線(xiàn)程直接new 一個(gè)Handler 嗎去件?怎么做?
主線(xiàn)程為什么不用扰路?
不可以尤溜,因?yàn)樵谥骶€(xiàn)程中,Activity 內(nèi)部包含一個(gè)Looper 對(duì)象汗唱,它會(huì)自動(dòng)管理Looper宫莱,處理子線(xiàn)程中發(fā)送過(guò)來(lái)的消息。而
對(duì)于子線(xiàn)程而言哩罪,沒(méi)有任何對(duì)象幫助我們維護(hù)Looper 對(duì)象授霸,所以需要我們自己手動(dòng)維護(hù)。所以要在子線(xiàn)程開(kāi)啟Handler 要先創(chuàng)建Looper际插,并開(kāi)啟Looper 循環(huán)
`<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.8rem;"> new Thread(){
@Override
public void run() {
Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop(); handler.sendEmptyMessageDelayed(2000,2000);//去更新ui
}
}.start(); }</pre>
<pre style="margin: 8px 0px; font-weight: bold; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.8rem;">private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); //通過(guò)時(shí)間計(jì)算得到 if (remainTime < 1) {
handler.removeCallbacksAndMessages(null);
return; }
remainTime = remainTime - 1;
progressValue = (int) ((max_time - remainTime) * 100 / max_time);
Log.d("peng", "handleMessage" + progressValue + "remainTime:" + remainTime);
if (progressValue < 101) {
handler.sendEmptyMessageDelayed(MSG_WHAT, 1000);
updateProgress();
}
}
};</pre>
這2個(gè)looper碘耳,到底用哪個(gè)?用主looper
在線(xiàn)程切換中框弛,不會(huì)
<pre style="margin: 8px 0px; font-weight: bold; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;"> Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop(); </pre>
10.子線(xiàn)程中 Toast辛辨,showDialog的方法。(和上面一樣!)
android如何在子線(xiàn)程中彈出ToasT`
new Thread(){
@Override
public void run() {
try {
Looper.prepare();
Toast.makeText(getApplicationContext(), "備份成功", 0).show();
Looper.loop();
} catch (Exception e) {
Looper.prepare();
Toast.makeText(getApplicationContext(), "備份失敗", 0).show();
Looper.loop();
}
}
}.start();
看源碼:用這個(gè)可以斗搞,但是有內(nèi)存泄露導(dǎo)致
<pre style="margin: 8px 0px;">Looper.prepare();
Looper.loop();</pre>
解決辦法:在子線(xiàn)程中添加了Looper.prepare()以及Looper.loop()方法后指攒,Toast的報(bào)錯(cuò)沒(méi)有了,并且能夠正常彈出toast僻焚。
子線(xiàn)程顯示Toast是沒(méi)有問(wèn)題的允悦,但是Toast是一個(gè)比較特殊的UI,跟系統(tǒng)有關(guān)系溅呢。
Toast本質(zhì)上是一個(gè)window,跟activity是平級(jí)的猿挚,而我們平時(shí)所說(shuō)的非UI線(xiàn)程不能更新UI咐旧,是因?yàn)樵赩iewRootImpl里面會(huì)有線(xiàn)程檢查,checkThread只是Activity維護(hù)的View樹(shù)的行為绩蜻。
結(jié)果證明在子線(xiàn)程里面是可以彈Toast铣墨。那么問(wèn)題來(lái)了,顯示Toast是UI操作是毋庸置疑的办绝,那么就是我一直認(rèn)為的子線(xiàn)程不能進(jìn)行UI操作的認(rèn)識(shí)有誤區(qū)伊约?
可以在子線(xiàn)程中new Handler嗎?
1).要先prepare一下。然后looper循環(huán)
2).看demo孕蝉,發(fā)現(xiàn)有異步的問(wèn)題屡律。2個(gè)不在同一線(xiàn)程,handler和線(xiàn)程降淮。用syched實(shí)現(xiàn)超埋。比如可以用系統(tǒng)的handlerThread.看源碼
##### 主線(xiàn)程與子線(xiàn)程使用Handler的區(qū)別
答:主線(xiàn)程與子線(xiàn)程在使用Handler時(shí)主要區(qū)別在于子線(xiàn)程在new Handler之前需要保證子線(xiàn)程有Looper對(duì)象,否則會(huì)拋出異常佳鳖,
也就是在new Handler之前需要先執(zhí)行Looper.prepare方法霍殴,初始化一個(gè)Looper對(duì)象,存到ThreadLocal中系吩。同一個(gè)線(xiàn)程中来庭,Looper.prepare方法不能執(zhí)行多次,否則也是會(huì)拋出異常的穿挨,因?yàn)槿绻愠跏蓟啻蔚脑?huà)月弛,存入到ThreadLocal中,就不知道到底該用哪一個(gè)Looper對(duì)象了科盛。
主線(xiàn)程中不需要Looper.prepare是因?yàn)樵贏PP啟動(dòng)的時(shí)候尊搬,ActivityThread的main方法中執(zhí)行了Looper.getMainLooper方法,初始化了一個(gè)Looper對(duì)象土涝。
大家經(jīng)常提到的主線(xiàn)程佛寿,也叫UI線(xiàn)程,它就是ActivityThread,ActivityThread被創(chuàng)建時(shí)就會(huì)初始化Looper冀泻,這也是在主線(xiàn)程中默認(rèn)可以使用Handler的原因常侣。
主線(xiàn)程的looper在哪里啟動(dòng)的?
ActivityThread的main()函數(shù)! ActivityThread 的動(dòng)力是什么弹渔?
11.子線(xiàn)程一定不能更新UI胳施?線(xiàn)程中settextView,一定會(huì)報(bào)錯(cuò)嗎肢专?
[http://www.reibang.com/p/5a1af8e95fd5](http://www.reibang.com/p/5a1af8e95fd5) 非常好舞肆。
在onCreate中用子線(xiàn)程更新UI不蹦,而延遲之后就發(fā)生車(chē)禍博杖。
在viewRootIml源碼中的
texview里面的settxt()椿胯;
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">checkForRelayout();</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;"> requestLayout();
invalidate(); } else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout. nullLayouts();
requestLayout();
invalidate();</pre>
view里面的
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">ViewRootImpl方法里面:// 如果當(dāng)前線(xiàn)程不是主線(xiàn)程就拋出異常</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}</pre>
ViewRootImpl的checkThread方法點(diǎn)進(jìn)去看。
```
void checkThread() {
// 如果當(dāng)前線(xiàn)程不是主線(xiàn)程就拋出異常
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
```
ViewRootImpl中調(diào)用的地方是requestLayout方法
```
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 檢測(cè)當(dāng)前線(xiàn)程
checkThread();
mLayoutRequested = true;
// view樹(shù)遍歷
scheduleTraversals();
}
}
```
其實(shí)原因很簡(jiǎn)單剃根,既然是用ViewRootImpl的checkThread方法來(lái)檢查線(xiàn)程哩盲,那么就說(shuō)明在onCreate的時(shí)候ViewRootImpl還沒(méi)被創(chuàng)建,所以不會(huì)走checkThread方法狈醉,自然就不會(huì)報(bào)錯(cuò)了廉油。
最后的結(jié)果是ViewRootImpl是在onResume之后創(chuàng)建的,真相大白苗傅!
如果檢測(cè)線(xiàn)程要優(yōu)先執(zhí)行
<pre style="margin: 8px 0px;">invalidate快與requestLayout();那么更新ui沒(méi)有問(wèn)題</pre>
如果先requestLayout().里面調(diào)用了線(xiàn)程檢測(cè)抒线,在invalidate就會(huì)有問(wèn)題
或者:ViewRootImpl沒(méi)有創(chuàng)建
既然是用ViewRootImpl的checkThread方法來(lái)檢查線(xiàn)程,那么就說(shuō)明在onCreate的時(shí)候ViewRootImpl還沒(méi)被創(chuàng)建渣慕,所以不會(huì)走checkThread方法十兢,自然就不會(huì)報(bào)錯(cuò)了
每次view刷新的時(shí)候都會(huì)去檢測(cè)一次線(xiàn)程
**12.Android 線(xiàn)程A與線(xiàn)程B如何通信的 **
類(lèi)似子線(xiàn)程往主線(xiàn)程發(fā)消息一樣,其實(shí)android中線(xiàn)程通信無(wú)非就是handler和looper的操作摇庙。
需要注意的就是要loop.prapare()和looper.loop()旱物。不調(diào)用Loop.loop()方法的話(huà),是收不到消息的卫袒,
**demo: **
**<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">private Handler handler; private void testHandler() {
new Thread() {
@Override
public void run() {
super.run();
Looper.prepare();
if (handler == null) {
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.d("MainActivity", "handleMessage" + Thread.currentThread().getName());
}
};
}
Looper.loop();
}
}.start();
new Thread() {
@Override
public void run() {
super.run();
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d("MainActivity", "sendEmptyMessageDelayed" + Thread.currentThread().getName());
handler.sendEmptyMessageDelayed(222, 2000);
}
}.start(); }</pre>**
handler原理宵呛,主線(xiàn)程發(fā)送message給子線(xiàn)程?
原理:通過(guò)handler夕凝。哪個(gè)線(xiàn)程要收到宝穗,就通過(guò)哪個(gè)線(xiàn)程發(fā)送
[Android 使用handler實(shí)現(xiàn)線(xiàn)程間發(fā)送消息 (主線(xiàn)程 與 子線(xiàn)程之間)、(子線(xiàn)程 與 子線(xiàn)程之間)_良秋的專(zhuān)欄-CSDN博客](https://blog.csdn.net/a740169405/article/details/47070457)
13.handler運(yùn)行在哪個(gè)線(xiàn)程码秉?
看looper在哪個(gè)線(xiàn)程逮矛,handler用哪個(gè)?
因?yàn)椋簞?chuàng)建handler的時(shí)候傳了一個(gè)looper
**14.handler.post(Runnable) runnable是如何執(zhí)行的? **handler機(jī)制转砖,調(diào)用handler.post(Runnable)此Runnable運(yùn)行在什么線(xiàn)程须鼎?
Runuable什么時(shí)候執(zhí)行鲸伴,在handler.post,run的時(shí)候運(yùn)行
這個(gè)要看handler,handler要看looper晋控。模式直接new汞窗,是在主線(xiàn)程
開(kāi)啟的runnable會(huì)在這個(gè)handler所依附線(xiàn)程中運(yùn)行,而這個(gè)handler是在UI線(xiàn)程中創(chuàng)建的赡译,所以自然地依附在主線(xiàn)程中了仲吏。
postDelayed(new Runnable()) 而沒(méi)有重新生成新的 New Thread()
**Runable是怎么工作的?**
最終它們都會(huì)調(diào)用到Handler#sendMessageAtTime(Message msg, long uptimeMillis
[圖片上傳失敗...(image-49bb98-1650683445361)]
Handler 的 post(Runnable) 與 sendMessage 有什么區(qū)別
所以post(Runnable) 與 sendMessage的區(qū)別就在于后續(xù)消息的處理方式蝌焚,是交給`msg.callback`還是 `Handler.Callback`或者`Handler.handleMessage`裹唆。
**2種handler的寫(xiě)法有什么不一樣?
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; background-color: rgb(43, 43, 43);">private Handler handler2=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
return false;
}
}); private Handler handler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};</pre>**
**## Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一樣只洒?為什么這么設(shè)計(jì)许帐?
插件話(huà)用到了這個(gè)玩意。
源碼分析:
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">/**
* Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}</pre>**
* 如果為true红碑,則不再執(zhí)行Handler.handleMessage
* 如果為false舞吭,則兩個(gè)方法都要執(zhí)行泡垃。
handler的Callback和handlemessage都存在析珊,但callback返回true handleMessage還會(huì)執(zhí)行么
Dispathch方法:
可以看到,除了在Handler#handleMessage(...)中處理消息外蔑穴,Handler 機(jī)制還提供了兩個(gè) Callback 來(lái)增加消息處理的靈活性忠寻。具體來(lái)說(shuō),若設(shè)置了Message.Callback則優(yōu)先執(zhí)行存和,否則判斷Handler.Callback的返回結(jié)果奕剃,如果返回false,則最后分發(fā)到Handler.handleMessage(...)
**15.在子線(xiàn)程捐腿,如果消息輪詢(xún)完了纵朋。線(xiàn)程處于什么狀態(tài)?****Looper中的quitAllowed字段是啥茄袖?有什么用操软?**
要通過(guò)looper。quit()宪祥。清空消失聂薪,不然一直處于阻塞狀態(tài)?looper有個(gè)for循環(huán)蝗羊,處于block狀態(tài)藏澳。然后子線(xiàn)程一直在運(yùn)行,容易導(dǎo)致內(nèi)存泄漏耀找。
調(diào)用quitAllowed().喚醒翔悠。然后可以退出。
比如:ThreadHandler()
* 主線(xiàn)程中,一般情況下肯定不能退出凉驻,因?yàn)橥顺龊笾骶€(xiàn)程就停止了腻要。因?yàn)橄到y(tǒng)AMS等等。要處理消息涝登,不能停止雄家。所以是當(dāng)APP需要退出的時(shí)候,就會(huì)調(diào)用quit方法胀滚,涉及到的消息是EXIT_APPLICATION趟济,大家可以搜索下。* 子線(xiàn)程中咽笼,如果消息都處理完了顷编,就需要調(diào)用quit方法停止消息循環(huán)。
### 16.IdleHandler是啥剑刑?有什么使用場(chǎng)景媳纬?
IdleHandler應(yīng)用場(chǎng)景:leakcanery,啟動(dòng)流程里面:ActivityThread里面的handlerResumeActivity()方法!
**<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler()); }
</pre>**
通過(guò)源碼分析:
如果callback為空和不為空的話(huà)
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}</pre>
使用當(dāng)前線(xiàn)程的MessageQueue.addIdleHandler方法可以在消息隊(duì)列中添加一個(gè)IdelHandler施掏。
```
MessageQueue messageQueue = Looper.myQueue();
messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
```
當(dāng)MessageQueue 阻塞時(shí)钮惠,即當(dāng)前線(xiàn)程空閑時(shí),會(huì)回調(diào)IdleHandler中的方法七芭;
> 注:a素挽,添加IdelHandler時(shí),消息隊(duì)列不為空狸驳,當(dāng)消息處理完或者剩下消息還沒(méi)到觸發(fā)時(shí)間预明,會(huì)回調(diào)方法 b,當(dāng)添加IdelHandler時(shí)耙箍,消息隊(duì)列為空撰糠,則當(dāng)時(shí)不會(huì)觸發(fā)回調(diào)
當(dāng)IdelHandler接口返回false時(shí),表示該IdelHandler只執(zhí)行一次辩昆,
批量任務(wù)阅酪,任務(wù)密集,且只關(guān)注最終結(jié)果
例如卤材,在開(kāi)發(fā)一個(gè)IM類(lèi)型的界面時(shí)遮斥,通常情況下,每次收到一個(gè)IM消息時(shí)扇丛,都會(huì)刷新一次界面术吗,但是當(dāng)短時(shí)間內(nèi), 收到多條消息時(shí)帆精,就會(huì)刷新多次界面较屿,容易造成卡頓隧魄,影響性能,此時(shí)就可以使用一個(gè)工作線(xiàn)程監(jiān)聽(tīng)I(yíng)M消息隘蝎,在通過(guò)添加IdelHandler的方式通知界面刷新购啄,避免短時(shí)間內(nèi)多次刷新界面情況的發(fā)生。
ok嘱么,綜上所述狮含,IdleHandler就是當(dāng)消息隊(duì)列里面沒(méi)有當(dāng)前要處理的消息了,需要堵塞之前曼振,可以做一些空閑任務(wù)的處理几迄。
常見(jiàn)的使用場(chǎng)景有:?jiǎn)?dòng)優(yōu)化。
17.Message 可以如何創(chuàng)建冰评?哪種效果更好映胁,為什么?
參考回答:可以通過(guò)三種方法創(chuàng)建:
直接生成實(shí)例Message m = new Message
通過(guò)Message m = Message.obtain
通過(guò)Message m = mHandler.obtainMessage()
后兩者效果更好甲雅,因?yàn)锳ndroid 默認(rèn)的消息池中消息數(shù)量是10解孙,而后兩者是直接在消息池中取出一個(gè)Message 實(shí)例,這樣做就可以避免多生成Message 實(shí)例抛人。
消息怎么復(fù)用的弛姜?Message為什么沒(méi)有被回收?msg復(fù)用原理
消息的享元模式函匕,有一個(gè)消息池
Message是什么數(shù)據(jù)結(jié)構(gòu)娱据?鏈表蚪黑,一直指向下一個(gè)盅惜。
消息機(jī)制里需要頻繁創(chuàng)建消息對(duì)象(Message),因此消息對(duì)象需要使用享元模式來(lái)緩存忌穿,以避免重復(fù)分配 & 回收內(nèi)存抒寂。
具體來(lái)說(shuō),Message 使用的是有容量限制的掠剑、無(wú)頭節(jié)點(diǎn)的單鏈表的對(duì)象池:
為啥:對(duì)象池就一個(gè)實(shí)體屈芜,不是一個(gè)集合?
從代碼中我們可以看到,Message的復(fù)用機(jī)制沒(méi)有使用任何一種數(shù)據(jù)結(jié)構(gòu)朴译,如LinkedArrayList井佑,而是通過(guò)Message對(duì)象內(nèi)部的spool和next字段,通過(guò)指針的方式來(lái)進(jìn)行對(duì)象管理眠寿,不得不說(shuō)躬翁,是一種非常巧妙的設(shè)計(jì)方式,一來(lái)降低了設(shè)計(jì)復(fù)雜度盯拱,而且由于沒(méi)有創(chuàng)建額外的數(shù)據(jù)容器來(lái)管理對(duì)象盒发,減輕了內(nèi)存的壓力例嘱,實(shí)現(xiàn)了輕量化的目的。
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-weight: bold; background-color: rgb(43, 43, 43); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">private static Message sPool;</pre>
```
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();
}
復(fù)制代碼
```
18.Handler如何保證線(xiàn)程安全的
答:在Handler發(fā)送消息時(shí)宁舰,會(huì)將Message存入MessageQueue消息隊(duì)列中拼卵,即enqueueMessage方法,這個(gè)方法中蛮艰,有一個(gè)synchronized(this){}的方法塊腋腮,同時(shí)在Looper.loop()方法中的MessageQueue.next()方法中也是使用synchronized加鎖的方式來(lái)保證存取Message的線(xiàn)程安全的。
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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; }</pre>
19\. 問(wèn)題:?jiǎn)栐谝粋€(gè)工作線(xiàn)程中創(chuàng)建自己的消息隊(duì)例應(yīng)該怎么做壤蚜?
20. Android-AsyncQueryHandler
參考博客:
[http://blog.csdn.net/guolin_blog/article/details/9991569](http://blog.csdn.net/guolin_blog/article/details/9991569)
[http://blog.csdn.net/lmj623565791/article/details/38377229](http://blog.csdn.net/lmj623565791/article/details/38377229)
[https://blog.csdn.net/star_nwe/article/details/115255882](https://blog.csdn.net/star_nwe/article/details/115255882)(牛逼,被封了)
[https://blog.csdn.net/jdsjlzx/article/details/110525796](https://blog.csdn.net/jdsjlzx/article/details/110525796)(非常完美)
手寫(xiě)handler流程:
3步搞定
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">private void main() {
Looper.prepare();//創(chuàng)建全局唯一的主線(xiàn)程looper
final com.handler.myapplication.Handler handler=new com.handler.myapplication.Handler(){
@Override
public void handleMessager(com.handler.myapplication.Message message) {
super.handleMessager(message);
}
};
new Thread(){
@Override
public void run() {
super.run();
//子線(xiàn)程發(fā)送消息低葫,存入消息
com.handler.myapplication.Message message=new com.handler.myapplication.Message();
message.obj="dddd";
handler.sendMessage(message);
}
}.start();
Looper.loop();//取消息 }
</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">
public class Looper {
public Looper() {
this.mQueue = new MessageQueue();
}
final static ThreadLocal<Looper> threadLocal=new ThreadLocal<>();
public MessageQueue mQueue; public static void prepare() {
if(threadLocal!=null){
Log.d("peng","prepare");
}
threadLocal.set(new Looper());
}
protected static Looper myLooper(){
return threadLocal.get();
}
public static void loop(){
Looper looper=myLooper();
MessageQueue messageQueue=looper.mQueue; Message messageResult=null; while (true){
try {
messageResult=messageQueue.next();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(messageResult!=null){
messageResult.target.dispatchMessage(messageResult);
}
}
}
}</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public class Message {
public String obj;
public Handler target; public Message() {
}
public Message(String obj) {
this.obj = obj;
}
}
</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public class MessageQueue {
BlockingQueue<Message> blockingDeque=new ArrayBlockingQueue<>(50); public Message next() throws InterruptedException {
return blockingDeque.take();
}
public void enqueMessage(Message message) {
try {
blockingDeque.put(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public class Handler {
private Looper mLooper;
private MessageQueue messageQueue; public Handler() {
this.mLooper = Looper.myLooper();
messageQueue=mLooper.mQueue;
}
public void handleMessager(Message message){
enqueueMessage(message);
}
private void enqueueMessage(Message message) {
message.target=this;
messageQueue.enqueMessage(message);
}
public void sendMessage(Message message) {
enqueueMessage(message);
}
public void dispatchMessage(Message messageResult) {
handleMessager(messageResult);
}
}
</pre>
列子:
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">public void testHandler2(final TextView textView){
mHandler=new Handler();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
i++;
mHandler.post(new Runnable() {
@Override
public void run() {
textView.setText(""+i);
Log.d("mHandler",""+i);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start(); }</pre>
錯(cuò)誤代碼如下:
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">*/*** ** Error**,**while**死循環(huán)仍律,主線(xiàn)程做了耗時(shí)的操作* *** ***@param*** *textView* **/* public void testHandler(final TextView textView){
mHandler=new Handler(getMainLooper());
mHandler.post(new Runnable() {
@Override
public void run() {
// textView.setText(""+i);
while (true){
Log.d("mHandler",""+i+"ThreadName"+Thread.currentThread().getName());
i++;
if(i%1000==0){
textView.setText(""+i);
}
}
}
});
}</pre>