1.Android Handler面試連環(huán)18連問(wèn) 手寫(xiě)增強(qiáng)版跨進(jìn)程的Handler【至少漲薪10k】

![handler.png](https://upload-images.jianshu.io/upload_images/11218161-d6df8f190bb693cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

本文主要三點(diǎn)目的

第一.面試18連問(wèn)

第二.epoll機(jī)制源碼三分鐘搞定

第三.手寫(xiě)可以跨進(jìn)程的Handler通信方案断序,沒(méi)錯(cuò)跨進(jìn)程。既可以線(xiàn)程通信荒澡,又可以跨進(jìn)程通信**


handler.png

面試連環(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)景刑桑?

handler3.png

1.Handler基本使用


handler3.png

一般情況下,在主線(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)題

  1. 不要阻塞UI線(xiàn)程钮热;
  2. 不要在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ī)制!

  1. 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è)步驟:

  1. handler發(fā)送消息到message queue测暗,這個(gè)消息可能是一個(gè)message央串,可能是一個(gè)runnable
  2. looper負(fù)責(zé)從message queue取消息
  3. looper把消息dispatch給handler
  4. 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)先處理異步消息翼抠。這里很好理解,

同步屏障的處理代碼在MessageQueuenext方法:

刪除屏障消息的方法很簡(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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嘿悬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子水泉,更是在濱河造成了極大的恐慌善涨,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件草则,死亡現(xiàn)場(chǎng)離奇詭異钢拧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)炕横,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)源内,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人份殿,你說(shuō)我怎么就攤上這事膜钓。” “怎么了卿嘲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵颂斜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拾枣,道長(zhǎng)沃疮,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任梅肤,我火速辦了婚禮司蔬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姨蝴。我一直安慰自己俊啼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布似扔。 她就那樣靜靜地躺著吨些,像睡著了一般搓谆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上豪墅,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天泉手,我揣著相機(jī)與錄音,去河邊找鬼偶器。 笑死斩萌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屏轰。 我是一名探鬼主播颊郎,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼霎苗!你這毒婦竟也來(lái)了姆吭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤唁盏,失蹤者是張志新(化名)和其女友劉穎内狸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體厘擂,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昆淡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刽严。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昂灵。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舞萄,靈堂內(nèi)的尸體忽然破棺而出眨补,到底是詐尸還是另有隱情,我是刑警寧澤鹏氧,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布渤涌,位于F島的核電站佩谣,受9級(jí)特大地震影響把还,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茸俭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一吊履、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧调鬓,春花似錦艇炎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)居砖。三九已至,卻和暖如春驴娃,著一層夾襖步出監(jiān)牢的瞬間奏候,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工唇敞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔗草,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓疆柔,卻偏偏與公主長(zhǎng)得像咒精,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子旷档,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容