您好,登錄后才能下訂單哦!
這篇文章主要講解了“Handler的作用有哪些”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Handler的作用有哪些”吧!
一種東西被設(shè)計(jì)出來(lái)肯定就有它存在的意義,而Handler
的意義就是切換線程。
作為Android
消息機(jī)制的主要成員,它管理著所有與界面有關(guān)的消息事件,常見(jiàn)的使用場(chǎng)景有:
比如Activity的啟動(dòng),就是AMS在進(jìn)行進(jìn)程間通信的時(shí)候,通過(guò)Binder線程 將消息發(fā)送給ApplicationThread
的消息處理者Handler
,然后再將消息分發(fā)給主線程中去執(zhí)行。
當(dāng)子線程網(wǎng)絡(luò)操作之后,需要切換到主線程進(jìn)行UI更新。
總之一句話,Hanlder
的存在就是為了解決在子線程中無(wú)法訪問(wèn)UI的問(wèn)題。
因?yàn)?code>Android中的UI控件不是線程安全的,如果多線程訪問(wèn)UI控件那還不亂套了。
那為什么不加鎖呢?
會(huì)降低UI訪問(wèn)的效率
。本身UI控件就是離用戶比較近的一個(gè)組件,加鎖之后自然會(huì)發(fā)生阻塞,那么UI訪問(wèn)的效率會(huì)降低,最終反應(yīng)到用戶端就是這個(gè)手機(jī)有點(diǎn)卡。太復(fù)雜了
。本身UI訪問(wèn)時(shí)一個(gè)比較簡(jiǎn)單的操作邏輯,直接創(chuàng)建UI,修改UI即可。如果加鎖之后就讓這個(gè)UI訪問(wèn)的邏輯變得很復(fù)雜,沒(méi)必要。所以,Android設(shè)計(jì)出了 單線程模型
來(lái)處理UI操作,再搭配上Handler,是一個(gè)比較合適的解決方案。
崩潰發(fā)生在ViewRootImpl類的checkThread
方法中:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
其實(shí)就是判斷了當(dāng)前線程 是否是 ViewRootImpl
創(chuàng)建時(shí)候的線程,如果不是,就會(huì)崩潰。
而ViewRootImpl創(chuàng)建的時(shí)機(jī)就是界面被繪制的時(shí)候,也就是onResume之后,所以如果在子線程進(jìn)行UI更新,就會(huì)發(fā)現(xiàn)當(dāng)前線程(子線程)和View創(chuàng)建的線程(主線程)不是同一個(gè)線程,發(fā)生崩潰。
解決辦法有三種:
ViewRootImpl
創(chuàng)建之前進(jìn)行子線程的UI更新,比如onCreate方法中進(jìn)行子線程更新UI。Handler、view.post
方法。看名字應(yīng)該是個(gè)隊(duì)列結(jié)構(gòu),隊(duì)列的特點(diǎn)是什么?先進(jìn)先出
,一般在隊(duì)尾增加數(shù)據(jù),在隊(duì)首進(jìn)行取數(shù)據(jù)或者刪除數(shù)據(jù)。
那Hanlder
中的消息似乎也滿足這樣的特點(diǎn),先發(fā)的消息肯定就會(huì)先被處理。但是,Handler
中還有比較特殊的情況,比如延時(shí)消息。
延時(shí)消息的存在就讓這個(gè)隊(duì)列有些特殊性了,并不能完全保證先進(jìn)先出,而是需要根據(jù)時(shí)間來(lái)判斷,所以Android
中采用了鏈表的形式來(lái)實(shí)現(xiàn)這個(gè)隊(duì)列,也方便了數(shù)據(jù)的插入。
來(lái)一起看看消息的發(fā)送過(guò)程,無(wú)論是哪種方法發(fā)送消息,都會(huì)走到sendMessageDelayed
方法
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);
}
sendMessageDelayed
方法主要計(jì)算了消息需要被處理的時(shí)間,如果delayMillis
為0,那么消息的處理時(shí)間就是當(dāng)前時(shí)間。
然后就是關(guān)鍵方法enqueueMessage
。
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
不懂得地方先不看,只看我們想看的:
Message
的when字段,也就是代表了這個(gè)消息的處理時(shí)間鏈表
,找出when小于某個(gè)節(jié)點(diǎn)的when,找到后插入。好了,其他內(nèi)容暫且不看,總之,插入消息就是通過(guò)消息的執(zhí)行時(shí)間,也就是when
字段,來(lái)找到合適的位置插入鏈表。
具體方法就是通過(guò)死循環(huán),使用快慢指針p和prev,每次向后移動(dòng)一格,直到找到某個(gè)節(jié)點(diǎn)p的when大于我們要插入消息的when字段,則插入到p和prev之間?;蛘弑闅v到鏈表結(jié)束,插入到鏈表結(jié)尾。
所以,MessageQueue
就是一個(gè)用于存儲(chǔ)消息、用鏈表實(shí)現(xiàn)的特殊隊(duì)列結(jié)構(gòu)。
總結(jié)上述內(nèi)容,延遲消息的實(shí)現(xiàn)主要跟消息的統(tǒng)一存儲(chǔ)方法有關(guān),也就是上文說(shuō)過(guò)的enqueueMessage
方法。
無(wú)論是即時(shí)消息還是延遲消息,都是計(jì)算出具體的時(shí)間,然后作為消息的when字段進(jìn)程賦值。
然后在MessageQueue中找到合適的位置(安排when小到大排列),并將消息插入到MessageQueue
中。
這樣,MessageQueue
就是一個(gè)按照消息時(shí)間排列的一個(gè)鏈表結(jié)構(gòu)。
剛才說(shuō)過(guò)了消息的存儲(chǔ),接下來(lái)看看消息的取出,也就是queue.next
方法。
Message next() {
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) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
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;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
}
奇怪,為什么取消息也是用的死循環(huán)呢?
其實(shí)死循環(huán)就是為了保證一定要返回一條消息,如果沒(méi)有可用消息,那么就阻塞在這里,一直到有新消息的到來(lái)。
其中,nativePollOnce
方法就是阻塞方法,nextPollTimeoutMillis
參數(shù)就是阻塞的時(shí)間。
那什么時(shí)候會(huì)阻塞呢??jī)煞N情況:
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
這時(shí)候阻塞時(shí)間就是消息時(shí)間減去當(dāng)前時(shí)間,然后進(jìn)入下一次循環(huán),阻塞。
if (msg != null) {}
else {
// No more messages.
nextPollTimeoutMillis = -1;
}
-1
就代表一直阻塞。
接著上文的邏輯,當(dāng)消息不可用或者沒(méi)有消息的時(shí)候就會(huì)阻塞在next方法,而阻塞的辦法是通過(guò)pipe/epoll機(jī)制
epoll機(jī)制
是一種IO多路復(fù)用的機(jī)制,具體邏輯就是一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,當(dāng)某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作,這個(gè)讀寫操作是阻塞的。在Android中,會(huì)創(chuàng)建一個(gè)Linux管道(Pipe)
來(lái)處理阻塞和喚醒。
epoll
機(jī)制進(jìn)入阻塞狀態(tài)。其實(shí)在Handler
機(jī)制中,有三種消息類型:
同步消息
。也就是普通的消息。異步消息
。通過(guò)setAsynchronous(true)設(shè)置的消息。同步屏障消息
。通過(guò)postSyncBarrier方法添加的消息,特點(diǎn)是target為空,也就是沒(méi)有對(duì)應(yīng)的handler。這三者之間的關(guān)系如何呢?
也就是說(shuō)同步屏障消息不會(huì)被返回,他只是一個(gè)標(biāo)志,一個(gè)工具,遇到它就代表要去先行處理異步消息了。
所以同步屏障和異步消息的存在的意義就在于有些消息需要“加急處理”
。
使用場(chǎng)景就很多了,比如繪制方法scheduleTraversals
。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 同步屏障,阻塞所有的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通過(guò) Choreographer 發(fā)送繪制任務(wù)
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
在該方法中加入了同步屏障,后續(xù)加入一個(gè)異步消息MSG_DO_SCHEDULE_CALLBACK
,最后會(huì)執(zhí)行到FrameDisplayEventReceiver
,用于申請(qǐng)VSYNC信號(hào)。
再看看loop方法,在消息被分發(fā)之后,也就是執(zhí)行了dispatchMessage
方法之后,還偷偷做了一個(gè)操作——recycleUnchecked
。
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
}
msg.recycleUnchecked();
}
}
//Message.java
private static Message sPool;
private static final int MAX_POOL_SIZE = 50;
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
在recycleUnchecked
方法中,釋放了所有資源,然后將當(dāng)前的空消息插入到sPool表頭。
這里的sPool
就是一個(gè)消息對(duì)象池,它也是一個(gè)鏈表結(jié)構(gòu)的消息,最大長(zhǎng)度為50。
那么Message又是怎么復(fù)用的呢?在Message的實(shí)例化方法obtain
中:
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ù)用消息池sPool
中的第一條消息,然后sPool指向下一個(gè)節(jié)點(diǎn),消息池?cái)?shù)量減一。
在Handler發(fā)送消息之后,消息就被存儲(chǔ)到MessageQueue
中,而Looper
就是一個(gè)管理消息隊(duì)列的角色。Looper會(huì)從MessageQueue
中不斷的查找消息,也就是loop方法,并將消息交回給Handler進(jìn)行處理。
而Looper的獲取就是通過(guò)ThreadLocal
機(jī)制:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
通過(guò)prepare
方法創(chuàng)建Looper并且加入到sThreadLocal中,通過(guò)myLooper
方法從sThreadLocal中獲取Looper。
下面就具體說(shuō)說(shuō)ThreadLocal
運(yùn)行機(jī)制。
//ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
從ThreadLocal
類中的get和set方法可以大致看出來(lái),有一個(gè)ThreadLocalMap
變量,這個(gè)變量存儲(chǔ)著鍵值對(duì)形式的數(shù)據(jù)。
key
為this,也就是當(dāng)前ThreadLocal變量。value
為T,也就是要存儲(chǔ)的值。然后繼續(xù)看看ThreadLocalMap
哪來(lái)的,也就是getMap方法:
//ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
原來(lái)這個(gè)ThreadLocalMap
變量是存儲(chǔ)在線程類Thread中的。
所以ThreadLocal
的基本機(jī)制就搞清楚了:
在每個(gè)線程中都有一個(gè)threadLocals變量,這個(gè)變量存儲(chǔ)著ThreadLocal和對(duì)應(yīng)的需要保存的對(duì)象。
這樣帶來(lái)的好處就是,在不同的線程,訪問(wèn)同一個(gè)ThreadLocal對(duì)象,但是能獲取到的值卻不一樣。
挺神奇的是不是,其實(shí)就是其內(nèi)部獲取到的Map不同,Map和Thread綁定,所以雖然訪問(wèn)的是同一個(gè)ThreadLocal
對(duì)象,但是訪問(wèn)的Map卻不是同一個(gè),所以取得值也不一樣。
這樣做有什么好處呢?為什么不直接用Map存儲(chǔ)線程和對(duì)象呢?
打個(gè)比方:
ThreadLocal
就是老師。Thread
就是同學(xué)。Looper
(需要的值)就是鉛筆。現(xiàn)在老師買了一批鉛筆,然后想把這些鉛筆發(fā)給同學(xué)們,怎么發(fā)呢??jī)煞N辦法:
這種做法就是Map里面存儲(chǔ)的是同學(xué)和鉛筆
,然后用的時(shí)候通過(guò)同學(xué)來(lái)從這個(gè)Map里找鉛筆。
這種做法就有點(diǎn)像使用一個(gè)Map,存儲(chǔ)所有的線程和對(duì)象,不好的地方就在于會(huì)很混亂,每個(gè)線程之間有了聯(lián)系,也容易造成內(nèi)存泄漏。
這種做法就是Map里面存儲(chǔ)的是老師和鉛筆
,然后用的時(shí)候老師說(shuō)一聲,同學(xué)只需要從口袋里拿出來(lái)就行了。
很明顯這種做法更科學(xué),這也就是ThreadLocal
的做法,因?yàn)殂U筆本身就是同學(xué)自己在用,所以一開(kāi)始就把鉛筆交給同學(xué)自己保管是最好的,每個(gè)同學(xué)之間進(jìn)行隔離。
比如:Choreographer。
public final class Choreographer {
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
private static volatile Choreographer mMainInstance;
Choreographer
主要是主線程用的,用于配合 VSYNC
中斷信號(hào)。
所以這里使用ThreadLocal
更多的意義在于完成線程單例的功能。
Looper的創(chuàng)建是通過(guò)Looper.prepare
方法實(shí)現(xiàn)的,而在prepare方法中就判斷了,當(dāng)前線程是否存在Looper對(duì)象,如果有,就會(huì)直接拋出異常:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
所以同一個(gè)線程,只能創(chuàng)建一個(gè)Looper
,多次創(chuàng)建會(huì)報(bào)錯(cuò)。
按照字面意思就是是否允許退出,我們看看他都在哪些地方用到了:
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();
}
}
}
哦,就是這個(gè)quit
方法用到了,如果這個(gè)字段為false
,代表不允許退出,就會(huì)報(bào)錯(cuò)。
但是這個(gè)quit
方法又是干嘛的呢?從來(lái)沒(méi)用過(guò)呢。還有這個(gè)safe
又是啥呢?
其實(shí)看名字就差不多能了解了,quit方法就是退出消息隊(duì)列,終止消息循環(huán)。
mQuitting
字段為true。removeAllFutureMessagesLocked
方法,它內(nèi)部的邏輯是清空所有的延遲消息,之前沒(méi)處理的非延遲消息還是需要取處理,然后設(shè)置非延遲消息的下一個(gè)節(jié)點(diǎn)為空(p.next=null)。removeAllMessagesLocked
方法,直接清空所有的消息,然后設(shè)置消息隊(duì)列指向空(mMessages = null)然后看看當(dāng)調(diào)用quit方法之后,消息的發(fā)送和處理:
//消息發(fā)送
boolean enqueueMessage(Message msg, long when) {
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;
}
}
當(dāng)調(diào)用了quit方法之后,mQuitting
為true,消息就發(fā)不出去了,會(huì)報(bào)錯(cuò)。
再看看消息的處理,loop和next方法:
Message next() {
for (;;) {
synchronized (this) {
if (mQuitting) {
dispose();
return null;
}
}
}
}
public static void loop() {
for (;;) {
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
}
}
很明顯,當(dāng)mQuitting
為true的時(shí)候,next方法返回null,那么loop方法中就會(huì)退出死循環(huán)。
那么這個(gè)quit
方法一般是什么時(shí)候使用呢?
關(guān)于這個(gè)問(wèn)題,強(qiáng)烈建議看看Gityuan的回答:https://www.zhihu.com/question/34652589
我大致總結(jié)下:
Binder線程(ApplicationThread)
,會(huì)接受AMS發(fā)送來(lái)的事件Hanlder
再進(jìn)行消息分發(fā)。所以Activity的生命周期都是依靠主線程的
Looper.loop
,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施,比如收到
msg=H.LAUNCH_ACTIVITY
,則調(diào)用
ActivityThread.handleLaunchActivity()
方法,最終執(zhí)行到onCreate方法。queue.next()
中的
nativePollOnce()
方法里,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生。所以死循環(huán)也不會(huì)特別消耗CPU資源。 在loop方法中,找到要處理的Message
,然后調(diào)用了這么一句代碼處理消息:
msg.target.dispatchMessage(msg);
所以是將消息交給了msg.target
來(lái)處理,那么這個(gè)target是啥呢?
找找它的來(lái)頭:
//Handler
private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
在使用Hanlder發(fā)送消息的時(shí)候,會(huì)設(shè)置msg.target = this
,所以target就是當(dāng)初把消息加到消息隊(duì)列的那個(gè)Handler。
Hanlder中主要的發(fā)送消息可以分為兩種:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
通過(guò)post的源碼可知,其實(shí)post和sendMessage
的區(qū)別就在于:
post方法給Message設(shè)置了一個(gè)callback
。
那么這個(gè)callback有什么用呢?我們?cè)俎D(zhuǎn)到消息處理的方法dispatchMessage
中看看:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
這段代碼可以分為三部分看:
msg.callback
不為空,也就是通過(guò)post方法發(fā)送消息的時(shí)候,會(huì)把消息交給這個(gè)msg.callback進(jìn)行處理,然后就沒(méi)有后續(xù)了。msg.callback
為空,也就是通過(guò)sendMessage發(fā)送消息的時(shí)候,會(huì)判斷Handler當(dāng)前的mCallback是否為空,如果不為空就交給Handler.Callback.handleMessage處理。mCallback.handleMessage
返回true,則無(wú)后續(xù)了。mCallback.handleMessage
返回false,則調(diào)用handler類重寫的handleMessage方法。所以post(Runnable) 與 sendMessage的區(qū)別就在于后續(xù)消息的處理方式,是交給msg.callback
還是 Handler.Callback
或者Handler.handleMessage
。
接著上面的代碼說(shuō),這兩個(gè)處理方法的區(qū)別在于Handler.Callback.handleMessage
方法是否返回true:
true
,則不再執(zhí)行Handler.handleMessagefalse
,則兩個(gè)方法都要執(zhí)行。那么什么時(shí)候有Callback
,什么時(shí)候沒(méi)有呢?這涉及到兩種Hanlder的 創(chuàng)建方式:
val handler1= object : Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
val handler2 = Handler(object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
return true
}
})
常用的方法就是第1種,派生一個(gè)Handler的子類并重寫handleMessage方法。而第2種就是系統(tǒng)給我們提供了一種不需要派生子類的使用方法,只需要傳入一個(gè)Callback即可。
Looper
對(duì)象,所以線程和Looper是一一對(duì)應(yīng)的。MessageQueue
對(duì)象是在new Looper的時(shí)候創(chuàng)建的,所以Looper和MessageQueue是一一對(duì)應(yīng)的。Handler
的作用只是將消息加到MessageQueue中,并后續(xù)取出消息后,根據(jù)消息的target字段分發(fā)給當(dāng)初的那個(gè)handler,所以Handler對(duì)于Looper是可以多對(duì)一的,也就是多個(gè)Hanlder對(duì)象都可以用同一個(gè)線程、同一個(gè)Looper、同一個(gè)MessageQueue。總結(jié):Looper、MessageQueue、線程是一一對(duì)應(yīng)關(guān)系,而他們與Handler是可以一對(duì)多的。
主要做了兩件事:
Looper
和
MessageQueue
,并且調(diào)用loop方法開(kāi)啟了主線程的消息循環(huán)。public static void main(String[] args) {
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
final H mH = new H();
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int STOP_SERVICE = 116;
public static final int BIND_SERVICE = 121;
之前說(shuō)過(guò),當(dāng)MessageQueue
沒(méi)有消息的時(shí)候,就會(huì)阻塞在next方法中,其實(shí)在阻塞之前,MessageQueue
還會(huì)做一件事,就是檢查是否存在IdleHandler
,如果有,就會(huì)去執(zhí)行它的queueIdle
方法。
private IdleHandler[] mPendingIdleHandlers;
Message next() {
int pendingIdleHandlerCount = -1;
for (;;) {
synchronized (this) {
//當(dāng)消息執(zhí)行完畢,就設(shè)置pendingIdleHandlerCount
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//初始化mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//mIdleHandlers轉(zhuǎn)為數(shù)組
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 遍歷數(shù)組,處理每個(gè)IdleHandler
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);
}
//如果queueIdle方法返回false,則處理完就刪除這個(gè)IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
}
}
當(dāng)沒(méi)有消息處理的時(shí)候,就會(huì)去處理這個(gè)mIdleHandlers
集合里面的每個(gè)IdleHandler
對(duì)象,并調(diào)用其queueIdle
方法。最后根據(jù)queueIdle
返回值判斷是否用完刪除當(dāng)前的IdleHandler
。
然后看看IdleHandler
是怎么加進(jìn)去的:
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
//做事情
return false;
}
});
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
ok,綜上所述,IdleHandler
就是當(dāng)消息隊(duì)列里面沒(méi)有當(dāng)前要處理的消息了,需要堵塞之前,可以做一些空閑任務(wù)的處理。
常見(jiàn)的使用場(chǎng)景有:啟動(dòng)優(yōu)化
。
我們一般會(huì)把一些事件(比如界面view的繪制、賦值)放到onCreate
方法或者onResume
方法中。但是這兩個(gè)方法其實(shí)都是在界面繪制之前調(diào)用的,也就是說(shuō)一定程度上這兩個(gè)方法的耗時(shí)會(huì)影響到啟動(dòng)時(shí)間。
所以我們可以把一些操作放到IdleHandler
中,也就是界面繪制完成之后才去調(diào)用,這樣就能減少啟動(dòng)時(shí)間了。
但是,這里需要注意下可能會(huì)有坑。
如果使用不當(dāng),IdleHandler
會(huì)一直不執(zhí)行,比如在View的onDraw方法
里面無(wú)限制的直接或者間接調(diào)用View的invalidate方法
。
其原因就在于onDraw方法中執(zhí)行invalidate
,會(huì)添加一個(gè)同步屏障消息,在等到異步消息之前,會(huì)阻塞在next方法,而等到FrameDisplayEventReceiver
異步任務(wù)之后又會(huì)執(zhí)行onDraw方法,從而無(wú)限循環(huán)。
直接看源碼:
public class HandlerThread extends Thread {
@Override
public void run() {
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
}
哦,原來(lái)如此。HandlerThread
就是一個(gè)封裝了Looper的Thread類。
就是為了讓我們?cè)谧泳€程里面更方便的使用Handler。
這里的加鎖就是為了保證線程安全,獲取當(dāng)前線程的Looper對(duì)象,獲取成功之后再通過(guò)notifyAll
方法喚醒其他線程,那哪里調(diào)用了wait
方法呢?
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
就是getLooper
方法,所以wait的意思就是等待Looper創(chuàng)建好,那邊創(chuàng)建好之后再通知這邊正確返回Looper。
老規(guī)矩,直接看源碼:
public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
理一下這個(gè)源碼:
Service
HandlerThread
,也就是有完整的Looper在運(yùn)行。ServiceHandler
。onHandleIntent
方法。stopSelf
停止當(dāng)前Service。所以,這就是一個(gè)可以在子線程進(jìn)行耗時(shí)任務(wù),并且在任務(wù)執(zhí)行后自動(dòng)停止的Service。
BlockCanary
是一個(gè)用來(lái)檢測(cè)應(yīng)用卡頓耗時(shí)的三方庫(kù)。
上文說(shuō)過(guò),View的繪制也是通過(guò)Handler來(lái)執(zhí)行的,所以如果能知道每次Handler處理消息的時(shí)間,就能知道每次繪制的耗時(shí)了?那Handler消息的處理時(shí)間怎么獲取呢?
再去loop方法中找找細(xì)節(jié):
public static void loop() {
for (;;) {
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
可以發(fā)現(xiàn),loop方法內(nèi)有一個(gè)Printer
類,在dispatchMessage
處理消息的前后分別打印了兩次日志。
那我們把這個(gè)日志類Printer
替換成我們自己的Printer
,然后統(tǒng)計(jì)兩次打印日志的時(shí)間不就相當(dāng)于處理消息的時(shí)間了?
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
這就是BlockCanary的原理。
這也是常常被問(wèn)的一個(gè)問(wèn)題,Handler
內(nèi)存泄露的原因是什么?
"內(nèi)部類持有了外部類的引用,也就是Hanlder持有了Activity的引用,從而導(dǎo)致無(wú)法被回收唄。"
其實(shí)這樣回答是錯(cuò)誤的,或者說(shuō)沒(méi)回答到點(diǎn)子上。
我們必須找到那個(gè)最終的引用者,不會(huì)被回收的引用者,其實(shí)就是主線程,這條完整引用鏈應(yīng)該是這樣:
主線程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
具體分析可以看看我之前寫的這篇文章:https://juejin.cn/post/6909362503898595342
主線程崩潰,其實(shí)都是發(fā)生在消息的處理內(nèi),包括生命周期、界面繪制。
所以如果我們能控制這個(gè)過(guò)程,并且在發(fā)生崩潰后重新開(kāi)啟消息循環(huán),那么主線程就能繼續(xù)運(yùn)行。
Handler(Looper.getMainLooper()).post {
while (true) {
//主線程異常攔截
try {
Looper.loop()
} catch (e: Throwable) {
}
}
}
感謝各位的閱讀,以上就是“Handler的作用有哪些”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Handler的作用有哪些這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。