前言
Handler屬於八股文中非常經典的一個考題了,導致這個知識點很多時候,考官都懶得問了;這玩意很久之前就看過,但是過了一段時間,就很容易忘記,但是處理內存泄漏,IdleHandler之類的考點答案肯定很難忘。。。雖然考官很多時候不屑問,但是要是問到了,你忘了且不知道怎么回答,那就很尷尬了
鄙人也來炒個剩飯,力求通俗易懂的來描述下Handler機制的整個流程;相關知識點,畫了一些流程圖,時序圖來展示其運行機制,力爭讓本文圖文並茂!
文章中關鍵方法源碼,可以直接點擊方法名,跳轉查看對應方法的源碼
如果看了沒收獲,噴我!
總流程
開頭需要建立個handler作用的總體印象,下面畫了一個總體的流程圖
從上面的流程圖可以看出,總體上是分幾個大塊的
- Looper.prepare()、Handler()、Looper.loop() 總流程
- 收發消息
- 分發消息
相關知識點大概涉及到這些,下面詳細講解下!
- 需要詳細的查看該思維導圖,請右鍵下載后查看
使用
先來看下使用,不然源碼,原理圖搞了一大堆,一時想不起怎么用的,就尷尬了
使用很簡單,此處僅做個展示,大家可以熟悉下
演示代碼盡量簡單是為了演示,關於靜態內部類持有弱引用或者銷毀回調中清空消息隊列之類,就不在此處展示了
- 來看下消息處理的分發方法:dispatchMessage(msg)
Handler.java
...
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
...
從上面源碼可知,handler的使用總的來說,分倆大類,細分三小類
- 收發消息一體
- handleCallback(msg)
- 收發消息分開
- mCallback.handleMessage(msg)
- handleMessage(msg)
收發一體
- handleCallback(msg)
- 使用post形式,收發都是一體,都在post()方法中完成,此處不需要創建Message實例等,post方法已經完成這些操作
public class MainActivity extends AppCompatActivity {
private TextView msgTv;
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgTv = findViewById(R.id.tv_msg);
//消息收發一體
new Thread(new Runnable() {
@Override public void run() {
String info = "第一種方式";
mHandler.post(new Runnable() {
@Override public void run() {
msgTv.setText(info);
}
});
}
}).start();
}
}
收發分開
mCallback.handleMessage(msg)
- 實現Callback接口
public class MainActivity extends AppCompatActivity {
private TextView msgTv;
private Handler mHandler = new Handler(new Handler.Callback() {
//接收消息,刷新UI
@Override public boolean handleMessage(@NonNull Message msg) {
if (msg.what == 1) {
msgTv.setText(msg.obj.toString());
}
//false 重寫Handler類的handleMessage會被調用, true 不會被調用
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgTv = findViewById(R.id.tv_msg);
//發送消息
new Thread(new Runnable() {
@Override public void run() {
Message message = Message.obtain();
message.what = 1;
message.obj = "第二種方式 --- 1";
mHandler.sendMessage(message);
}
}).start();
}
}
handleMessage(msg)
- 重寫Handler類的handlerMessage(msg)方法
public class MainActivity extends AppCompatActivity {
private TextView msgTv;
private Handler mHandler = new Handler() {
//接收消息,刷新UI
@Override public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
msgTv.setText(msg.obj.toString());
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgTv = findViewById(R.id.tv_msg);
//發送消息
new Thread(new Runnable() {
@Override public void run() {
Message message = Message.obtain();
message.what = 1;
message.obj = "第二種方式 --- 2";
mHandler.sendMessage(message);
}
}).start();
}
}
prepare和loop
大家肯定有印象,在子線程和子線程的通信中,就必須在子線程中初始化Handler,必須這樣寫
- prepare在前,loop在后,固化印象了
new Thread(new Runnable() {
@Override public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
});
- 為啥主線程不需要這樣寫,聰明你肯定想到了,在入口出肯定做了這樣的事
ActivityThread.java
...
public static void main(String[] args) {
...
//主線程Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//主線程的loop開始循環
Looper.loop();
...
}
...
為什么要使用prepare和loop?我畫了個圖,先讓大家有個整體印象
- 上圖的流程,鄙人感覺整體畫的還是比較清楚的
- 總結下就是
- Looper.prepare():生成Looper對象,set在ThreadLocal里
- handler構造函數:通過Looper.myLooper()獲取到ThreadLocal的Looper對象
- Looper.loop():內部有個死循環,開始事件分發了;這也是最復雜,干活最多的方法
具體看下每個步驟的源碼,這里也會標定好鏈接,方便大家隨時過去查看
- Looper.prepare()
- 可以看見,一個線程內,只能使用一次prepare(),不然會報異常的
Looper.java
...
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
...
- Handler()
- 這里通過Looper.myLooper() ---> sThreadLocal.get()拿到了Looper實例
Handler.java
...
@Deprecated
public Handler() {
this(null, false);
}
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
...
Looper.java
...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
...
- Looper.loop():該方法分析,在
分發消息
里講- 精簡了大量源碼,詳細的可以點擊上面方法名
- Message msg = queue.next():遍歷消息
- msg.target.dispatchMessage(msg):分發消息
- msg.recycleUnchecked():消息回收,進入消息池
Looper.java
...
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
....
msg.recycleUnchecked();
}
}
...
收發消息
收發消息的操作口都在Handler里,這是我們最直觀的接觸的點
下方的思維導圖整體做了個概括
前置知識
在說發送和接受消息之前,必須要先解釋下,Message中一個很重要的屬性:when
when這個變量是Message中的,發送消息的時候,我們一般是不會設置這個屬性的,實際上也無法設置,只有內部包才能訪問寫的操作;將消息加入到消息隊列的時候會給發送的消息設置該屬性。消息加入消息隊列方法:enqueueMessage(...)
在我們使用sendMessage發送消息的時候,實際上也會調用sendMessageDelayed延時發送消息發放,不過此時傳入的延時時間會默認為0,來看下延時方法:sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
這地方調用了sendMessageAtTime方法,此處!做了一個時間相加的操作:SystemClock.uptimeMillis() + delayMillis
- SystemClock.uptimeMillis():這個方法會返回一個毫秒數值,返回的是,打開設備到此刻所消耗的毫秒時間,這很明顯是個相對時間刻!
- delayMillis:就是我們發送的延時毫秒數值
后面會將這個時間刻賦值給when:when = SystemClock.uptimeMillis() + delayMillis
說明when代表的是開機到現在的一個時間刻,通俗的理解,when可以理解為:現實時間的某個現在或未來的時刻(實際上when是個相對時刻,相對點就是開機的時間點)
發送消息
發送消息涉及到倆個方法:post(...)和sendMessage(...)
- post(Runnable):發送和接受消息都在post中完成
- sendMessage(msg):需要自己傳入Message消息對象
- 看下源碼
- 使用post會自動會通過getPostMessage方法創建Message對象
- 在enqueueMessage中將生成的Message加入消息隊列,注意
- 此方法給msg的target賦值當前handler之后,才進行將消息添加的消息隊列的操作
- msg.setAsynchronous(true):設置Message屬性為異步,默認都為同步;設置為異步的條件,需要手動在Handler構造方法里面設置
Handler.java
...
//post
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
//生成Message對象
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
//sendMessage方法
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
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;
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);
}
///將Message加入詳細隊列
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//設置target
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
//設置為異步方法
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
...
- enqueueMessage(...):精簡了一些代碼,完整代碼,可點擊左側方法名
- Message通過enqueueMessage加入消息隊列
- 請明確:when = SystemClock.uptimeMillis() + delayMillis,when代表的是一個時間刻度,消息進入到消息隊列,是按照時間刻度排列的,時間刻度按照從小到大排列,也就是說消息在消息隊列中:按照從現在到未來的循序排隊
- 這地方有幾種情況,記錄下:mMessage為當前消息分發到的消息位置
- mMessage為空,傳入的msg則為消息鏈表頭,next置空
- mMessage不為空、消息隊列中沒有延時消息的情況:從當前分發位置移到鏈表尾,將傳入的msg插到鏈表尾部,next置空
- mMessage不為空、含有延時消息的情況:舉個例子
- A,B,C消息依次發送,三者分邊延時:3秒,1秒,2秒 { A(3000)、B(1000)、C(2000) }
- 這是一種理想情況:三者依次進入,進入之間的時間差小到忽略,這是為了方便演示和說明
- 這種按照時間遠近的循序排列,可以保證未延時或者延時時間較小的消息,能夠被及時執行
- 在消息隊列中的排列為:B ---> C ---> A
MessageQueue.java
...
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) {
// 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;
}
...
- 來看下發送的消息插入消息隊列的圖示
接收消息
接受消息相對而言就簡單多
- dispatchMessage(msg):關鍵方法呀
Handler.java
...
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
...
-
handleCallback(msg)
- 觸發條件:Message消息中實現了handleCallback回調
- 現在基本上只能使用post()方法了,setCallback(Runnable r) 被表明為@UnsupportedAppUsage,被hide了,沒法調用,如果使用反射倒是可以調用,但是沒必要。。。
-
mCallback.handleMessage(msg)
- 觸發條件
- 使用sendMessage方法發送消息(必須)
- 實現Handler的Callback回調
- 分發的消息,會在Handler中實現的回調中分發
- 觸發條件
-
handleMessage(msg)
- 觸發條件
- 使用sendMessage方法發送消息(必須)
- 未實現Handler的Callback回調
- 實現了Handler的Callback回調,返回值為false(mCallback.handleMessage(msg))
- 需要重寫Handler類的handlerMessage方法
- 觸發條件
分發消息
消息分發是在loop()中完成的,來看看loop()這個重要的方法
- Looper.loop():精簡了巨量源碼,詳細的可以點擊左側方法名
- Message msg = queue.next():遍歷消息
- msg.target.dispatchMessage(msg):分發消息
- msg.recycleUnchecked():消息回收,進入消息池
Looper.java
...
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
//遍歷消息池,獲取下一可用消息
Message msg = queue.next(); // might block
...
try {
//分發消息
msg.target.dispatchMessage(msg);
...
} catch (Exception exception) {
...
} finally {
...
}
....
//回收消息,進圖消息池
msg.recycleUnchecked();
}
}
...
遍歷消息
遍歷消息的關鍵方法肯定是下面這個
- Message msg = queue.next():Message類中的next()方法;當然這必須要配合外層for(無限循環)來使用,才能遍歷消息隊列
來看看這個Message中的next()方法吧
- next():精簡了一些源碼,完整的點擊左側方法名
MessageQueue.java
...
Message next() {
final long ptr = mPtr;
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
...
//阻塞,除非到了超時時間或者喚醒
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;
// 這是關於同步屏障(SyncBarrier)的知識,放在同步屏障欄目講
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//每個消息處理有耗時時間,之間存在一個時間間隔(when是將要執行的時間點)。
//如果當前時刻還沒到執行時刻(when),計算時間差值,傳入nativePollOnce定義喚醒阻塞的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
//該操作是把異步消息單獨從消息隊列里面提出來,然后返回,返回之后,該異步消息就從消息隊列里面剔除了
//mMessage仍處於未分發的同步消息位置
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回符合條件的Message
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//這是處理調用IdleHandler的操作,有幾個條件
//1、當前消息隊列為空(mMessages == null)
//2、已經到了可以分發下一消息的時刻(now < mMessages.when)
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);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
總結下源碼里面表達的意思
-
next()內部是個死循環,你可能會疑惑,只是拿下一節點的消息,為啥要死循環?
- 為了執行延時消息以及同步屏障等等,這個死循環是必要的
-
nativePollOnce阻塞方法:到了超時時間(nextPollTimeoutMillis)或者通過喚醒方式(nativeWake),會解除阻塞狀態
- nextPollTimeoutMillis大於等於零,會規定在此段時間內休眠,然后喚醒
- 消息隊列為空時,nextPollTimeoutMillis為-1,進入阻塞;重新有消息進入隊列,插入頭結點的時候會觸發nativeWake喚醒方法
-
如果 msg.target == null為零,會進入同步屏障狀態
- 會將msg消息死循環到末尾節點,除非碰到異步方法
- 如果碰到同步屏障消息,理論上會一直死循環上面操作,並不會返回消息,除非,同步屏障消息被移除消息隊列
-
當前時刻和返回消息的when判定
-
消息when代表的時刻:一般都是發送消息的時刻,如果是延時消息,就是 發送時刻+延時時間
-
當前時刻小於返回消息的when:進入阻塞,計算時間差,給nativePollOnce設置超時時間,超時時間一到,解除阻塞,重新循環取消息
-
當前時刻大於返回消息的when:獲取可用消息返回
-
-
消息返回后,會將mMessage賦值為返回消息的下一節點(只針對不涉及同步屏障的同步消息)
這里簡單的畫了個流程圖
分發消息
分發消息主要的代碼是: msg.target.dispatchMessage(msg);
也就是說這是Handler類中的dispatchMessage(msg)方法
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,這里的代碼,在收發消息欄目的接受消息那塊已經說明過了,這里就無須重復了
消息池
msg.recycleUnchecked()是處理完成分發的消息,完成分發的消息並不會被回收掉,而是會進入消息池,等待被復用
- recycleUnchecked():回收消息的代碼還是蠻簡單的,來分析下
- 首先會將當前已經分發處理的消息,相關屬性全部重置,flags也標志可用
- 消息池的頭結點會賦值為當前回收消息的下一節點,當前消息成為消息池頭結點
- 簡言之:回收消息插入消息池,當做頭結點
- 需要注意的是:消息池有最大的容量,如果消息池大於等於默認設置的最大容量,將不再接受回收消息入池
- 默認最大容量為50: MAX_POOL_SIZE = 50
Message.java
...
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
來看下消息池回收消息圖示
既然有將已使用的消息回收到消息池的操作,那肯定有獲取消息池里面消息的方法了
- obtain():代碼很少,來看看
- 如果消息池不為空:直接取消息池的頭結點,被取走頭結點的下一節點成為消息池的頭結點
- 如果消息池為空:直接返回新的Message實例
Message.java
...
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();
}
來看下從消息池取一個消息的圖示
IdleHandler
在MessageQueue類中的next方法里,可以發現有關於對IdleHandler的處理,大家可千萬別以為它是什么Handler特殊形式之類,這玩意就是一個interface,里面抽象了一個方法,結構非常的簡單
- next():精簡了大量源碼,只保留IdleHandler處理的相關邏輯;完整的點擊左側方法名
MessageQueue.java
...
Message next() {
final long ptr = mPtr;
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
...
//阻塞,除非到了超時時間或者喚醒
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;
...
//這是處理調用IdleHandler的操作,有幾個條件
//1、當前消息隊列為空(mMessages == null)
//2、未到到了可以分發下一消息的時刻(now < mMessages.when)
//3、pendingIdleHandlerCount < 0表明:只會在此for循環里執行一次處理IdleHandler操作
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
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);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
實際上從上面的代碼里面,可以分析出很多信息
IdleHandler相關信息
-
調用條件
- 當前消息隊列為空(mMessages == null) 或 未到分發返回消息的時刻
- 在每次獲取可用消息的死循環中,IdleHandler只會被處理一次:處理一次后pendingIdleHandlerCount為0,其循環不可再被執行
-
實現了IdleHandler中的queueIdle方法
- 返回false,執行后,IdleHandler將會從IdleHandler列表中移除,只能執行一次:默認false
- 返回true,每次分發返回消息的時候,都有機會被執行:處於
保活
狀態
-
IdleHandler代碼
MessageQueue.java ... /** * Callback interface for discovering when a thread is going to block * waiting for more messages. */ public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */ boolean queueIdle(); } public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } } public void removeIdleHandler(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); } }
-
怎么使用IdleHandler呢?
- 這里簡單寫下用法,可以看看,留個印象
public class MainActivity extends AppCompatActivity { private TextView msgTv; private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); msgTv = findViewById(R.id.tv_msg); //添加IdleHandler實現類 mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("我是IdleHandler")); mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("我是大帥比")); //消息收發一體 new Thread(new Runnable() { @Override public void run() { String info = "第一種方式"; mHandler.post(new Runnable() { @Override public void run() { msgTv.setText(info); } }); } }).start(); } //實現IdleHandler類 class InfoIdleHandler implements MessageQueue.IdleHandler { private String msg; InfoIdleHandler(String msg) { this.msg = msg; } @Override public boolean queueIdle() { msgTv.setText(msg); return false; } } }
總結
- 通俗的講:當所有消息處理完了 或者 你發送了延遲消息,在這倆種空閑時間里,都滿足執行IdleHandler的條件
- 這地方需要說明下,如果延遲消息時間設置過短的;IdleHandler可能會在發送消息后執行,畢竟運行到next這步也需要一點時間,延遲時間設置長點,你就可以很明顯得發現,IdleHandler在延遲的空隙間執行了!
- 從其源碼上,可以看出來,IdlerHandler是在消息分發的空閑時刻,專門用來處理相關事物的
同步屏障
來到最復雜的模塊了
在理解同步屏障的概念前,我們需要先搞懂幾個前置知識
前置知識
同步和異步消息
什么是同步消息?什么是異步消息?
- 講真的,異步消息和同步消息界定,完成是通過一個方法去界定的
- isAsynchronous():來分析下
- FLAG_ASYNCHRONOUS = 1 << 1:所以FLAG_ASYNCHRONOUS為2
- 同步消息:flags為0或者1的時候,isAsynchronous返回false,此時該消息標定為同步消息
- flags為0,1:同步消息
- 異步消息:理論上只要按照位操作,右往左,第二位為1的數,isAsynchronous返回true;但是,Message里面基本只使用了:0,1,2,可得出結論
- flags為2:異步消息
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
- setAsynchronous(boolean async):這個方法會影響flags的值
- 因為flags是int類型,沒有賦初值,故其初始值為0
- setAsynchronous傳入true的話,或等於操作,會將flags數值改成2
msg.setAsynchronous(true);
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
- 怎么生成異步消息?so easy
Message msg = Message.obtain();
//設置異步消息標記
msg.setAsynchronous(true);
- 一般來說:默認消息不做設置,flags都為0,故默認為同步消息,下面欄目將分析下setAsynchronous在何處使用了
默認消息類型
我們正常情況下,很少會使用setAsynchronous
方法的,那么在不使用該方法的時候,消息的默認類型是什么呢?
- 在生成消息,然后發送消息的時候,都會經過下述方法
- enqueueMessage:正常發送消息(post、延遲和非延遲之類),都會經過此方法
- 因為發送的所有消息都會經過enqueueMessage方法,然后加入消息隊列,可以看見所有的消息都被處理過
- msg.target = this
- 這地方給Message類的target賦值了!
- 說明:只要使用post或sendMessage之類發送消息,其消息就絕不可能是同步屏障消息!
- 關於同步異步,可以看見和mAsynchronous息息相關
- 只要mAsynchronous為true的話,我們的消息都會異步消息
- 只要mAsynchronous為false的話,我們的消息都會同步消息
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- mAsynchronous在哪設置的呢?
- 這是在構造方法里面給mAsynchronous賦值了
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- 看看一些通用的構造方法
public Handler() {
this(null, false);
}
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
- 總結下
- 這下清楚了!如果不做特殊設置的話:默認消息都是同步消息
- 默認消息都會給其target變量賦值:默認消息都不是同步屏障消息
生成同步屏障消息
在next方法中發現,target為null的消息被稱為同步屏障消息,那他為啥叫同步屏障消息呢?
- postSyncBarrier(long when)
- sync:同步 barrier:屏障,障礙物 ---> 同步屏障
- 同步屏障實際挺能代表其含義的,它能屏蔽消息隊列中后續所有的同步方法分發
MessageQueue.java
...
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
- mMessage這個變量,表明是將要被處理的消息,將要被返回的消息,也可以認為,他是未處理消息隊列的頭結點消息
- 關於同步屏障消息
- 從消息池取一個可用消息
- 這地方有個很有意思的循環操作,這while操作的,會將mMessages頭結點賦值給p變量,將p節點移到當前時刻消息的下一節點
- 頭結點(mMessage)是否為空
- 不為空:因為上面的循環操作,會讓p節點的消息,肯定是剛好大於當前時間刻,p節點的上一節點消息為當前時刻過去時刻的消息,此時!咱們的同步屏障消息msg,就插在這倆者之間!
- 為空:成為頭結點
- 同步屏障消息是直接插到消息隊列,他沒有設置target屬性且不經過enqueueMessage方法,故其target屬性為null
總結下:
同步屏障消息插入消息隊列的規律,和上面正常發送消息插入基本是一致的;如果消息隊列有延時消息,延時消息的時刻大於目前的時刻,同步消息會在這些延時消息之前。
OK,同步屏障消息插入,基本可以理解為:正常的非延時消息插入消息隊列!
- 同步屏障消息插入消息隊列流程圖
同步屏障流程
- next():精簡了大量源碼碼,只保留和同步屏障有關的代碼;完整的點擊左側方法名
MessageQueue.java
...
Message next() {
final long ptr = mPtr;
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
...
//阻塞,除非到了超時時間或者喚醒
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;
// 這是關於同步屏障(SyncBarrier)的邏輯塊
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//每個消息處理有耗時時間,之間存在一個時間間隔(when是將要執行的時間點)。
//如果當前時刻還沒到執行時刻(when),計算時間差值,傳入nativePollOnce定義喚醒阻塞的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
//該操作是把異步消息單獨從消息隊列里面提出來,然后返回,返回之后,該異步消息就從消息隊列里面剔除了
//mMessage仍處於未分發的同步消息位置
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回符合條件的Message
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
...
}
}
去掉大量我們無需關注的代碼,發現這也沒啥嘛,就是一堆if eles for之類的,來分析分析
- Message msg = mMessages:這步賦值是非常重要的,表示即使我們對msg一頓操作,mMessage還是保留消息隊列頭結點消息的位置
- msg.target == null:遇到同步屏障消息
- 首先是一個while循環,內部邏輯,不斷將msg節點的位置后移
- 結束while的倆個條件
- msg移到尾結點,也就是移到了消息隊列尾結點,將自身賦值為null(尾結點的next)
- 遇上標記為異步的消息,放行該消息進行后續分發
- 分析下,倆個放行條件產生的不同影響
- 消息隊列不含異步消息
- 當我們在同步屏障邏輯里面,將msg自身移到尾結點,並賦值為null(尾結點的next)
- msg為null,是無法進行后續分發操作,會重新進行循環流程
- mMessage頭結點重新將自身位置賦值給msg,繼續上述的重復過程
- 可以發現,上述邏輯確實起到了同步屏障的作用,屏蔽了其所有后續同步消息的分發;只有移除消息隊列中的該條同步屏障消息,才能繼續進行同步消息的分發
- 消息隊列含有異步消息
- 消息隊列中如果有異步消息,同步屏障的邏輯會放行異步消息
- 同步屏障里面堆prevMsg賦值了!請記住在整個方法里面,只有同步屏障邏輯里面堆prevMsg賦值了!這個參數為null與否,對消息隊列節點影響很大
- prevMsg為空:會直接將msg的next賦值給mMessage;說明分發完消息后,會直接移除頭結點,將頭結點的下一節點賦值為頭結點
- prevMsg不為空:不會對mMessage投節點操作;會將分發消息的上一節點的下一節點位置,換成分發節點的下一節點,有點繞
- 通過上面分析,可知;異步消息分發完后,會將其直接從消息隊列中移除,頭結點位置不變
- 消息隊列不含異步消息
文字寫了一大堆,我也是盡可能詳細描述,同步屏障邏輯代碼塊會產生的影響,整個圖,加深下印象!
同步屏障作用
那么這個同步屏障有什么作用呢?
有個急需的問題,就是什么地方用到了postSyncBarrier(long when)方法,這個方法對外是不暴露的,只有內部包能夠調用
搜索了整個源碼包,發現只有幾個地方使用了它,剔除測試類,MessageQueue類,有作用的就是:ViewRootImpl類和Device類
Device類
-
pauseEvents():Device內部涉及的是打開設備的時候,會添加一個同步屏障消息,屏蔽后續所有的同步消息處理
- pauseEvents()是Device類中私有內部類DeviceHandler的方法
- 這說明,我們無法調用這個方法;事實上,我們連Device類都無法調用,Device屬於被隱藏的類,和他同一目錄的還有Event和Hid,這些類系統都不想對外暴露
- 這就很雞賊了,說明插入同步屏障的消息的方法,系統確實不想對外暴露;當然不包括非常規方法:反射
- pauseEvents()是Device類中私有內部類DeviceHandler的方法
-
同步屏障添加:開機時,添加同步屏障
Device.java ... private class DeviceHandler extends Handler { ... @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_OPEN_DEVICE: ... pauseEvents(); break; ... } } public void pauseEvents() { mBarrierToken = getLooper().myQueue().postSyncBarrier(); } public void resumeEvents() { getLooper().myQueue().removeSyncBarrier(mBarrierToken); mBarrierToken = 0; } }
-
同步屏障移除:完成開機后,移除同步屏障
Device.java ... private class DeviceHandler extends Handler { ... public void pauseEvents() { mBarrierToken = getLooper().myQueue().postSyncBarrier(); } public void resumeEvents() { getLooper().myQueue().removeSyncBarrier(mBarrierToken); mBarrierToken = 0; } } private class DeviceCallback { public void onDeviceOpen() { mHandler.resumeEvents(); } .... }
-
Device中使用同步屏障整體過程比較簡單,這里簡單描述下
- 打開設備時,會發送一個同步屏障消息,屏蔽后續所有同步消息
- 完成開機后,移除同步屏障消息
- 總結:很明顯,這是盡量的提升打開設備速度,不被其它次等重要的事件干擾
ViewRootImpl類
該欄目的分析,必須引用一個非常重要的結論,給出該結論的文章:源碼分析_Android UI何時刷新_Choreographer
-
scheduleTraversals():非常重要的方法
ViewRootImpl.java ... void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
-
結論:源碼分析_Android UI何時刷新_Choreographer
- 關於上面的方法的分析,整體流程比較麻煩,涉及到整個刷新過程的分析
- 這邊前輩的文章分析完UI刷新流程,給出了一個非常重要的結論
我們調用View的requestLayout或者invalidate時,最終都會觸發ViewRootImp執行scheduleTraversals()方法。這個方法中ViewRootImp會通過Choreographer來注冊個接收Vsync的監聽,當接收到系統體層發送來的Vsync后我們就執行doTraversal()來重新繪制界面。通過上面的分析我們調用invalidate等刷新操作時,系統並不會立即刷新界面,而是等到Vsync消息后才會刷新頁面。
我們這邊已經有了前輩給出的結論,我們知道了界面刷新(requestLayout或者invalidate)的過程一定會觸發scheduleTraversals()方法,這說明會添加同步屏障消息,那肯定有移除同步屏障消息的步驟,這個步驟很有可能存在doTraversal()方法中,來看下這個方法
- doTraversal():removeSyncBarrier!我giao!果然在這地方!
- 這地方做了倆件事:移除同步屏障(removeSyncBarrier)、繪制界面(performTraversals)
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
-
doTraversal()是怎么被調用呢?
-
調用:mTraversalRunnable在scheduleTraversals()中使用了
final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void scheduleTraversals() { if (!mTraversalScheduled) { ... mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
-
-
postCallback是Choreographer類中方法,該類涉及巨多的消息傳遞,而且都是使用了異步消息
setAsynchronous(true)
,這些都是和界面刷新相關,所以都是優先處理,完整的流程可以看上面貼的文章 -
postCallback的核心就是讓DisplayEventReceiver注冊了個Vsync的通知,后期收到送來的Vsync后,我們就執行doTraversal()來重新繪制界面
總結
- 通過上面的對ViewRootImpl說明,需要來總結下同步屏障對界面繪制過程的影響
- 詳細版總結(不講人話版)
調用View的requestLayout或者invalidate時,最終都會執行scheduleTraversals(),此時會在主線程消息隊列中插入一個同步屏障消息(停止所有同步消息分發),會將mTraversalRunnable添加到mCallbackQueues中,並注冊接收Vsync的監聽,當接受到Vsync通知后,會發送一個異步消息,觸發遍歷執行mCallbackQueues的方法,這會執行我們添加的回調mTraversalRunnable,從而執行doTraversal(),此時會移除主線程消息隊列中同步屏障消息,最后執行繪制操作
- 通俗版總結
調用requestLayout或者invalidate時,會在主線程消息隊列中插入一個同步屏障消息,同時注冊接收Vsync的監聽;當接受到Vsync通知,會發送一個異步消息,執行真正的繪制事件:此時會移除消息隊列中的同步屏障消息,然后才會執行繪制操作
- 下面給不講人話版畫了個流轉圖示
總結
消息插入對比
-
有個很重要的事情,我們再來看下:正常發送消息和同步屏障消息插入消息隊列直接的區別,見下圖
- 取消息:關於取消息,都是取的mMessage,可以理解為,取消息隊列的頭結點
- 非延時消息在同步屏障消息之前發送,都會排在同步屏障消息之前
- 延時消息,如果時刻大於發送同步屏障消息的時刻,會排在同步屏障消息之后
Vsync
-
關於Vsync
- Vsync 信號一般是由硬件產生的,現在手機一般為60hz~120hz,每秒刷新60到120次,一個時間片算一幀
- 每個 Vsync 信號之間的時間就是一幀的時間段
-
來看下執行同步消息時間片:這圖真吉兒不好畫,吐血
- 由上圖可知:某種極端情況,你所發送的消息,在分發的時候,可能存在一幀的延時
總結
相關總結
- 同步屏障能確保消息隊列中的異步消息,會被優先執行
- 鑒於正常消息和同步屏障消息插入消息隊列的區別:同步屏障能夠及時的屏障隊列中的同步消息
- 某些極端場景:發送的消息,在分發的時候,可能會存一幀延時
- 極端場景:Vsync信號到來之后,立馬執行了RequestLayout等操作
- 同步屏障能確保在UI刷新中:Vsync信號到來后,能夠立馬執行真正的繪制頁面操作
同步消息和異步消息使用建議
在正常的情況,肯定不建議使用異步消息,此處假設一個場景:因為某種需求,你發送了大量的異步消息,由於消息進入消息隊列的特殊性,系統發送的異步消息,也只能乖乖的排在你的異步消息后面,假設你的異步消息占據了大量的時間片,甚至占用了幾幀,導致系統UI刷新的異步消息無法被及時執行,此時很有可能發生掉幀
當然,如果你能看明白這個同步屏障欄目所寫的東西,相信什么時候設置消息為異步,心中肯定有數
- 正常情況,請繼續使用同步消息
- 特殊情況,需要自己發送的消息被優先處理:可以使用異步消息
考點
上面源碼基本就分析到這邊了,咱們看看能根據這些知識點,能提一些什么問題呢?
一個小知識
我逛一些論壇的時候,發現有人:對Handler怎么在主線程和子線程進行數據交互的原理,感到迷惑。
-
如果看完這整篇,或許你的心里已經有了答案,為了更加明確這個知識,我還是在這里總結下吧!
- 主線程和子線程通過handler交互,交互的載體是通過Message這個對象,實際上我們在子線程發送的所有消息,都會加入到主線程的消息隊列中,然后主線程分發這些消息,這個就很容易做到倆個線程信息的交互。
-
看到這里,你可能有疑問了,我從子線程發送的消息,怎么就加到了主線程的消息隊列里呢???
- 大家可以看看你自己的代碼,你的handler對象是不是在主線程初始的?子線程發送消息,是不是通過這個handler發送的?
- 這就很簡單了,handler只需要把發送的消息,加到自身持有的Looper對象的MessageQueue里面(mLooper變量)就ok了
-
所以,你在哪個線程里面初始化Handler對象,在不同的線程中,使用這個對象發送消息;都會在你初始化Handler對象的線程里分發消息
1、先來個自己想的問題:Handler中主線程的消息隊列是否有數量上限?為什么?
這問題整的有點雞賊,可能會讓你想到,是否有上限這方面?而不是直接想到到上限數量是多少?
解答:Handler主線程的消息隊列肯定是有上限的,每個線程只能實例化一個Looper實例(上面講了,Looper.prepare只能使用一次),不然會拋異常,消息隊列是存在Looper()中的,且僅維護一個消息隊列
重點:每個線程只能實例化一次Looper()實例、消息隊列存在Looper中
拓展:MessageQueue類,其實都是在維護mMessage,只需要維護這個頭結點,就能維護整個消息鏈表
2、Handler中有Loop死循環,為什么沒有卡死?為什么沒有發生ANR?
先說下ANR:5秒內無法響應屏幕觸摸事件或鍵盤輸入事件;廣播的onReceive()
函數時10秒沒有處理完成;前台服務20秒內,后台服務在200秒內沒有執行完畢;ContentProvider的publish在10s內沒進行完。所以大致上Loop死循環和ANR聯系不大,問了個正確的廢話,所以觸發事件后,耗時操作還是要放在子線程處理,handler將數據通訊到主線程,進行相關處理。
線程實質上是一段可運行的代碼片,運行完之后,線程就會自動銷毀。當然,我們肯定不希望主線程被over,所以整一個死循環讓線程保活。
為什么沒被卡死:在事件分發里面分析了,在獲取消息的next()方法中,如果沒有消息,會觸發nativePollOnce方法進入線程休眠狀態,釋放CPU資源,MessageQueue中有個原生方法nativeWake方法,可以解除nativePollOnce的休眠狀態,ok,咱們在這倆個方法的基礎上來給出答案
-
當消息隊列中消息為空時,觸發MessageQueue中的nativePollOnce方法,線程休眠,釋放CPU資源
-
消息插入消息隊列,會觸發nativeWake喚醒方法,解除主線程的休眠狀態
- 當插入消息到消息隊列中,為消息隊列頭結點的時候,會觸發喚醒方法
- 當插入消息到消息隊列中,在頭結點之后,鏈中位置的時候,不會觸發喚醒方法
-
綜上:消息隊列為空,會阻塞主線程,釋放資源;消息隊列為空,插入消息時候,會觸發喚醒機制
- 這套邏輯能保證主線程最大程度利用CPU資源,且能及時休眠自身,不會造成資源浪費
-
本質上,主線程的運行,整體上都是以事件(Message)為驅動的
3、為什么不建議在子線程中更新UI?
多線程操作,在UI的繪制方法表示這不安全,不穩定。
假設一種場景:我會需要對一個圓進行改變,A線程將圓增大倆倍,B改變圓顏色。A線程增加了圓三分之一體積的時候,B線程此時,讀取了圓此時的數據,進行改變顏色的操作;最后的結果,可能會導致,大小顏色都不對。。。
4、可以讓自己發送的消息優先被執行嗎?原理是什么?
這個問題,我感覺只能說:在有同步屏障的情況下是可以的。
同步屏障作用:在含有同步屏障的消息隊列,會及時的屏蔽消息隊列中所有同步消息的分發,放行異步消息的分發。
在含有同步屏障的情況,我可以將自己的消息設置為異步消息,可以起到優先被執行的效果。
5、子線程和子線程使用Handler進行通信,存在什么弊端?
子線程和子線程使用Handler通信,某個接受消息的子線程肯定使用實例化handler,肯定會有Looper操作,Looper.loop()內部含有一個死循環,會導致線程的代碼塊無法被執行完,該線程始終存在。
如果在完成通信操作,我們一般可以使用: mHandler.getLooper().quit() 來結束分發操作
- 說明下:quit()方法會進行幾項操作
- 清空消息隊列(未分發的消息,不再分發了)
- 調用了原生的銷毀方法
nativeDestroy
(猜測下:可能是一些資源的釋放和銷毀) - 拒絕新消息進入消息隊列
- 它可以起到結束loop()死循環分發消息的操作
- 拓展:quitSafely() 可以確保所有未完成的事情完成后,再結束消息分發
6、Handler中的阻塞喚醒機制?
這個阻塞喚醒機制是基於 Linux 的 I/O 多路復用機制 epoll 實現的,它可以同時監控多個文件描述符,當某個文件描述符就緒時,會通知對應程序進行讀/寫操作.
MessageQueue 創建時會調用到 nativeInit,創建新的 epoll 描述符,然后進行一些初始化並監聽相應的文件描述符,調用了epoll_wait方法后,會進入阻塞狀態;nativeWake觸發對操作符的 write
方法,監聽該操作符被回調,結束阻塞狀態
詳細請查看:同步屏障?阻塞喚醒?和我一起重讀 Handler 源碼
7、什么是IdleHandler?什么條件下觸發IdleHandler?
IdleHandler的本質就是接口,為了在消息分發空閑的時候,能處理一些事情而設計出來的
具體條件:消息隊列為空的時候、發送延時消息的時候
8、消息處理完后,是直接銷毀嗎?還是被回收?如果被回收,有最大容量嗎?
Handler存在消息池的概念,處理完的消息會被重置數據,采用頭插法進入消息池,取的話也直接取頭結點,這樣會節省時間
消息池最大容量為50,達到最大容量后,不再接受消息進入
9、不當的使用Handler,為什么會出現內存泄漏?怎么解決?
先說明下,Looper對象在主線程中,整個生命周期都是存在的,MessageQueue是在Looper對象中,也就是消息隊列也是存在在整個主線程中;我們知道Message是需要持有Handler實例的,Handler又是和Activity存在強引用關系
存在某種場景:我們關閉當前Activity的時候,當前Activity發送的Message,在消息隊列還未被處理,Looper間接持有當前activity引用,因為倆者直接是強引用,無法斷開,會導致當前Activity無法被回收
思路:斷開倆者之間的引用、處理完分發的消息,消息被處理后,之間的引用會被重置斷開
解決:使用靜態內部類弱引Activity、清空消息隊列
最后
寫這篇文章加上思維導圖,也大概整了十三來張圖,我真的盡力了!