編輯:關於Android編程
【轉載請注明出處:從Handler+Message+Looper源碼帶你分析Android系統的消息處理機制 CSDN 廢墟的樹】
作為Android開發者,相信很多人都使用過Android的Handler類來處理異步任務。那麼Handler類是怎麼構成一個異步任務處理機制的呢?這篇
博客帶你從源碼分析Android的消息循環處理機制,便於深入的理解。
這裡不得不從“一個Bug引發的思考”開始研究Android的消息循環處理機制。說來話長,在某一次的項目中,原本打算開啟一個工作線程
WorkThread去執行一個耗時任務,然後在工作線程WorkThread中new一個Handler對象來發送消息。代碼簡化成如下:
private class WorkThread extends Thread {
private Handler mHandler;
@Override
public void run() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e(TAG, 任務執行完成);
break;
}
}
};
//模擬一個耗時任務
try {
sleep(9000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//任務執行完成之後發送一個消息
int what = 0;
mHandler.sendEmptyMessage(what);
}
}
當以上代碼執行之後,很不幸的出現了如下的一個Bug:
Log打印日志提示:不能在線程中沒有調用 Looper.prepare()方法之前就去創建handler對象,言外之意就是,在線程中還沒調Looper.prepare()
方法之前,你是不能去創建Handler對象的,否則拋出錯誤異常。為什麼會這樣呢?帶著疑問,我們跟蹤代碼進入到Handler源碼中的構造方法:
public Handler(Callback callback, boolean async) {
.............
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
Can't create handler inside thread that has not called Looper.prepare());
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
代碼第5-8行:我們發現當成員變量mLooper為空值時,就會拋出上面的異常了,意思就是剛才在WorkThread中創建Handler的時候mLooper是
空的,難道不可以這樣創建Handler消息處理機制?那為什麼在UI線程中直接new一個Hanlder對象不會出錯呢?帶著這種好奇心,我們今天來分
析一下Android系統消息處理機制有關的 Handler,Message,Looper,Thread類之間的關聯。
Handler:消息的執行者,也可以稱之為異步任務的執行者 Message:消息的封裝者,把異步任務,消息碼Handler對象等封裝成Message對象 MessageQueue:消息隊列,用於保存當前線程的所有消息Message對象的一個列表 Looper:循環者,能讓工作線程變成循環線程,然後從消息隊列中循環讀取消息 Thread:異步任務或者耗時任務執行場所,一般開啟一個新的工作線程處理耗時任務在Android的消息處理機制中,Handler扮演者重要的角色。Handler負責如下幾個工作:
消息的發送 消息的入列 消息的調度/消息的分發 消息的處理對於消息的發送,相信很多人平時用的最多的是Handler.sendEmptyMessage(),那麼利用Handler發送的消息最終會發送到哪裡呢?騷年不用YY了,源碼會告訴你答案,我們跟蹤Handler類中的sendEmptyMessage方法:
/**
* Sends a Message containing only the what value.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
/**
* Sends a Message containing only the what value, to be delivered
* after the specified amount of time elapses.
* @see #sendMessageDelayed(android.os.Message, long)
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + sendMessageAtTime() called with no mQueue);
Log.w(Looper, e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
有以上代碼發現,Handler消息的發送最終都會調用sendMessageAtTime成員方法。該方法首先判斷當前Handler的成員變量mQueue是否為空,如果為空,則打印一個警告,並且返回false,表
示該消息發送失敗。那麼成員變量mQueue是神馬東西呢?mQueue是MessageQueue對象,而MessageQueue類是一個消息隊列,被Looper
對象持有。關於MessageQueue消息隊列相關內容後面會展開分析。繼續分析代碼,如果消息隊列不為空,則會調用enqueueMessage方法,跟蹤代碼進入該方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
首先,將當前類Handler對象賦值給消息Message類中的target成員變量。然後調用消息隊列MessageQueue類中的enqueueMessage方法將
該消息msg插入到消息隊列中。這個過程稱之為消息入列的一個過程。
有1-1節可知,消息的入列是調用MessageQueue消息隊列類中的enqueueMessage成員方法實現的,跟蹤代碼看看消息入列的具體實現
boolean enqueueMessage(Message msg, long when) {
//判斷消息的目標對象是否為空
if (msg.target == null) {
throw new IllegalArgumentException(Message must have a target.);
}
//判斷消息是否正在使用
if (msg.isInUse()) {
throw new IllegalStateException(msg + This message is already in use.);
}
synchronized (this) {
//判斷是否正在清除消息隊列
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + sending message to a Handler on a dead thread);
Log.w(MessageQueue, e.getMessage(), e);
msg.recycle();
return false;
}
//標記消息正在使用
msg.markInUse();
//對消息的延時處理時間幅值
msg.when = when;
//保存消息隊列中的延時時間最小的那個消息
Message p = mMessages;
boolean needWake;
//條件判斷,消息入列,將延時時間最小的那個消息賦值給mEssages變量
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;
}
分析:
1.代碼第3行:判斷當前入列的消息的目標處理對象是否為空,有1-1節我們知道msg.target的值是一個Handler實例。換言之,如果消息的目標處
理對象Handler為null,那麼該消息就沒有Handler去處理它,因此此處拋出一個異常
2.代碼第7行:判斷當前Message是否正在使用?如果是則拋出異常。說明一個相同的消息不可能同時存在同一個MessageQueue消息隊列中。
3.代碼第13行:判斷當前消息隊列是否正在退出?如果正在退出,則拋出異常,說明當前Message消息無法入列,因為MessageQueue消息隊列退出了。
4.代碼第27-52行:Message消息按照延時時間大小插入當前MessageQueue消息隊列中,最終延時時間最短的消息在隊列的最前面。
總結:到此,我們知道Handler將消息發送到消息隊列MessageQueue中去了。那麼這個消息隊列MessageQueue是從哪裡來
的呢?它屬於誰呢?這裡直接給出答案,MessageQueue消息隊列是在Looper類中創建,Looper循環則持有當前線程中的消息隊列
MessageQueue。至於為什麼是這樣,後面給出詳細解釋。
有1-2小節我們知道,消息的執行者Handler將消息Message發送到消息隊列MessageQueue中,並且消息隊列中的所有消息都是按照時間排列。
那麼在消息隊列MessageQueue中的消息又是怎麼分發出去的,或者說消息隊列中的消息是怎麼被消費掉的?現在我們來解答1-2小節中的最後一個問題,消息隊列MessageQueue從哪裡來?屬於誰?在文章的一開頭,
我們有“一個Bug引發的思考”知道了在線程中創建Handler對象之前需要調用Looper.prepare(),否則會拋出異常。那麼我們就來分析一下Looper這個類:
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
*
Most interaction with a message loop is through the * {@link Handler} class. * *
This is a typical example of the implementation of a Looper thread, * using the separation of {@link #prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper. * *
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }
*/
分析:源碼開頭給出了一個很長的說明,大意是:Looper類作用是為一個線程構造消息循環的,因為線程Thread默認是不帶消息循環的。因此
你可以調用Looper類中的prepare方法去為構造一個帶消息循環的線程,並且調用Looper.loop()方法啟動循環去循環處理消息,直到loop循環結
束。並且Google官方還提供了一個標准的構造一個帶消息循環的線程實例,代碼如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
以上代碼就是創建帶消息循環的線程標准寫法。那麼我們來看看Looper.prepare()方法做了什麼:
public final class Looper {
private static final String TAG = Looper;
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Printer mLogging;
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
}
分析:
1.代碼第24行:調用sThreadLocal.get()獲得存儲在本地線程中的Looper值,如果本地線程ThreadLocal中有當前的Looper對象,者會拋出一個異
常:”Only one Looper may be created per thread”每一個線程只能有一個Looper對象。所以每一個線程只能擁有一個Looper對象,關於ThreadLocal類,這裡不展開學習,主要作用是將Looper對象存儲在本地線程ThreadLocal中。
2.代碼第27行:將當前的Looper對象保存在本地線程對象ThreadLocal中。至此,Looper的准備工作就完成了。
Looper的准備工作完成之後,我們來看看Looper的啟動方法Looper.loop(),進入loop()方法代碼如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(No Looper; Looper.prepare() wasn't called on this thread.);
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
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);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, Thread identity changed from 0x
+ Long.toHexString(ident) + to 0x
+ Long.toHexString(newIdent) + while dispatching to
+ msg.target.getClass().getName() +
+ msg.callback + what= + msg.what);
}
msg.recycleUnchecked();
}
}
分析:注釋開頭就描述了,該方法的作用是為了在線程中構建一個消息循環機制,當然你可以調用Looper類中的quit()成員方法結束當前loop循環。
代碼第10行:獲得當前線程Thread的消息隊列並且賦值給本地變量queue。
代碼第17-49行:構建一個死循環來遍歷當前Looper中的消息隊列。
代碼第18-22行:每次遍歷都獲取消息隊列中最前端的消息,也就是延時時間最短的消息。當從消息隊列中獲取的消息為空,則說明該消息隊列已經退出。
代碼第31行:這一行是重點,此處調用了當前消息的目標對象去分發消息。有1-1小節的最後一段我們知道,消息隊列的目標對象就是Handler對象,因此這裡就是調用Handler去調度消息或者叫分發消息。有此處也看出來,Message消息是有哪個Handler發送的,就有哪個Handler去分發消息。
跟蹤代碼進入
msg.target.dispatchMessage(msg);
以上方法在Handler類中
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
該方法就是處理系統消息的地方,以上代碼有三個分支,我們先看最後一個分支,就是執行handleMessage(msg);方法,跟蹤代碼:
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
我擦,這麼簡單,就一個空方法?那怎麼處理消息呢?注釋已經告訴我們了,Handler的子類必須重寫該方法用來接收消息,也就是說最後的消息
處理邏輯是延伸給Handler類的之類了。相信讀者對該方法應該不陌生吧,就是我們經常在實現Handler類中重寫的那個方法,消息的處理就在這個方法裡面實現了。
到此,消息的調用/消息的分發就結束了,最後通過一個空方法將消息的處理邏輯留給了Handler的之類。
消息有以上三步操作,最後進入消息的處理階段,這裡就到了我們非常熟悉的地方了,消息的處理是留給我們Handler子類去實現的,也就是我們平時編寫Handler消息那樣:
public Handler mHandler;
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
下面用一張圖來描述消息的發送,消息的入列,消息的分發,消息的處理四個過程。
有線程中的Handler把Message類封裝的消息發送到當前線程的Looper對象中的消息隊列MessageQueue中,然後Looper遍歷循環消息隊列MessageQueue,從消息隊列中取出消息,通過target Handler將消息分發出去,最後當前線程中的Handler獲取到該消息,並對消息進行處理。
分析Handler類源碼發現,Handller類有很多個構造方法。愛思考的你肯定會問,這些構造方法有什麼不同呢?那麼我們就從代碼中來分析他們之間的不同吧:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//接收消息之後,處理消息
}
};
我們一般在UI線程中這麼來實例化一個Handler對象,然後通過重寫Handler類中的handleMessage(Message msg)方法來處理消息。
private Handler handler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//接收消息之後,處理消息
return true;
}
});
此處的Callback參數是Handler類的內部回調接口,我們通過實現Callback接口中的handleMessage(Message msg)方法來處理消息。
private Handler handler2 = new Handler(Looper.myLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//TODO 接收消息之後,處理消息
return true;
}
});
第一個參數是當前線程的Looper對象,第二個參數是Callback接口,消息的處理和2-2小節一樣,唯一不同的地方就是多了一個Looper參數。
由1-3小節我們發現,Handler類中的dispatchMessage方法分發消息有幾種情況:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
//第一種處理消息方式
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//第二種處理消息方式
if (mCallback.handleMessage(msg)) {
return;
}
}
//第三種處理消息方式
handleMessage(msg);
}
}
1.而當你使用2-1小節的構造方法創建Handler對象時,就是采用上面代碼中的第三種處理消息的方式了,即重寫Handler類中的handleMessage(msg)方法。
2.當你使用2-2和2-3小節的構造方法創建Handler對象時,就采用上面代碼中的第二中處理消息的方式,即實現Handler類中的Callback接口中的
handleMessage(msg)方法。值得注意的是,如果Callback接口回調處理消息返回值是false的話,此消息還會調用第三種處理消息方式,因此沒有特殊需求,我們一般在實現Callback接口回調方法時都返回true。
3.那麼上面代碼第一種處理消息的方式handleCallback(msg)是什麼時候觸發的呢?上面代碼加了一個if條件判斷,只有當msg消息中的成員變量
callback不為空時才調用該方式處理消息。那什麼時候callback不為空呢?帶著這個問題繼續分析Handler發送消息的方法。
handler發送消息的方法有多達11種,一聽嚇一跳,這麼多方法怎麼區分?不用怕,我把這些方法分為如下兩類。
send系列 post系列
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//接收消息之後,處理消息
}
};
Message msg = mHandler.obtainMessage();
//發送一個消息碼為0的空消息
mHandler.sendEmptyMessage(0);
//發送一個消息碼為0,絕對時間點為1000ms的空消息,該時間應該大於等於當前時間,下同
mHandler.sendEmptyMessageAtTime(0, 1000);
//發送一個消息碼為0,延時時間為1000ms的空消息
mHandler.sendEmptyMessageDelayed(0, 1000);
//發送一個沒有延時的msg封裝的消息
mHandler.sendMessage(msg);
//發送一個絕對時間點為1000ms的msg封裝的消息
mHandler.sendMessageAtTime(msg, 1000);
//發送一個延時時間為1000ms的msg封裝的消息
mHandler.sendMessageDelayed(msg, 1000);
//立即發送一個msg封裝的消息到消息隊列的最前端
mHandler.sendMessageAtFrontOfQueue(msg);
分析源碼你會發現,除了最後一個方法,以上所有的方法最後都會調用如下方法:
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) uptimeMillis.
* The time-base is {@link android.os.SystemClock#uptimeMillis}.
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + sendMessageAtTime() called with no mQueue);
Log.w(Looper, e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
分析:以上注釋都解釋的很清楚,第一個參數就是Message類封裝的消息,第二個參數就是消息的更新時間,該時間是絕對時間。
mHandler = new Handler();
mHandler.post(new Runnable() {
@Override
public void run() {
//TODO 接收消息之後,處理消息
}
});
mHandler.postAtTime(new Runnable() {
@Override
public void run() {
//TODO 接收消息之後,處理消息
}
}, new Object(), 1000);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//TODO 接收消息之後,處理消息
}
}, 1000);
mHandler.postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
//TODO 接收消息之後,處理消息
}
});
分析:和send系列大有不同,此處我們的Handler無需在去重寫handlerMessage方法來處理消息了,此處將接口類Runnable作為消息發送,然後實現Runnable接口類中的run方法來處理消息。對於這一點我相信很多初學者會有疑問,這裡又沒有Message封裝消息,怎麼將Runnable接口類當作消息發送呢? 一句話,還是跟蹤源碼看看:
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by uptimeMillis.
* The time-base is {@link android.os.SystemClock#uptimeMillis}.
* Time spent in deep sleep will add an additional delay to execution.
* The runnable will be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*
* @see android.os.SystemClock#uptimeMillis
*/
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
分析:不管post系列調用哪個方法,最終都會調用上面這個方法來發送消息,你會驚奇的發現,原來post系列也是調用send系列的方法發送方法的。只不過此處調用了getPostMessage方法將Runnable對象轉換成Message對象而已。跟蹤代碼進入getPostMessage方法:
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
看到沒?調用此方法,將Runnable對象r賦值給了Message類中的clalback成員變量。現在我們回顧一下2-3小節Handler類中的
dispatchMessage方法處理消息的三種方式,此處將Runnable接口對象賦值之後Message類中的msg.callback就不會為空,因此調用第一種處理消息的方式。
不管3-1或者3-2小節調用那種方式發送消息,最終都會調用Handler類中的enqueueMessage成員方法,將消息按照消息更新時間順序插入到消息隊列中,這個過程的分析可以參考1-1小節最後一段內容。
在Message消息封裝者類中有這麼一段注釋:
/*While the constructor of Message is public, the best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
* them from a pool of recycled objects.
*/
這段話告訴我們,當你需要構造一個Message消息對象的時候,最好的方法是調用Message.obtain()或者Handler.obtainMessage()方法來獲得,而不是這樣獲得:
Message msg1 = new Message();
為什麼這樣獲得Message對象不好?我們跟蹤源碼瞧瞧不就知道了。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
這是一個Message類中的靜態方法,方法開頭解釋的很清楚:從全局的消息池中獲得一個新的消息示例,避免用戶多次創建Message消息實例浪費
內存。只有當全局的消息池中沒有可用的消息實例,才去調用new 一個新的Message消息實例。總之,歸納成一句話,調用Message.obtain()或
者Handler.obtainMessage()方法避免多次創建Message實例,這樣性能更加優化。
方法包括如下:
//從消息隊列中移出post系列方法中Runnable對象的消息
mHandler.removeCallbacks(Runnable r);
//從消息隊列中移除消息碼為“what”的消息
mHandler.removeMessages(int what);
//從消息隊列中移除所有post系列方法和send系列方法發送的消息
mHandler.removeCallbacksAndMessages(Object token);
可能很初學者會有疑問,開發中很少用到以上這些方法。那如果你很少使用以上方法,說明你還是一個Android小白。如果不調用以上相應的方法
去移除消息,就會存在Handler對象內存洩漏的隱患。此話怎講?我在一次項目開發中遇到這樣的問題:
我開啟一個新的工作線程WorkThread去執行一段耗時網絡請求,當請求結束之後我調用UI線程中的Handler發送一個Message消息給UI線程,但
是此時,我早已經退出當前持有Handler對象的Activity,這會持有Handler的Activity是接收不到WorkThread發送過來的消息的,因為
Activity已經退出,因此會出現一個bug:Handler對象內存洩露。那怎麼解決這個Handler內存洩露隱患呢?
很簡單,在持有Handler對象的Activity的onDestory方法調用如下代碼去移除消息隊列中的所有消息
mHandler.removeCallbacksAndMessages(null);
此方法只要傳遞一個null空參數就表示移出當前線程中消息隊列中所有的消息。有些人可能會說,我並不想清除消息隊列,可能還有其他Activity等待接收消息,好辦。你可以調用如下相應的方法移除消息隊列中相應的消息:
//從消息隊列中移出post系列方法中Runnable對象的消息
mHandler.removeCallbacks(Runnable r);
//從消息隊列中移除消息碼為“what”的消息
mHandler.removeMessages(int what);
那麼我們是否還有其他辦法解決Handler內存洩漏問題呢?答案是肯定的:將Handler申明為靜態的,因為靜態類不持有外部類的引用。
public class SubActivity extends Activity {
static class StaticHandler extends Handler {
WeakReference mActivityReference;
StaticHandler(Activity activity) {
mActivityReference= new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final SubActivity activity = (SubActivity) mActivityReference.get();
if (activity != null) {
activity.textView.setText(測試靜態Handler解決內存洩漏問題);
}
}
}
}
文章一開頭由“一個Bug引發的思考”我們知道,在線程中使用Handler之前需要調用Looper.prepare方法。愛思考的你會很納悶,那為什麼我
們的UI線程在創建Handler對象的時候沒有調用Looper.prepare方法呢?此時我們不得不從UI線程是怎麼來的說起了。不過這裡不展開討論,直接給出答案,我們創建一個Application的時候,都是由系統的一個ActivityThread類來啟動的。跟蹤代碼進入ActivityThread類:
public final class ActivityThread {
...................
public static void main(String[] args) {
..............
Looper.prepareMainLooper();
..............
}
}
該類中有一個main方法,是不是感覺很熟悉啊?對了,這裡就是我們整個Application應用的入口了。在main方法中調用了Looper.prepareMainLooper(),猜測該方法裡面就調用了Looper.prepare方法來為線程循環loop准備。跟蹤源碼:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(The main Looper has already been prepared.);
}
sMainLooper = myLooper();
}
}
果然,裡面調用了prepare方法。到此,我們就知道其實在UI線程中系統在ActivityThread類中就幫我們調用了Looper.prepare方法為UI線程loop循環做准備。
你可能會遇到這樣一種情況,在UI線程中創建一個工作線程WorkThread執行耗時任務。在正常情況下,這個WorkThread執行完任務之後就銷毀
了。但是你過了一段時間又有一個耗時任務需要執行,這會你會怎麼辦?你只能重新去創建一個WorkThread執行耗時任務。但是線程Thread的每
次創建和銷毀都是很耗系統資源的,因此在這種情況下,我們的HandlerThread就此誕生了。HandelrThread是繼承自Thread實現了,
也就是說HandlerThread是一個線程。只是該線程裡面幫你實現了Looper循環機制,從而使得該線程變成帶有循環機制的線程。言外之
意:當該線程執行完一個耗時任務之後不會馬上被銷毀,除了用戶主動調用HandlerThread類中的quit成員方法來退出當前loop循環。關於
HandlerTread是怎麼使用的?可以參考我的另一篇博客:
Android HandlerThread 源碼分析
通過以上文章分析,基本了解了Android系統的消息處理機制是怎麼一回事,也知道了怎麼使用Handler,以及Handler使用的一些陷阱等。來總結一下 Handler,Message,Looper,MessageQueue,Thread之間的關聯吧。
如果一個線程Thread想要使用Handler類來發送消息,就必須先調用Looper類中的prepare成員方法來做一些准備工作,然後調用Looper類
中的loop方法啟動該線程的循環機制。Handler類把封裝成Message類的消息發送到Looper類中的消息隊列中,然後Looper類中的loop循環方法
中分發消息,最後將相應的消息對象分發到相應的target handler類中去處理消息。
由此我們知道:一個線程Thread中只能擁有一個Looper對象,且一個Looper對象中只能擁有一個MessageQueue消息隊列,但是一個Thread
線程中可以有多個Handler對象,而多個Handler對象共享同一個MessageQueue消息隊列。最後提供一個Handler消息處理機制的流程圖
一、概述隨著Android L版本的發布,RecyclerView已經逐漸地取代了ListView,用來顯示較多的數據集,RecyclerView相比ListView在性
本節學習Activity的狀態保存與恢復。先用例子開始:布局文件主要是實現如下,大家自行編寫Activity邏輯代碼: public class FiveAct
Android WebView加載了Chromium動態庫之後,就可以啟動Chromium渲染引擎了。Chromium渲染引擎由Browser、Render和GPU三端組
英文詞典是手機中經常使用的應用。因此,在本文將結合Android來討論如何實現一個Android版的英文詞典。實現英文詞典的方法很多。在本文使用了SQLite數據庫來保存