您好,登錄后才能下訂單哦!
這篇文章主要講解了“Handler的作用有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Handler的作用有哪些”吧!
一種東西被設計出來肯定就有它存在的意義,而Handler
的意義就是切換線程。
作為Android
消息機制的主要成員,它管理著所有與界面有關的消息事件,常見的使用場景有:
比如Activity的啟動,就是AMS在進行進程間通信的時候,通過Binder線程 將消息發送給ApplicationThread
的消息處理者Handler
,然后再將消息分發給主線程中去執行。
當子線程網絡操作之后,需要切換到主線程進行UI更新。
總之一句話,Hanlder
的存在就是為了解決在子線程中無法訪問UI的問題。
因為Android
中的UI控件不是線程安全的,如果多線程訪問UI控件那還不亂套了。
那為什么不加鎖呢?
會降低UI訪問的效率
。本身UI控件就是離用戶比較近的一個組件,加鎖之后自然會發生阻塞,那么UI訪問的效率會降低,最終反應到用戶端就是這個手機有點卡。太復雜了
。本身UI訪問時一個比較簡單的操作邏輯,直接創建UI,修改UI即可。如果加鎖之后就讓這個UI訪問的邏輯變得很復雜,沒必要。所以,Android設計出了 單線程模型
來處理UI操作,再搭配上Handler,是一個比較合適的解決方案。
崩潰發生在ViewRootImpl類的checkThread
方法中:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
其實就是判斷了當前線程 是否是 ViewRootImpl
創建時候的線程,如果不是,就會崩潰。
而ViewRootImpl創建的時機就是界面被繪制的時候,也就是onResume之后,所以如果在子線程進行UI更新,就會發現當前線程(子線程)和View創建的線程(主線程)不是同一個線程,發生崩潰。
解決辦法有三種:
ViewRootImpl
創建之前進行子線程的UI更新,比如onCreate方法中進行子線程更新UI。Handler、view.post
方法。看名字應該是個隊列結構,隊列的特點是什么?先進先出
,一般在隊尾增加數據,在隊首進行取數據或者刪除數據。
那Hanlder
中的消息似乎也滿足這樣的特點,先發的消息肯定就會先被處理。但是,Handler
中還有比較特殊的情況,比如延時消息。
延時消息的存在就讓這個隊列有些特殊性了,并不能完全保證先進先出,而是需要根據時間來判斷,所以Android
中采用了鏈表的形式來實現這個隊列,也方便了數據的插入。
來一起看看消息的發送過程,無論是哪種方法發送消息,都會走到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
方法主要計算了消息需要被處理的時間,如果delayMillis
為0,那么消息的處理時間就是當前時間。
然后就是關鍵方法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字段,也就是代表了這個消息的處理時間鏈表
,找出when小于某個節點的when,找到后插入。好了,其他內容暫且不看,總之,插入消息就是通過消息的執行時間,也就是when
字段,來找到合適的位置插入鏈表。
具體方法就是通過死循環,使用快慢指針p和prev,每次向后移動一格,直到找到某個節點p的when大于我們要插入消息的when字段,則插入到p和prev之間。或者遍歷到鏈表結束,插入到鏈表結尾。
所以,MessageQueue
就是一個用于存儲消息、用鏈表實現的特殊隊列結構。
總結上述內容,延遲消息的實現主要跟消息的統一存儲方法有關,也就是上文說過的enqueueMessage
方法。
無論是即時消息還是延遲消息,都是計算出具體的時間,然后作為消息的when字段進程賦值。
然后在MessageQueue中找到合適的位置(安排when小到大排列),并將消息插入到MessageQueue
中。
這樣,MessageQueue
就是一個按照消息時間排列的一個鏈表結構。
剛才說過了消息的存儲,接下來看看消息的取出,也就是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;
}
}
}
}
奇怪,為什么取消息也是用的死循環呢?
其實死循環就是為了保證一定要返回一條消息,如果沒有可用消息,那么就阻塞在這里,一直到有新消息的到來。
其中,nativePollOnce
方法就是阻塞方法,nextPollTimeoutMillis
參數就是阻塞的時間。
那什么時候會阻塞呢?兩種情況:
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
這時候阻塞時間就是消息時間減去當前時間,然后進入下一次循環,阻塞。
if (msg != null) {}
else {
// No more messages.
nextPollTimeoutMillis = -1;
}
-1
就代表一直阻塞。
接著上文的邏輯,當消息不可用或者沒有消息的時候就會阻塞在next方法,而阻塞的辦法是通過pipe/epoll機制
epoll機制
是一種IO多路復用的機制,具體邏輯就是一個進程可以監視多個描述符,當某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,這個讀寫操作是阻塞的。在Android中,會創建一個Linux管道(Pipe)
來處理阻塞和喚醒。
epoll
機制進入阻塞狀態。其實在Handler
機制中,有三種消息類型:
同步消息
。也就是普通的消息。異步消息
。通過setAsynchronous(true)設置的消息。同步屏障消息
。通過postSyncBarrier方法添加的消息,特點是target為空,也就是沒有對應的handler。這三者之間的關系如何呢?
也就是說同步屏障消息不會被返回,他只是一個標志,一個工具,遇到它就代表要去先行處理異步消息了。
所以同步屏障和異步消息的存在的意義就在于有些消息需要“加急處理”
。
使用場景就很多了,比如繪制方法scheduleTraversals
。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 同步屏障,阻塞所有的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通過 Choreographer 發送繪制任務
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);
在該方法中加入了同步屏障,后續加入一個異步消息MSG_DO_SCHEDULE_CALLBACK
,最后會執行到FrameDisplayEventReceiver
,用于申請VSYNC信號。
再看看loop方法,在消息被分發之后,也就是執行了dispatchMessage
方法之后,還偷偷做了一個操作——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
方法中,釋放了所有資源,然后將當前的空消息插入到sPool表頭。
這里的sPool
就是一個消息對象池,它也是一個鏈表結構的消息,最大長度為50。
那么Message又是怎么復用的呢?在Message的實例化方法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();
}
直接復用消息池sPool
中的第一條消息,然后sPool指向下一個節點,消息池數量減一。
在Handler發送消息之后,消息就被存儲到MessageQueue
中,而Looper
就是一個管理消息隊列的角色。Looper會從MessageQueue
中不斷的查找消息,也就是loop方法,并將消息交回給Handler進行處理。
而Looper的獲取就是通過ThreadLocal
機制:
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();
}
通過prepare
方法創建Looper并且加入到sThreadLocal中,通過myLooper
方法從sThreadLocal中獲取Looper。
下面就具體說說ThreadLocal
運行機制。
//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方法可以大致看出來,有一個ThreadLocalMap
變量,這個變量存儲著鍵值對形式的數據。
key
為this,也就是當前ThreadLocal變量。value
為T,也就是要存儲的值。然后繼續看看ThreadLocalMap
哪來的,也就是getMap方法:
//ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
原來這個ThreadLocalMap
變量是存儲在線程類Thread中的。
所以ThreadLocal
的基本機制就搞清楚了:
在每個線程中都有一個threadLocals變量,這個變量存儲著ThreadLocal和對應的需要保存的對象。
這樣帶來的好處就是,在不同的線程,訪問同一個ThreadLocal對象,但是能獲取到的值卻不一樣。
挺神奇的是不是,其實就是其內部獲取到的Map不同,Map和Thread綁定,所以雖然訪問的是同一個ThreadLocal
對象,但是訪問的Map卻不是同一個,所以取得值也不一樣。
這樣做有什么好處呢?為什么不直接用Map存儲線程和對象呢?
打個比方:
ThreadLocal
就是老師。Thread
就是同學。Looper
(需要的值)就是鉛筆。現在老師買了一批鉛筆,然后想把這些鉛筆發給同學們,怎么發呢?兩種辦法:
這種做法就是Map里面存儲的是同學和鉛筆
,然后用的時候通過同學來從這個Map里找鉛筆。
這種做法就有點像使用一個Map,存儲所有的線程和對象,不好的地方就在于會很混亂,每個線程之間有了聯系,也容易造成內存泄漏。
這種做法就是Map里面存儲的是老師和鉛筆
,然后用的時候老師說一聲,同學只需要從口袋里拿出來就行了。
很明顯這種做法更科學,這也就是ThreadLocal
的做法,因為鉛筆本身就是同學自己在用,所以一開始就把鉛筆交給同學自己保管是最好的,每個同學之間進行隔離。
比如: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
中斷信號。
所以這里使用ThreadLocal
更多的意義在于完成線程單例的功能。
Looper的創建是通過Looper.prepare
方法實現的,而在prepare方法中就判斷了,當前線程是否存在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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
所以同一個線程,只能創建一個Looper
,多次創建會報錯。
按照字面意思就是是否允許退出,我們看看他都在哪些地方用到了:
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();
}
}
}
哦,就是這個quit
方法用到了,如果這個字段為false
,代表不允許退出,就會報錯。
但是這個quit
方法又是干嘛的呢?從來沒用過呢。還有這個safe
又是啥呢?
其實看名字就差不多能了解了,quit方法就是退出消息隊列,終止消息循環。
mQuitting
字段為true。removeAllFutureMessagesLocked
方法,它內部的邏輯是清空所有的延遲消息,之前沒處理的非延遲消息還是需要取處理,然后設置非延遲消息的下一個節點為空(p.next=null)。removeAllMessagesLocked
方法,直接清空所有的消息,然后設置消息隊列指向空(mMessages = null)然后看看當調用quit方法之后,消息的發送和處理:
//消息發送
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;
}
}
當調用了quit方法之后,mQuitting
為true,消息就發不出去了,會報錯。
再看看消息的處理,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;
}
}
}
很明顯,當mQuitting
為true的時候,next方法返回null,那么loop方法中就會退出死循環。
那么這個quit
方法一般是什么時候使用呢?
關于這個問題,強烈建議看看Gityuan的回答:https://www.zhihu.com/question/34652589
我大致總結下:
Binder線程(ApplicationThread)
,會接受AMS發送來的事件Hanlder
再進行消息分發。所以Activity的生命周期都是依靠主線程的
Looper.loop
,當收到不同Message時則采用相應措施,比如收到
msg=H.LAUNCH_ACTIVITY
,則調用
ActivityThread.handleLaunchActivity()
方法,最終執行到onCreate方法。queue.next()
中的
nativePollOnce()
方法里,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生。所以死循環也不會特別消耗CPU資源。 在loop方法中,找到要處理的Message
,然后調用了這么一句代碼處理消息:
msg.target.dispatchMessage(msg);
所以是將消息交給了msg.target
來處理,那么這個target是啥呢?
找找它的來頭:
//Handler
private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
在使用Hanlder發送消息的時候,會設置msg.target = this
,所以target就是當初把消息加到消息隊列的那個Handler。
Hanlder中主要的發送消息可以分為兩種:
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;
}
通過post的源碼可知,其實post和sendMessage
的區別就在于:
post方法給Message設置了一個callback
。
那么這個callback有什么用呢?我們再轉到消息處理的方法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
不為空,也就是通過post方法發送消息的時候,會把消息交給這個msg.callback進行處理,然后就沒有后續了。msg.callback
為空,也就是通過sendMessage發送消息的時候,會判斷Handler當前的mCallback是否為空,如果不為空就交給Handler.Callback.handleMessage處理。mCallback.handleMessage
返回true,則無后續了。mCallback.handleMessage
返回false,則調用handler類重寫的handleMessage方法。所以post(Runnable) 與 sendMessage的區別就在于后續消息的處理方式,是交給msg.callback
還是 Handler.Callback
或者Handler.handleMessage
。
接著上面的代碼說,這兩個處理方法的區別在于Handler.Callback.handleMessage
方法是否返回true:
true
,則不再執行Handler.handleMessagefalse
,則兩個方法都要執行。那么什么時候有Callback
,什么時候沒有呢?這涉及到兩種Hanlder的 創建方式:
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種,派生一個Handler的子類并重寫handleMessage方法。而第2種就是系統給我們提供了一種不需要派生子類的使用方法,只需要傳入一個Callback即可。
Looper
對象,所以線程和Looper是一一對應的。MessageQueue
對象是在new Looper的時候創建的,所以Looper和MessageQueue是一一對應的。Handler
的作用只是將消息加到MessageQueue中,并后續取出消息后,根據消息的target字段分發給當初的那個handler,所以Handler對于Looper是可以多對一的,也就是多個Hanlder對象都可以用同一個線程、同一個Looper、同一個MessageQueue。總結:Looper、MessageQueue、線程是一一對應關系,而他們與Handler是可以一對多的。
主要做了兩件事:
Looper
和
MessageQueue
,并且調用loop方法開啟了主線程的消息循環。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;
之前說過,當MessageQueue
沒有消息的時候,就會阻塞在next方法中,其實在阻塞之前,MessageQueue
還會做一件事,就是檢查是否存在IdleHandler
,如果有,就會去執行它的queueIdle
方法。
private IdleHandler[] mPendingIdleHandlers;
Message next() {
int pendingIdleHandlerCount = -1;
for (;;) {
synchronized (this) {
//當消息執行完畢,就設置pendingIdleHandlerCount
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//初始化mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//mIdleHandlers轉為數組
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 遍歷數組,處理每個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,則處理完就刪除這個IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
}
}
當沒有消息處理的時候,就會去處理這個mIdleHandlers
集合里面的每個IdleHandler
對象,并調用其queueIdle
方法。最后根據queueIdle
返回值判斷是否用完刪除當前的IdleHandler
。
然后看看IdleHandler
是怎么加進去的:
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
就是當消息隊列里面沒有當前要處理的消息了,需要堵塞之前,可以做一些空閑任務的處理。
常見的使用場景有:啟動優化
。
我們一般會把一些事件(比如界面view的繪制、賦值)放到onCreate
方法或者onResume
方法中。但是這兩個方法其實都是在界面繪制之前調用的,也就是說一定程度上這兩個方法的耗時會影響到啟動時間。
所以我們可以把一些操作放到IdleHandler
中,也就是界面繪制完成之后才去調用,這樣就能減少啟動時間了。
但是,這里需要注意下可能會有坑。
如果使用不當,IdleHandler
會一直不執行,比如在View的onDraw方法
里面無限制的直接或者間接調用View的invalidate方法
。
其原因就在于onDraw方法中執行invalidate
,會添加一個同步屏障消息,在等到異步消息之前,會阻塞在next方法,而等到FrameDisplayEventReceiver
異步任務之后又會執行onDraw方法,從而無限循環。
直接看源碼:
public class HandlerThread extends Thread {
@Override
public void run() {
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
}
哦,原來如此。HandlerThread
就是一個封裝了Looper的Thread類。
就是為了讓我們在子線程里面更方便的使用Handler。
這里的加鎖就是為了保證線程安全,獲取當前線程的Looper對象,獲取成功之后再通過notifyAll
方法喚醒其他線程,那哪里調用了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創建好,那邊創建好之后再通知這邊正確返回Looper。
老規矩,直接看源碼:
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);
}
理一下這個源碼:
Service
HandlerThread
,也就是有完整的Looper在運行。ServiceHandler
。onHandleIntent
方法。stopSelf
停止當前Service。所以,這就是一個可以在子線程進行耗時任務,并且在任務執行后自動停止的Service。
BlockCanary
是一個用來檢測應用卡頓耗時的三方庫。
上文說過,View的繪制也是通過Handler來執行的,所以如果能知道每次Handler處理消息的時間,就能知道每次繪制的耗時了?那Handler消息的處理時間怎么獲取呢?
再去loop方法中找找細節:
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);
}
}
}
可以發現,loop方法內有一個Printer
類,在dispatchMessage
處理消息的前后分別打印了兩次日志。
那我們把這個日志類Printer
替換成我們自己的Printer
,然后統計兩次打印日志的時間不就相當于處理消息的時間了?
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
這就是BlockCanary的原理。
這也是常常被問的一個問題,Handler
內存泄露的原因是什么?
"內部類持有了外部類的引用,也就是Hanlder持有了Activity的引用,從而導致無法被回收唄。"
其實這樣回答是錯誤的,或者說沒回答到點子上。
我們必須找到那個最終的引用者,不會被回收的引用者,其實就是主線程,這條完整引用鏈應該是這樣:
主線程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
具體分析可以看看我之前寫的這篇文章:https://juejin.cn/post/6909362503898595342
主線程崩潰,其實都是發生在消息的處理內,包括生命周期、界面繪制。
所以如果我們能控制這個過程,并且在發生崩潰后重新開啟消息循環,那么主線程就能繼續運行。
Handler(Looper.getMainLooper()).post {
while (true) {
//主線程異常攔截
try {
Looper.loop()
} catch (e: Throwable) {
}
}
}
感謝各位的閱讀,以上就是“Handler的作用有哪些”的內容了,經過本文的學習后,相信大家對Handler的作用有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。