Handler、HandlerThread及handler記憶體洩漏
在Android開發中,handler機制可以說是使用的最為頻繁的一種機
過,但是這些部落格都是看了就忘,沒有留下太深的印象,而且因為
是別人寫的東西,個人的理解不夠深入,這裡就結合原始碼和官方文
檔,做一個詳細一點的handler機制分析,加深印象,也便於自己以後
翻看。
Handler、Looper、MessageQueue和Message
handler原始碼分析:
建立handler:
handler物件的建立主要是提供4個成員變數:
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;//這裡的MessageQueue實際為looper的一個屬性
mCallback = callback;
mAsynchronous = async;
}
從這4個成員變數的定義來看,其均被定義為final型別,即必須在物件建立時賦值:
final MessageQueue mQueue;//繫結的MessageQueue
final Looper mLooper;//繫結的Looper物件
final Callback mCallback;//Callback中定義了handleMessage方法,可以通過實現Callback介面,new Handler(Callback)的方式例項化handler物件,避免寫成內部類,造成程式碼結構混亂,一般很少看到指定這個,直接繼承handler,重寫handleMessage即可
final boolean mAsynchronous;//看起來像一個標誌位,預設指定為false
ok,綜合分析下來,在例項化handler物件時,looper物件是必須指定的,但是,我們平常例項化Handler方式並沒有傳入looper物件,這是為什麼呢?原始碼中一下部分解釋了這個問題:
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()" );
}
從上面程式碼我們可以看出:這裡如果mLooper物件為空,直接丟擲異常。
而我們平常的寫法並不會丟擲異常,就只有一種解釋了:Looper.myLooper創建出了物件,或者將一個已經建立好的looper物件傳了進來。
Looper.myLooper原始碼如下:
public static Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal是一個ThreadLocal物件,在不同的執行緒中呼叫其get方法時,返回該執行緒對應的一個looper物件(可以暫時理解為一個key為thread,value為Looper的HashMap,實際情況比這個要複雜,後續我會再寫篇部落格對ThreadLocal原始碼進行分析)。
結合先前的結論,我們可以得出sThreadLocal.get()返回了不為null的Looper物件。
這裡我們直接結果論,在Android應用主執行緒啟動時(從原始碼上看是在Application例項化前),會自動建立主執行緒的Looper,這個過程在ActivityThread.main()函式中完成:
public static void main(String[] args) {
.....
Looper.prepareMainLooper();
.....
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
如果我們在子執行緒中進行直接新建Handler而不新建Handler物件,則會丟擲異常。
從上面的原始碼中可以看出,主執行緒自動建立handler物件且啟動之,主執行緒中的Looper物件是同步的,而且可以看出,主執行緒只能有一個Looper物件例項與之對應。如果多次新建,則會丟擲異常。其實,對所有執行緒均是如此。
Looper物件建立時(呼叫Looper.prepa()),均是通過 prepare(boolean quitAllowed)方法例項化。
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));
}
如果多次建立Looper物件,則會丟擲異常。
OK,接下來我麼看handler例項化過程中的MessageQueue物件mQueue,其為mLooper物件中的一個屬性,在呼叫Looper.loop()方法時被使用,具體使用後續分析,這裡先看MessageQueue的資料結果結構.
MessageQueue用於管理Message物件(對新增的Message物件進行排序和提供下一個被處理的Message物件),雖然從名字上看是一個佇列,但是實際結果為一個連結串列結構,在其初始化方法中我們沒有辦法看出,但是從其next方法和enqueueMessage方法可以看出,這兩個方法中的操作都是典型的列表操作,不向佇列一般有嚴格的先進先出原則(因為訊息本身有延時屬性,後新增的訊息可能比先新增的佇列先出)。
接下來,Message類的結構:
public int what;//標誌一個message型別
public int arg1;
public int arg2;
public Object obj;//提供一些屬性,使用者可以通過設定這些屬性值,用於傳遞具體資訊
long when;//訊息應該發出的時間
Bundle data;//message也可以用於傳遞bundle物件
Handler target;//message應該傳遞給哪一個Handler物件,一個執行緒可以有多個handler物件,但是隻有一個looper物件
Runnable callback;//使用handler.post(Runnable)方法時,會將對應的Runnable物件傳遞過來,用於之後的執行
Message next;//指向下一條訊息
private static final Object sPoolSync = new Object();//一個用於同步的物件,在對程序內的訊息池進行操作時使用
private static Message sPool;//當前訊息池中用於返回的物件
private static int sPoolSize = 0;//當前訊息池大小
private static final int MAX_POOL_SIZE = 50;//最大訊息池大小
關於Message物件的結構大約就這麼多,一個需要注意的地方是訊息池。
呼叫recycle方法時,會將一個Message物件新增到訊息池中。
public void recycle() {
//清除物件的所有屬性值
clearForRecycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;//將當前物件新增到訊息池連結串列頭部,這個訊息池的結構應該是一個棧結構,後進先出
sPoolSize++;
}
}
}
呼叫obtain方法時,會從訊息池中取出一個Message物件,也即當前的sPool物件。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;//取出當前連結串列最前面的訊息
sPool = m.next;
m.next = null;//重置指標
sPoolSize--;
return m;
}
}
return new Message();//如果當前訊息池中沒有訊息,則新建一個訊息物件
}
主要的類已經分析的差不多了,這裡做一個基本的總結:
1. 建立handler物件需要Looper物件
2. 每個執行緒有且只有一個Looper物件,主執行緒自身已經建立了一個Looper物件,可以直接使用
3. 每個Looper物件中有一個MessageQueue物件,負責管理和提供Message物件,MessageQueue使用的是連結串列結構
4. Message物件指定了目標Handler物件,傳送時間和用於傳遞的資訊物件,Message管理著一個訊息池,用於回收利用Message物件,這個訊息池為棧結構
接下來,我們開始分析handler的工作流程:
使用handler傳送訊息有如下幾種方式:
//1.handler.sendMessage(Message msg)
public final boolean sendMessage(Message msg)\{
return sendMessageDelayed(msg, 0);
}
// 2.handler.post(Runnable r)
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
// 3.Activity.runOnUiThread(Runnable action)
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
繼續點選原始碼檢視,所有的方式執行的語句為:
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);
}
故,傳送訊息的方式為 enqueueMessage(queue, msg, uptimeMillis),其原始碼如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
均是通過呼叫mQueue.enqueueMessage(msg, uptimeMillis)實現.
我們再來看MessageQueue.enqueueMessage方法的實現:
boolean enqueueMessage(Message msg, long when) {
//不能重複新增正在使用的msg
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
//msg必須有handler來接收
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
}
synchronized (this) {
//正在退出是時不能新增訊息
if (mQuitting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
}
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//連結串列中沒有訊息||新加入的訊息需要在連結串列最前方的訊息之前發出
//將當前訊息物件新增到訊息池連結串列頭部
msg.next = p;
mMessages = msg;
//如果當前looper被阻塞(之前沒訊息需要傳送),需要喚醒
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; //
prev.next = msg;
}
// 需要喚醒時,將本線looper喚醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
ok,訊息已經發送出去了,接下來需要在handler.handleMessage()方法中獲取訊息並處理,那麼,訊息怎麼傳送過來呢,
這個就需要通過Looper的運行了,在ActivityThread的main方法中我們找到looper方法的執行方式如下:
Looper.loop();
那麼玄機一定就在這了,其原始碼如下:
public static void loop() {
//獲取當前執行緒的的Looper物件
final Looper me = myLooper();
//在呼叫之前,Looper物件必須存在
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
......
//死迴圈,通過queue.next()方法獲取訊息物件,並呼叫msg.target.dispatchMessage(msg)方法
for (;;) {
......
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
......
//回收已經處理的訊息
msg.recycle();
}
}
ok,這裡的核心就是queue.next()和msg.target.dispatchMessage(msg)了
Message next() {
......
//死迴圈,獲取連結串列中的訊息
for (;;) {
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) {
if (now < msg.when) {
// 如果還沒有到訊息的傳送時間,等
// Next message is not ready. Set a timeout to wake up when it is ready.
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;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
......
}
......
}
.....
}
queue.next()就是從連結串列中取出已經已經可以傳送的訊息。
接下來看msg.target.dispatchMessage(msg),msg.target就是msg對應的handler物件。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
這裡的訊息分發機制:
1. 如果msg有Runnable需要執行,呼叫其run方法執行(注意這裡只是呼叫run方法,不是啟動一個執行緒)
2. 如果指定了CallBack,呼叫其handleMessage方法
3. 呼叫handler的handleMessage方法
上述過程可以用如圖所示表示出來:
圖片引用自 Android<我所理解的Handler機制>
在上述分析中,我們得出下列結論:
1. Looper.loop()方法會一直迴圈取其對應的MessageQueue中的訊息物件並分發給其對應的handler
2. handleMessage方法在Looper對應的執行緒中呼叫
3. 這個訊息機制是一個命令模式的使用案例,message作為一個命令,其中儲存了命令型別(what)和內容(arg1,arg2,obj),以及命令接收者(target),handler把命令發出去,將命令的執行者設定為自己,追蹤在接收到命令後,做出相應反應
HandlerThread相關
HandlerThread的使用很簡單,建立HandlerThread物件,並將之與handler物件關聯,之後就不需要他了
HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg){
}
};
handler.sendEmptyMessage(0x01);
其實,handlerthread的意義就是建立了一個執行緒,使用這個執行緒的looper物件建立handler物件,之後該handler的handleMessage方法在HandlerThread物件中執行。
比如我有一個需要往磁碟寫log的程式,而且我不希望其在主執行緒中執行,這時我可以新建一個HandlerThread物件,使用其looper物件,將主執行緒中需要列印的訊息傳送到子執行緒中去執行(這與我們新建一個子執行緒,在其run方法中呼叫looper.perpare()方法後,再在run方法最後一句新增looper.loop()是一致的,事實上HandlerThread也是這麼實現的)。
其核心方法為run(繼承自Thread類,本身為一個執行緒)和getLooper方法:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
run方法就是一個準備looper和執行looper的過程,getLooper就是返回執行緒Looper物件而已,這裡不做多述。
handler導致Activity記憶體洩漏
不想解釋得過於複雜,就這麼說吧,從上面的原始碼可以看出來,message應用了handler物件(通過target屬性),而handler為Activity的屬性,所以如果Activity被finish的時候,如果handler不能被回收,這Activity就不能被回收,在還有訊息沒有被處理(比如訊息指定在10分鐘後發出)的時候會出現這種情況。這就是handler導致Activity記憶體洩漏的問題原因。
為了避免這個問題,Android推薦的方式是將handler寫成靜態內部類,handler使用弱應用對Activity進行引用。
程式碼如下:
程式碼引用自:Android App 記憶體洩露之Handler
/**
*
* 實現的主要功能。
*
* @version 1.0.0
* @author Abay Zhuang <br/>
* Create at 2014-7-28
*/
public class HandlerActivity2 extends Activity {
private static final int MESSAGE_1 = 1;
private static final int MESSAGE_2 = 2;
private static final int MESSAGE_3 = 3;
private final Handler mHandler = new MyHandler(this);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
// just finish this activity
finish();
}
public void todo() {
};
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity2> mActivity;
public MyHandler(HandlerActivity2 activity) {
mActivity = new WeakReference<HandlerActivity2>(activity);
}
@Override
public void handleMessage(Message msg) {
System.out.println(msg);
if (mActivity.get() == null) {
return;
}
mActivity.get().todo();
}
}
HandlerActivity2在finish後,因為mHandler對其為弱引用,所以可以正常回收,但是這是mHandler依然存在,所以在handleMessage中需要判斷其引用的HandlerActivity2是否還存在。
mHandler在離開HandlerActivity2就不需要了,但是作為靜態物件依然會存在,但是又不能將其nullfy掉(否則訊息到的時候就是空指標異常了),所以其會一直存活到應用結束。
@Override
public void onDestroy() {
// If null, all callbacks and messages will be removed.
mHandler.removeCallbacksAndMessages(null);
}
這樣會把target為mHandler的物件的訊息從佇列中移除掉,個人覺得這個方法更好
我不知道Google為什麼不推薦下面這種方式,而推薦實用靜態類,但是為了避免煩人的handlerleak警告,還是從了吧。