對任何有開發經驗的童鞋,對Handler肯定是如雷貫耳,使用也必定非常熟練,當現在我還是想對其來個梳理,總結下常見的疑問:
1.常聽人說mHandler.obtainMessage(what,obj).sendToTarget()要比{Message msg = new Message;msg.what= 1;msg.obj = obj;mHandler.sendMessage(msg)}要簡潔,要好?
對這樣的傳說,究竟有多少人認真審視過?它是不是真的好?好又好在哪裏?是簡潔還是性能?對這一路的疑惑,我們慢慢道來,這裏面涉及到了Handler也涉及到了Message,下面從源碼來解惑,先來看看Handler類源碼中幾個重要方法:
//構造函數
public Handler() {
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());
}
}
//獲取當前線程的私有looper
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 = null;
}<pre name="code" class="html">//獲取Message對象
public final Message obtainMessage(int what, Object obj)
{
return Message.obtain(this, what, obj);
}
//發送消息
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)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
//派送消息並處理,這也就是之所以我們實現了handleMessage後,就可以得到處理
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
看完Handler後,再來看下Messager類幾個重要方法:
public static Message obtain(Handler h, int what, Object obj) {//對應handler中的obtainMessage
Message m = obtain();
m.target = h;
m.what = what;
m.obj = obj;
return m;
}<pre name="code" class="html">//獲取消息,如果消息池爲null,就創建消息,如果不爲null,則從消息池裏面取消息
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
//回收消息,如果消息池沒有滿則加入消息池中,滿的話,則捨棄
public void recycle() { clearForRecycle(); synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }看完Handler和Messager類後,就可以回到問題了,當採用obtainMessage獲取消息時,當消息池中沒有消息就和new Message方式開銷都是一樣的,都是通過創建Message來獲取消息,如果消息池不爲null,則前者可以直接取消息而不用new了,節省了開銷和空間,因而傳說不欺人也。
2.在線程中創建Handler,如果沒有調用prepare(),爲何一直會報錯,調用了prepare()後,不報錯了但是消息沒有處理?爲何調用prepare和loop後的activity內部線程,在onDestory中沒有調用該線程的quit方法會導致內存泄露?
廢話先別說,直接上Looper類源碼:
Looper構造函數:
private Looper() {
mQueue = new MessageQueue();//創建了個消息隊列
mRun = true;
mThread = Thread.currentThread(); //該looper所在線程屬於當前線程
}
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper()); //作用就是爲該線程創建一個隸屬於自己的Looper
}
public static Looper myLooper() {
return sThreadLocal.get();//取出線程自己所屬的Looper
}
public static void loop() {
Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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();
//此處就是個循環,一直從消息隊列中取消息進程處理,處理完成後,就調用recycle回收msg,結合Message類,就可以知道回收掉的msg,就會重新放入消息池中。
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
long wallStart = 0;
long threadStart = 0;
// 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);
wallStart = SystemClock.currentTimeMicro();
threadStart = SystemClock.currentThreadTimeMicro();
} //msg.target就是handler,調用dispatchMessage就是分發消息,再通過handlerMessage進行處理
msg.target.dispatchMessage(msg);
if (logging != null) {
long wallTime = SystemClock.currentTimeMicro() - wallStart;
long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
if (logging instanceof Profiler) {
((Profiler) logging).profile(msg, wallStart, wallTime,
threadStart, threadTime);
}
}
// 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.recycle();
}
}
}
//結束消息循環,該線程所做任務也就完成了
Looper.quit():
public void quit() {
Message msg = Message.obtain();
// NOTE: By enqueueing directly into the message queue, the
// message is left with a null target. This is how we know it is
// a quit message.
mQueue.enqueueMessage(msg, 0);
}
看完源碼後,我們應該有這樣一個思路,在線程中創建Handler,如果沒有調用Looper.prepare(),則會報錯,原因很簡單,因爲在Handler的構造函數中會去取該線程私有的looper,而線程的私有looper,是在Looper.prepare中通過sThreadLocal設置進去的,而沒有調用prepare,在初始化Handler時取出線程的私有looper則爲null,因此就會報“Can't create handler inside thread that has not called Looper.prepare()”異常。
當調用Looper.prepare()後在handler初始化時就可以獲取到looper,也具備自己的消息隊列了,因而也就不會報錯,但沒有調用Looper.loop(),則不會執行handler發出的消息,looper.loop()方法通過源碼我們可以知道,它的工作就是一直從線程私有消息隊列中取消息,並調用handler的dispatchMessage方法去分派消息,最後處理完成後,就將該消息所佔用的空間回收值消息池中,如果沒有調用Looper.loop(),則表示沒有從消息隊列中取消息進行處理的意向,因而也不會執行你所實現的handleMessage。
當我們在線程中調用了Looper.prepare()和Looper.loop(),如果此時線程是activity的內部線程或匿名線程,如果沒有該線程的quit方法,則會造成內存泄露,其原因就是調用Looper.loop()後,其內部就是一個死循環,如果不調用quit,則會一直存活,加之該activity的隱含this也存活在該線程中,因而在activity銷燬後,沒有調用quit方法,會導致該線程還是一直在運行,造成activity隱含的this沒有得到釋放,進而導致泄露。
說到此處,總結下Thread,Looper,Handler,MessageQueue,Message的關係:
當我們創建個線程後,調用了Looper.prepare(),此時就會爲線程創建一份私有的looper和消息隊列MessageQueue,如果此時在創建Handler,就表示該Handler存放消息的位置就是該線程的MessageQueue,調用Looper.loop後,表示會一直從MessageQueue中取消息取處理,並執行Handler中實現的handleMessage方法,處理完成msg後,則會回收msg所佔空間值Message的消息池中。