Handler內(nèi)存泄露
sendMessage方法內(nèi)存泄露
有這么一個需求闸衫,延遲執(zhí)行一段邏輯,先看第一種方式诽嘉,直接讓線程sleep:
private val handler2 = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
startActivity(Intent(this@HandlerActivity, XXXActivity::class.java))
}
}
private fun test() {
thread {
val message = Message()
SystemClock.sleep(3000) //1
message.apply {
what = 3
obj = "hahaha"
}
handler2.sendMessage(message)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("XX", "onDestroy")
handler2.removeMessages(3) //2
}
從上面的代碼里看到在代碼1處讓程序休眠3秒蔚出,然后點擊返回按鈕銷毀此Activity,那么即使在onDestroy方法里移除掉這個message,也是沒有效果的虫腋。原因在于此時這個message還沒有添加到MessageQueue里骄酗,所以移除的是null。
那么該如何解決呢悦冀,使用以下代碼:
private fun test() {
thread {
val message = Message()
message.apply {
what = 3
obj = "hahaha"
}
handler2.sendMessageDelayed(message,3000)
}
}
使用sendMessageDelayed方法執(zhí)行延遲操作趋翻,即可以避免帶來的內(nèi)存泄露。
為什么不能在子線程new Handler
我們在單獨的線程里初始化一個Handler
private fun test() {
thread {
Handler()
}
}
然后在onCreate方法里調(diào)用盒蟆,發(fā)現(xiàn)會閃退踏烙,報了一個錯誤,如下:
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-4,5,main] that has not called Looper.prepare()
那么為什么我們在子線程中new Handler會報這個異常呢历等,原因是我們的Handler需要初始化一個loop對象讨惩,而我們沒有做。那么為什么在Android的主線程中我們可以直接使用Handler而不報錯呢寒屯,是因為在應(yīng)用啟動的時候已經(jīng)幫我們調(diào)用了初始化loop荐捻。在ActivityThread類里main方法中已經(jīng)幫我們初始化好了loop,這個loop綁定的是主線程。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
......
Looper.prepareMainLooper();
......
}
Looper.prepareMainLooper();
就是這行代碼完成了loop的初始化工作靴患。那么我們再進(jìn)入到這個方法中看看:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
可以看到這里先調(diào)用了prepare方法仍侥,然后初始化sMainLooper對象,并且可以看出sMainLooper只能被初始化一次鸳君,否則被拋出異常农渊。
再進(jìn)入到prepare方法里看看:
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方法初始化了looper對象或颊,并且在looper中綁定了當(dāng)前線程和new除了一個MessageQueue砸紊。并且把這個looper對象放入了sThreadLocal中。然后通過調(diào)用myLooper()方法從sThreadLocal中取得looper對象囱挑,至此完成looper的初始化工作醉顽。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那么,我們該如何在子線程中使用Handler呢平挑,很簡單游添,在我們使用Handler之前調(diào)用一下Looper的prepare方法即可:
private fun test() {
thread {
Looper.prepare();
Handler()
}
}
這樣的話,這個Handler就是運行在我們new出來的子線程中了通熄,當(dāng)然這個Handler也不能去更改UI了唆涝。
更改UI只能在主線程中操作嗎
我們學(xué)習(xí)Android的時候就知道,不能在非UI線程中去更新UI唇辨,否則會報錯廊酣,那么真的是絕對的嗎,看下面的代碼:
private fun test() {
thread {
btnTxt.text = "我哦喔喔"
}
}
我們在子線程中將一個Button控件的text值修改赏枚,并且成功運行沒有報錯亡驰,但是在某些手機或者某些系統(tǒng)上就會拋出異常。那么是為什么呢饿幅?原因是我們在調(diào)用setText的時候會調(diào)用requestLayout();
方法凡辱,這個方法里又會調(diào)用ViewRootImpl的requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
在這個方法中就會檢查線程是否正確,即調(diào)用checkThread方法:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
這里就會做線程的檢查栗恩,如果不是主線程煞茫,就會拋出異常,但是在執(zhí)行requestLayout方法時還會并行的執(zhí)行invalidate方法摄凡,所以续徽,有可能頁面invalidate方法先執(zhí)行了,然后才觸發(fā)checkThread方法亲澡,那么就不會拋出異常钦扭。
我們可以修改一下上面的代碼驗證一下:
private fun test() {
thread {
SystemClock.sleep(1000)
btnTxt.text = "我哦喔喔"
}
}
我們讓修改的代碼延遲一秒執(zhí)行,可以發(fā)現(xiàn)床绪,程序就閃退了客情,并且拋出了上面的異常其弊。
Handler的dispatchMessage方法分析
我們在通過Handler去發(fā)送消息,并執(zhí)行的時候可以有三種方式:
//方式一
private val handler = Handler(Handler.Callback {
when (it.what) {
3 -> {}
else -> {}
}
false
})
//方式二
private val handler2 = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
startActivity(Intent(this@HandlerActivity, AopActivity::class.java))
}
}
//方式三
handler.post(){
btnTxt.text = "handler"
}
那么這三種方式有什么區(qū)別呢膀斋,答案就在Handler的dispatchMessage方法源碼里:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);//1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {//2
return;
}
}
handleMessage(msg);//3
}
}
注釋1處的代碼
讓我先看注釋1處的代碼梭伐,也就是dispatchMessage首先判斷了msg里的callback是否是null,如果不為null仰担,那么就會調(diào)用handleCallback方法糊识。那么這個callback是什么呢,就是我們調(diào)用Handler.post時傳進(jìn)來的Runnable摔蓝。
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到我們調(diào)用post方法時赂苗,會將我們傳進(jìn)來的Runnable封裝到Message對象里并且返回,那么在dispatchMessage方法里這個最先被判斷的就不會為空贮尉,也就會執(zhí)行handleCallback方法:
private static void handleCallback(Message message) {
message.callback.run();
}
這個handleCallback方法也就是我們傳進(jìn)Runnable的run方法拌滋。
注釋2處的代碼
dispatchMessage方法里,如果callback為null了又進(jìn)行判斷mCallback是否為null猜谚。那這個mCallback是什么呢败砂,就是我們初始化Handler對象是在構(gòu)造方法中傳進(jìn)來的Handler.Callback對象,它是一個被定義在Handler中的接口:
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
如果我們在初始化中傳進(jìn)來這個CallBack魏铅,那么將執(zhí)行它里面的handleMessage方法昌犹。
注釋3處的代碼
那么,如果以上這兩個對象都為null的話沦零,將調(diào)用Handler內(nèi)部的handleMessage方法祭隔,這個方法是個空實現(xiàn)货岭,也就是我們自己實現(xiàn)的handleMessage方法路操。
Handler的發(fā)送消息和執(zhí)行消息過程
Handler的發(fā)送消息
當(dāng)我們調(diào)用了Handler的sentXXX方法時,到最終都會調(diào)用enqueueMessage方法千贯,這個方法代碼如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//1
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//2
}
從上面的代碼中可以看出屯仗,這個方法做了兩件事
- 將當(dāng)前的Handler對象賦值給msg.target對象
- 調(diào)用MessageQueue中的enqueueMessage方法
那么,我們再到enqueueMessage方法中看一下邏輯:
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
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 {
......
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;
}
......
}
return true;
}
代碼很長搔谴,其中最重要的一句就是mMessages = msg;即將傳進(jìn)來的msg賦值給全局的mMessages魁袜。這個過程就是Handler的發(fā)送消息的過程。
Handler的執(zhí)行消息
那么我們在發(fā)送消息的時候最終將msg賦值到了MessageQueue中的全局對象mMessages中敦第,是如何將它取出執(zhí)行的呢峰弹。
首先是通過Looper.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;
......
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);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
這段代碼很長,我截取了比較關(guān)鍵的部分芜果,可以看出loop方法先是取出looper對象鞠呈,然后從looper對象中取出MessageQueue對象,接著在一個死循環(huán)中取出queue中的msg右钾,如果為null蚁吝,就返回旱爆。否則就調(diào)用msg.target的dispatchMessage方法,那么這里的msg.target就是剛才發(fā)送消息時綁定的Handler對象窘茁,所以最終會通過Handler的dispatchMessage方法調(diào)用我們的回調(diào)方法怀伦。
至此,Handler的發(fā)送消息和執(zhí)行消息就分析完了山林。