一、Handler基礎
1. Handler簡介
在Android中使用消息機制,通常就是指Handler機制。Handler是Android消息機制的上層接口。
Handler的使用過程很簡單,通過它可以輕松地将一個任務切換到Handler所在的線程中去執行。通常情況下,Handler的使用場景就是更新UI。
示例:
public class Activity extends android.app.Activity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println(msg.what);
}
};
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
//...耗時操作
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessage(message);
}
}).start();
}
}
在子線程中,進行耗時操作,執行完操作後,發送消息,通知主線程更新UI。這便是消息機制的典型應用場景。
2. Handler模型
Android消息機制中的五大概念:
-
:目前線程存儲的資料僅能從目前線程取出。ThreadLocal
-
:具有時間優先級的消息隊列(單連結清單)。因為單連結清單在插入和删除上比較有優勢。主要功能向消息池投遞消息MessageQueue
和取走消息池的消息MessageQueue.enqueueMessage
。MessageQueue.next
-
:儲存在ThreadLocal中的輪詢消息隊列,不斷循環執行Looper
,若有新的消息到來,從MessageQueue中讀取消息,按分發機制将消息分發給目标處理者。Looper.loop
-
:具體處理邏輯的地方,主要功能向消息池發送各種消息事件Handler
和處理相應消息事件Handler.sendMessage
。Handler.handleMessage
-
:需要傳遞的消息,可以傳遞資料。Message
3. Handler架構
消息機制的運作流程:
在子線程執行完耗時操作,當Handler發送消息時,将會調用
MessageQueue.enqueueMessage
,向消息隊列中添加消息。當通過
Looper.loop
開啟循環後,會不斷地從線程池中讀取消息,即調用
MessageQueue.next
,然後調用目标Handler(即發送該消息的Handler)的
dispatchMessage
方法傳遞消息,然後傳回到Handler所線上程,目标Handler收到消息,調用
handleMessage
方法,接收消息,處理消息。
MessageQueue,Handler和Looper三者之間的關系:
每個線程中隻能存在一個Looper,Looper是儲存在ThreadLocal中的。
主線程(UI線程)已經建立了一個Looper,是以在主線程中不需要再建立Looper,但是在其他線程中需要建立Looper。
每個線程中可以有多個Handler,即一個Looper可以處理來自多個Handler的消息。
Looper中維護一個MessageQueue,來維護消息隊列,消息隊列中的Message可以來自不同的Handler。
下面是消息機制的整體架構圖,接下來我們将慢慢解剖整個架構。
從中我們可以看出:
Looper有一個MessageQueue消息隊列;
MessageQueue有一組待處理的Message;
Message中記錄發送和處理消息的Handler;
Handler中有Looper和MessageQueue。
接下來我們通過源碼來分析Handler的原理。
二、Handler源碼
1. Looper
要想使用消息機制,首先要建立一個Looper。
1.1 初始化Looper
1.1.1 普通線程初始化
相關代碼如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
final Thread mThread;
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
prepare()
無參情況下,預設調用
prepare(true)
表示的是這個Looper可以退出,而對于false的情況則表示目前Looper不可以退出。
sThreadLocal.get() != null
這裡看出,隻能建立一個,不能重複建立Looper,否則會抛出RuntimeException。
然後建立Looper,并儲存在ThreadLocal。
其中ThreadLocal是線程本地存儲區(Thread Local Storage,簡稱為TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能通路對方的TLS區域。
Looper中建立了MessageQueue,且儲存了MessageQueue的引用。
// True if the message queue can be quit.
private final boolean mQuitAllowed;
private long mPtr; // used by native code
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private native static long nativeInit();
MessageQueue的初始化過程中儲存了是否能退出這一狀态,且調用了
nativeInit()
這一native層的函數,儲存傳回的指針引用。
1.1.2 主線程初始化
主線程中不需要自己建立Looper,這是由于在程式啟動的時候,系統已經幫我們自動調用了
Looper.prepare()
方法。檢視ActivityThread中的
main()
方法,代碼如下所示:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
...
}
Android主線程的
Looper#prepareMainLooper()
方法,我們看下其實作。
private static Looper sMainLooper; // guarded by Looper.class
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
這裡首先也是調用了
prepare(false)
方法,false表示目前Looper不可以退出,主線程的Handler是不允許退出的。
Looper.loop()
是個死循環,後面的代碼正常情況不會執行。因為主線程不允許退出,退出就意味 APP 要挂。
通過
myLooper()
方法擷取主線程的Looper,指派給sMainLooper。
我們注意到
Looper#getMainLooper()
方法供我們擷取主線程的Looper。
1.2 開啟Looper
public static void loop() {
final Looper me = myLooper(); //擷取TLS存儲的Looper對象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //擷取Looper對象中的消息隊列
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //進入loop的主循環方法
Message msg = queue.next(); //可能會阻塞,因為next()方法可能會無限循環
if (msg == null) { //消息為空,則退出循環
return;
}
Printer logging = me.mLogging; //預設為null,可通過setMessageLogging()方法來指定輸出,用于debug功能
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);//擷取msg的目标Handler,然後用于分發Message
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "...“);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "...”);
}
msg.recycleUnchecked();
}
}
loop()
進入循環模式,不斷重複下面的操作,直到消息為空時退出循環:
- 讀取MessageQueue的下一條Message(關于next(),後面詳細介紹);
- 把Message分發給相應的target(Handler)。
當next()取出下一條消息時,隊列中已經沒有消息時,next()會無限循環,産生阻塞。等待MessageQueue中加入消息,然後重新喚醒。
1.3 結束Looper
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
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);
}
}
private native static void nativeWake(long ptr);
可以看到
quit()
和
quitSafely()
對應立即結束和安全結束方法。
特别地,對于主線程,在App退出時,ActivityThread中的 mH(Handler)收到消息後,才會執行退出。
//ActivityThread.java
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
手動退出主線程Looper,便會抛出異常:
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
主線程不允許退出,一旦退出就意味着程式挂了。
2. Handler
2.1 建立Handler
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
//必須先執行Looper.prepare(),才能擷取Looper對象,否則為null.
mLooper = Looper.myLooper(); //從目前線程的TLS中擷取Looper對象
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //消息隊列,來自Looper對象
mCallback = callback; //回調方法,預設為null
mAsynchronous = async; //設定消息是否為異步處理方式
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
對于Handler的無參構造方法,預設采用目前線程TLS中的Looper對象,并且callback回調方法為null,且消息為同步處理方式。隻要執行的
Looper.prepare()
方法,那麼便可以擷取有效的Looper對象。
2.2 其他構造方法
public Handler() {
this(null, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async){
...//同上
}
以上,預設采用目前線程的Looper建構Handler。
如果要指定Looper,有如下構造方法。
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
3. 發送消息
發送消息有幾種方式,但是歸根結底都是調用了
sendMessageAtTime()
方法。
在子線程中通過Handler的post()方式或send()方式發送消息,最終都是調用了
sendMessageAtTime()
方法。
3.1 post方法
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
3.2 send方法
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
3.3 runOnUiThread()
就連子線程中調用Activity中的runOnUiThread()中更新UI,其實也是發送消息通知主線程更新UI,最終也會調用
sendMessageAtTime()
方法。
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
如果目前的線程不等于UI線程(主線程),就去調用Handler的post()方法,最終會調用
sendMessageAtTime()
方法。否則就直接調用Runnable對象的run()方法。
3.4 sendMessageAtTime()
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//其中mQueue是消息隊列,從Looper中擷取的
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//調用enqueueMessage方法
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//調用MessageQueue的enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到sendMessageAtTime()`方法的作用很簡單,就是調用MessageQueue的enqueueMessage()方法,往消息隊列中添加一個消息。
下面來看enqueueMessage()方法的具體執行邏輯。
3.5 enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
// 每一個Message必須有一個target
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) { //正在退出時,回收msg,加入到消息池
...
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//p為null(代表MessageQueue沒有消息) 或者msg的觸發時間是隊列中最早的, 則進入該該分支
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//将消息按時間順序插入到MessageQueue。一般地,不需要喚醒事件隊列,除非
//消息隊頭存在barrier,并且同時Message是隊列中最早的異步消息。
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;
}
MessageQueue是按照Message觸發時間的先後順序排列的,隊頭的消息是将要最早觸發的消息。
當有消息需要加入消息隊列時,會從隊列頭開始周遊,直到找到消息應該插入的合适位置,以保證所有消息的時間順序。
4. 擷取消息
當發送了消息後,在MessageQueue維護了消息隊列,然後在Looper中通過
loop()
方法,不斷地擷取消息。上面對
loop()
方法進行了介紹,其中最重要的是調用了
queue.next()
方法,通過該方法來提取下一條資訊。下面我們來看一下
next()
方法的具體流程。
4.1 next()
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
Message next() {
final long ptr = mPtr;
if (ptr == 0) { //當消息循環已經退出,則直接傳回
return null;
}
int pendingIdleHandlerCount = -1; // 循環疊代的首次為-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會傳回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//當消息Handler為空時,查詢MessageQueue中的下一條異步消息msg,為空則退出循環。
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 {
// 擷取一條消息,并傳回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//設定消息的使用狀态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地擷取MessageQueue中的下一條即将要執行的消息
}
} else {
//沒有消息
nextPollTimeoutMillis = -1;
}
//消息正在退出,傳回null
if (mQuitting) {
dispose();
return null;
}
//IdleHandler的處理
......
}
}
nativePollOnce是阻塞操作,其中nextPollTimeoutMillis代表下一個消息到來前,還需要等待的時長;當nextPollTimeoutMillis = -1時,表示消息隊列中無消息,會一直等待下去。
可以看出
next()
方法根據消息的觸發時間,擷取下一條需要執行的消息,隊列中消息為空時,則會進行阻塞操作。
5. 分發消息
在loop()方法中,擷取到下一條消息後,執行
msg.target.dispatchMessage(msg)
,來分發消息到目标Handler對象。 下面就來具體看下
dispatchMessage(msg)
方法的執行流程。
5.1 dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//當Message存在回調方法,回調msg.callback.run()方法;
handleCallback(msg);
} else {
if (mCallback != null) {
//當Handler存在Callback成員變量時,回調方法handleMessage();
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler自身的回調方法handleMessage()
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
分發消息流程 :
- 當Message的
不為空時,則回調方法msg.callback
;msg.callback.run()
- 當Handler的
不為空時,則回調方法mCallback
;mCallback.handleMessage(msg)
- 最後調用Handler自身的回調方法
,該方法預設為空,Handler子類通過覆寫該方法來完成具體的邏輯。handleMessage()
消息分發的優先級 :
- Message的回調方法:
,優先級最高;message.callback.run()
- Handler中Callback的回調方法:
,優先級僅次于1;Handler.mCallback.handleMessage(msg)
- Handler的預設方法:
,優先級最低。Handler.handleMessage(msg)
- 對于很多情況下,消息分發後的處理方法是第3種情況,即
,一般地往往通過覆寫該方法進而實作自己的業務邏輯。Handler.handleMessage()
6. 移除消息
removeAllMessagesLocked(),實作如下:
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
立即删除消息,不管消息是否在目前時間應該執行。
removeAllFutureMessagesLocked(),實作如下:
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
等待目前時間應該執行的消息執行完成後删除所有消息。
removeCallbacksAndMessages(),實作如下:
//Handler
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
//MessageQueue
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
移除所有的回調和消息。
7. 總結
以上便是消息機制的原理,以及從源碼角度來解析消息機制的運作過程。可以簡單地用下圖來了解。
三、IdleHandler
IdleHandler 可以用來提升性能,主要用在我們希望能夠在目前線程消息隊列空閑時做些事情,不過最好不要做耗時操作。具體用法如下。
//getMainLooper().myQueue()或者Looper.myQueue()
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
//你要處理的事情
return false;
}
});
關于 IdleHandler 在 MessageQueue 與 Looper 和 Handler 的關系原理源碼分析如下:
/**
* 擷取目前線程隊列使用Looper.myQueue(),擷取主線程隊列可用getMainLooper().myQueue()
*/
public final class MessageQueue {
......
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
/**
* 目前隊列将進入阻塞等待消息時調用該接口回調,即隊列空閑
*/
public static interface IdleHandler {
/**
* 傳回true就是單次回調後不删除,下次進入空閑時繼續回調該方法,false隻回調單次。
*/
boolean queueIdle();
}
/**
* 判斷目前隊列是不是空閑的,輔助方法
*/
public boolean isIdle() {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
return mMessages == null || now < mMessages.when;
}
}
/**
* 添加一個IdleHandler到隊列,如果IdleHandler接口方法傳回false則執行完會自動删除,
* 否則需要手動removeIdleHandler。
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
/**
* <p>This method is safe to call from any thread.
* 删除一個之前添加的 IdleHandler。
*/
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
......
//Looper的prepare()方法會通過ThreadLocal準備目前線程的MessageQueue執行個體,
//然後在loop()方法中死循環調用目前隊列的next()方法擷取Message。
Message next() {
......
for (;;) {
......
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
......
//把通過addIdleHandler添加的IdleHandler轉成數組存起來在mPendingIdleHandlers中
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);
}
//循環周遊所有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 {
//調用IdleHandler接口的queueIdle方法并擷取傳回值。
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//如果IdleHandler接口的queueIdle方法傳回false說明隻執行一次需要删除。
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
......
}
}
}
四、同步屏障機制
Message分為3中:普通消息(同步消息)、屏障消息(同步屏障)和異步消息。
我們通常使用的都是普通消息,而屏障消息就是在消息隊列中插入一個屏障,在屏障之後的所有普通消息都會被擋着,不能被處理。不過異步消息卻例外,屏障不會擋住異步消息,是以可以這樣認為:屏障消息就是為了確定異步消息的優先級,設定了屏障後,隻能處理其後的異步消息,同步消息會被擋住,除非撤銷屏障。
1. 插入屏障消息
同步屏障是通過
MessageQueue#postSyncBarrier
方法插入到消息隊列的。
//MessageQueue#postSyncBarrier
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//1、屏障消息和普通消息的差別是屏障消息沒有tartget。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//2、根據時間順序将屏障插入到消息連結清單中适當的位置
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;
}
//3、傳回一個序号,通過這個序号可以撤銷屏障
return token;
}
}
postSyncBarrier方法就是用來插入一個屏障到消息隊列的,可以看到它很簡單,從這個方法我們可以知道如下:
- 屏障消息和普通消息的差別在于屏障沒有tartget,普通消息有target是因為它需要将消息分發給對應的target,而屏障不需要被分發,它就是用來擋住普通消息來保證異步消息優先處理的。
- 屏障和普通消息一樣可以根據時間來插入到消息隊列中的适當位置,并且隻會擋住它後面的同步消息的分發。
- postSyncBarrier傳回一個int類型的數值,通過這個數值可以撤銷屏障。
- postSyncBarrier方法是私有的,如果我們想調用它就得使用反射。
- 插入普通消息會喚醒消息隊列,但是插入屏障不會。
2. 屏障消息的工作原理
通過postSyncBarrier方法屏障就被插入到消息隊列中了,那麼屏障是如何擋住普通消息隻允許異步消息通過的呢?我們知道MessageQueue是通過next方法來擷取消息的。
Message next() {
//1、如果有消息被插入到消息隊列或者逾時時間到,就被喚醒,否則阻塞在這。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//2、遇到屏障 msg.target == null
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//3、周遊消息連結清單找到最近的一條異步消息
}
if (msg != null) {
//4、如果找到異步消息
if (now < msg.when) {//異步消息還沒到處理時間,就再等會(逾時時間)
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//異步消息到了處理時間,就從連結清單移除,傳回它。
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 {
// 如果沒有異步消息就一直休眠,等待被喚醒。
nextPollTimeoutMillis = -1;
}
//。。。。
}
}
可以看到,在注釋2如果碰到屏障就周遊整個消息連結清單找到最近的一條異步消息,在周遊的過程中隻有異步消息才會被處理執行到 if (msg != null){}中的代碼。可以看到通過這種方式就擋住了所有的普通消息。
3. 發送異步消息
Handler有幾個構造方法,可以傳入async标志為true,這樣構造的Handler發送的消息就是異步消息。不過可以看到,這些構造函數都是hide的,正常我們是不能調用的,不過利用反射機制可以使用@hide方法。
/**
* @hide
*/
public Handler(boolean async) {}
/**
* @hide
*/
public Handler(Callback callback, boolean async) { }
/**
* @hide
*/
public Handler(Looper looper, Callback callback, boolean async) {}
當調用
handler.sendMessage(msg)
發送消息,最終會走到:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);//把消息設定為異步消息
}
return queue.enqueueMessage(msg, uptimeMillis);
}
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
可以看到如果這個handler的mAsynchronous為true就把消息設定為異步消息,設定異步消息其實也就是設定msg内部的一個标志。而這個mAsynchronous就是構造handler時傳入的async。除此之外,還有一個公開的方法:
Message message=Message.obtain();
message.setAsynchronous(true);
handler.sendMessage(message);
在發送消息時通過
message.setAsynchronous(true)
将消息設為異步的,這個方法是公開的,我們可以正常使用。
4. 移除屏障
移除屏障可以通過MessageQueue的removeSyncBarrier方法:
//注釋已經寫的很清楚了,就是通過插入同步屏障時傳回的token 來移除屏障
/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//找到token對應的屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
final boolean needWake;
//從消息連結清單中移除
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
//回收這個Message到對象池中。
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);//喚醒消息隊列
}
}
5. 實戰
測試代碼如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Handler handler;
private int token;
public static final int MESSAGE_TYPE_SYNC=1;
public static final int MESSAGE_TYPE_ASYN=2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initHandler();
initListener();
}
private void initHandler() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_TYPE_SYNC){
Log.d("MainActivity","收到普通消息");
}else if (msg.what == MESSAGE_TYPE_ASYN){
Log.d("MainActivity","收到異步消息");
}
}
};
Looper.loop();
}
}).start();
}
private void initListener() {
findViewById(R.id.btn_postSyncBarrier).setOnClickListener(this);
findViewById(R.id.btn_removeSyncBarrier).setOnClickListener(this);
findViewById(R.id.btn_postSyncMessage).setOnClickListener(this);
findViewById(R.id.btn_postAsynMessage).setOnClickListener(this);
}
//往消息隊列插入同步屏障
@RequiresApi(api = Build.VERSION_CODES.M)
public void sendSyncBarrier(){
try {
Log.d("MainActivity","插入同步屏障");
MessageQueue queue=handler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token= (int) method.invoke(queue);
} catch (Exception e) {
e.printStackTrace();
}
}
//移除屏障
@RequiresApi(api = Build.VERSION_CODES.M)
public void removeSyncBarrier(){
try {
Log.d("MainActivity","移除屏障");
MessageQueue queue=handler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
method.setAccessible(true);
method.invoke(queue,token);
} catch (Exception e) {
e.printStackTrace();
}
}
//往消息隊列插入普通消息
public void sendSyncMessage(){
Log.d("MainActivity","插入普通消息");
Message message= Message.obtain();
message.what=MESSAGE_TYPE_SYNC;
handler.sendMessageDelayed(message,1000);
}
//往消息隊列插入異步消息
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private void sendAsynMessage() {
Log.d("MainActivity","插入異步消息");
Message message=Message.obtain();
message.what=MESSAGE_TYPE_ASYN;
message.setAsynchronous(true);
handler.sendMessageDelayed(message,1000);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
int id=v.getId();
if (id == R.id.btn_postSyncBarrier) {
sendSyncBarrier();
}else if (id == R.id.btn_removeSyncBarrier) {
removeSyncBarrier();
}else if (id == R.id.btn_postSyncMessage) {
sendSyncMessage();
}else if (id == R.id.btn_postAsynMessage){
sendAsynMessage();
}
}
}
五、Handler擴充
1. 記憶體洩漏
Handler 允許我們發送延時消息,如果在延時期間使用者關閉了 Activity,那麼該 Activity 會洩露。
這個洩露是因為 Message 會持有 Handler,而又因為 Java 的特性,内部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導緻 Activity 洩露。
解決該問題的最有效的方法是:将 Handler 定義成靜态的内部類,在内部持有 Activity 的弱引用,并及時移除所有消息。
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
并且再在
Activity.onDestroy()
前移除消息,加一層保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
注意:單純的在
onDestroy
移除消息并不保險,因為
onDestroy
并不一定執行。
2. 建立 Message 執行個體的最佳方式
由于 Handler 極為常用,是以為了節省開銷,Android 給 Message 設計了回收機制,是以我們在使用的時候盡量複用 Message ,減少記憶體消耗。
- 通過 Message 的靜态方法
擷取,源碼如下:Message.obtain()
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
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();
}
public static Message obtain(Message orig) {
Message m = obtain();
m.what = orig.what;
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = orig.obj;
m.replyTo = orig.replyTo;
m.sendingUid = orig.sendingUid;
if (orig.data != null) {
m.data = new Bundle(orig.data);
}
m.target = orig.target;
m.callback = orig.callback;
return m;
}
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
public static Message obtain(Handler h, int what) {
Message m = obtain();
m.target = h;
m.what = what;
return m;
}
public static Message obtain(Handler h, int what, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.obj = obj;
return m;
}
public static Message obtain(Handler h, int what, int arg1, int arg2) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
return m;
}
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
從sPool中取一個Message,複制後傳回。
- 通過 Handler 的公有方法
,源碼如下:handler.obtainMessage()
public final Message obtainMessage()
{
return Message.obtain(this);
}
public final Message obtainMessage(int what)
{
return Message.obtain(this, what);
}
public final Message obtainMessage(int what, Object obj)
{
return Message.obtain(this, what, obj);
}
public final Message obtainMessage(int what, int arg1, int arg2)
{
return Message.obtain(this, what, arg1, arg2);
}
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
return Message.obtain(this, what, arg1, arg2, obj);
}
可以看到是其實就是第一個方式的封裝。
3. 子線程使用Handler
示例代碼:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
//建立Looper,MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
}
});
//開始處理消息
Looper.loop();
}
}).start();
}
添加Looper.prepare()建立Looper,同時調用Looper.loop()方法開始處理消息。
在所有事情處理完成後應該調用quit方法來終止消息循環,否則這個子線程就會一直處于循環等待的狀态,是以不需要的時候終止Looper,調用 Looper.myLooper().quit()
。
4. 妙用 Looper 機制
我們可以利用 Looper 的機制來幫助我們做一些事情:
- 将 Runnable post 到主線程執行;
- 利用 Looper 判斷目前線程是否是主線程。
示例代碼如下:
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
//将 Runnable post 到主線程執行
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
//利用 Looper 判斷目前線程是否是主線程。
return Looper.myLooper() == Looper.getMainLooper();
}
}
5. epoll機制
Looper會線上程中不斷的檢索消息,如果是子線程的Looper死循環,一旦任務完成,使用者應該手動退出,而不是讓其一直休眠等待。而對于主線程,我們是絕不希望會被運作一段時間自己就退出,是以通過死循環來保證不會被退出。
主線程的死循環并不會特别消耗 CPU 資源。這就涉及到 Linux pipe/epoll機制:簡單說就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的
queue.next()
中的
nativePollOnce(
) 方法裡,此時主線程會釋放 CPU 資源進入休眠狀态,直到下個消息到達或者有事務發生,通過往 pipe 管道寫端寫入資料來喚醒主線程工作。
這裡采用的 epoll 機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 是以說,主線程大多數時候都是處于休眠狀态,并不會消耗大量CPU資源。
聊聊IO多路複用之select,poll,epoll詳解
6. ThreadLocal
ThreadLocal是一個線程内部的資料存儲類,通過它可以在指定線程存儲資料,資料存儲後,隻能在指定的線程可以擷取到存儲的資料,對于其他線程則無法擷取到資料。一般來說,當資料是以線程作為作用域并且不同線程有不同副本的時候,就可以考慮使用ThreadLocal。
Java中的實作是下面這樣,Java 的實作裡面也有一個 Map,叫做 ThreadLocalMap,不過持有 ThreadLocalMap 的不是 ThreadLocal,而是 Thread。Thread 這個類内部有一個私有屬性 threadLocals,其類型就是 ThreadLocalMap,ThreadLocalMap 的 Key 是 ThreadLocal。
6.1 整體
精簡之後的代碼如下:
class Thread {
//内部持有ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals;
}
class ThreadLocal<T>{
public T get() {
//首先擷取線程持有的ThreadLocalMap
ThreadLocalMap map = Thread.currentThread().threadLocals;
//在ThreadLocalMap中查找變量
Entry e = map.getEntry(this);
return e.value;
}
public void set(T value) {
//首先擷取線程持有的ThreadLocalMap
ThreadLocalMap map = Thread.currentThread().threadLocals;
//在ThreadLocalMap中設定變量
if (map != null)
map.set(this, value);
else
//建立ThreadLocalMap
createMap(t, value);
}
static class ThreadLocalMap{
//内部是數組而不是Map
Entry[] table;
//根據ThreadLocal查找Entry
Entry getEntry(ThreadLocal key){
//省略查找邏輯
}
//Entry定義
static class Entry extends WeakReference<ThreadLocal<?>>{
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
6.2 get
//ThreadLocal
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//ThreadLocal.ThreadLocalMap
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
6.3 set
//ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocal.ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在Java的實作方案中,ThreadLocal僅僅隻是一個代理工具類,内部并不持有任何線程相關的資料,所有和線程相關的資料都存儲在Thread裡。
這樣的設計從資料的親緣性上來講,ThreadLocalMap屬于Thread也更加合理。是以ThreadLocal的get方法,其實就是拿到每個線程獨有的ThreadLocalMap。
還有一個原因,就是不容易産生記憶體洩漏。Thread持有ThreadLocalMap,而且ThreadLocalMap裡對ThreadLocal的引用還是弱引用,是以隻要Thread對象可以被回收,那麼ThreadLocalMap就能被回收。Java的實作方案雖然看上去複雜一些,但是更安全。
但是,如果線上程池中使用ThreadLocal可能會導緻記憶體洩漏,原因是線程池中線程的存活時間太長,往往和程式都是同生共死的,這就意味着Thread持有的ThreadLocalMap一直都不會被回收,再加上ThreadLocalMap中的Entry對ThreadLocal是弱引用,是以隻要ThreadLocal結束了自己的生命周期是可以被回收掉的。但是Entry中的Value卻是被Entry強引用的,是以即便Value的生命周期結束了,Value也是無法被回收的,進而導緻記憶體洩漏。
是以我們可以通過
try{} finally{}
方案來手動釋放資源。
ExecutorService pools;
ThreadLocal tl;
pools.execute(()->{
//ThreadLocal增加變量
tl.set(obj);
try {
// 省略業務邏輯代碼
}finally {
//手動清理ThreadLocal
tl.remove();
}
});
7. Handler鎖相關
enqueueMessage()
通過synchronized關鍵字保證線程安全。同時
messagequeue.next()
内部也會通過synchronized加鎖,確定取的時候線程安全,同時插入也會加鎖。
我的學習筆記,歡迎star和fork
歡迎關注我的公衆号,持續分析優質技術文章