Handler是Android中的消息處理機制议蟆,是一種線程間通信的解決方案闷沥,同時你也可以理解為它天然的為我們在主線程創(chuàng)建一個隊列,隊列中的消息順序就是我們設(shè)置的延遲的時間咐容。
簡單使用
一般是在主線程中實現(xiàn)一個Handler舆逃,然后在子線程中使用它。
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子線程中通過自定義的 Handler 發(fā)消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定義一個 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主線程:handleMessage: ${msg.what}")
}
}
}
或者有時候需要在子線程中創(chuàng)建運行在主線程中的Handler
class HandlerActivity: AppCompatActivity() {
private var mHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
thread {
//獲得main looper 運行在主線程
mHandler = MyHandler(Looper.getMainLooper())
mHandler!!.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定義一個 Handler
class MyHandler(): Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主線程:handleMessage: ${msg.what}")
}
}
}
在第二個用法中出現(xiàn)了一個Looper.getMainLooper()
戳粒,使用它作為參數(shù)路狮,即使MyHandler是在子線程中定義的,但是它的handleMessage
方法依然運行在主線程蔚约。我們看一下Handler的構(gòu)造方法之一:
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
handleMessage方法具體運行在哪個線程是和這個Looper息息相關(guān)的奄妨。
概述
這就是整個Handler在Java層的流程示意圖∑凰睿可以看到砸抛,在Handler
調(diào)用sendMessage
方法以后,Message
對象會被添加到MessageQueue
中去苔咪。而這個MessageQueue
就是被包裹在了Looper
中锰悼。那么Looper
對象是干什么的呢?它和Handler
是什么關(guān)系呢团赏?我們來看一下他們具體的職責吧~
- Handler
消息機制中作為一個對外暴露的工具箕般,其內(nèi)部持有了一個
Looper
。負責Message
的發(fā)送及處理舔清。
Handler.post(Runnable r)/Handler.sendMessage(Message msg)
:向消息隊列發(fā)送各種消息事件丝里;
Handler.handleMessage()
:處理相應(yīng)的消息事件
- Looper
作為消息循環(huán)的核心曲初,其內(nèi)部包含了一個消息隊列
MessageQueue
,用于記錄所有待處理的消息杯聚;通過Looper.loop()
不斷地從MessageQueue
中抽取Message臼婆,按分發(fā)機制將消息分發(fā)給目標處理者,可以看成是消息泵幌绍。注意颁褂,線程切換就是在這一步完成的。
- MessageQueue
則作為一個消息隊列傀广,則包含了一系列鏈接在一起的
Message
颁独;不要被這個Queue
的名字給迷惑了,就以為它是一個隊列伪冰,但其實內(nèi)部通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表誓酒,等待Looper
的抽取。
- Message
則是消息體贮聂,內(nèi)部又包含了一個目標處理器
target
靠柑,這個target
正是最終處理它的Handler。
Handler
從我們大家最熟悉的sendMessage方法說起吓懈。sendMessage方法見名思意歼冰,就是發(fā)送一個信息,可是要發(fā)送到哪里去呢骄瓣,這是代碼:
# Handler.java
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull 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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// focus -1
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
不管我們是通過post/send發(fā)送消息停巷,最終都會調(diào)用到enqueueMessage
方法,在這個方法中做了兩件事:
1.給Message賦值
2.將Message按照時間順序放入隊列
最后的入隊列是在MessageQueue
中完成的榕栏,已經(jīng)不再是Handler
的方法了畔勤,也就是說,調(diào)用走到了這里扒磁。事件的流向已經(jīng)不歸Handler
管了庆揪。Handler
只負責把Message
發(fā)送出去,然后等待時機處理這條Message
妨托,至于Message
存取的過程跟Handler
沒有關(guān)系缸榛。
注意focus1處的代碼,Message
將當前的Handler
對象賦值給了target
字段兰伤,所以說:
Handler
在發(fā)送Message
(消息)時内颗,每個發(fā)出去的Message
都持有把它發(fā)出去的Handler
的引用。
MessageQueue
MessageQueue
是一個由單鏈表構(gòu)成的優(yōu)先級隊列(取的都是頭部敦腔,所以說是隊列)找前。前面提到Handler
發(fā)送消息入隊是由MessageQueue
來完成的,那么MessageQueue
是在哪里定義好的呢躺盛?
答案是在Handler
的構(gòu)造函數(shù)中:
public Handler(@Nullable Callback callback, boolean async) {
// ...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
// ...
}
可以看到项戴,Handler
中持有的MessageQueue
是從Looper
中拿到的。關(guān)于Looper
稍后再講槽惫。
上面說到周叮,最后發(fā)送消息都調(diào)用的是MessageQueue
的queue.enqueueMessage(msg, uptimeMillis)
方法。現(xiàn)在我們已經(jīng)拿到了queue,進去看看這個方法它做了什么。
// MessageQueue.java
//省略部分代碼
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//【1】拿到隊列頭部
Message p = mMessages;
boolean needWake;
//【2】如果消息不需要延時北秽,或者消息的執(zhí)行時間比頭部消息早畴蹭,插到隊列頭部
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 {
//【3】消息插到隊列中間
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
從以上代碼也可看出來:
在多個Handler
往MessageQueue
中添加數(shù)據(jù)(發(fā)送消息時各個Handler
可能處于不同線程)時得糜,其內(nèi)部通過synchronized
關(guān)鍵字保證線程安全。同時messagequeue.next()
內(nèi)部也會通過synchronized
加鎖晰洒,確保取的時候線程安全朝抖,同時插入也會加鎖。
1.mMessages 是隊列的第一消息谍珊,獲取到它判斷消息隊列是不是空的侮邀,是則將當前的消息放到隊列頭部;
2.如果當前消息不需要延時贝润,或當前消息的執(zhí)行時間比頭部消息早绊茧,也是放到隊列頭部。
3.如果不是以上情況打掘,說明當前隊列不為空华畏,并且隊列的頭部消息執(zhí)行時間比當前消息早,需要將它插入到隊列的中間位置尊蚁。
如何判斷這個位置呢亡笑?依然是通過消息被執(zhí)行的時間。
通過遍歷整個隊列横朋,當隊列中的某個消息的執(zhí)行時間比當前消息晚時仑乌,將消息插到這個消息的前面。
可以看到,消息隊列是一個根據(jù)消息【執(zhí)行時間先后】連接起來的單向鏈表绝骚。想要獲取可執(zhí)行的消息耐版,只需要遍歷這個列表,對比當前時間與消息的執(zhí)行時間压汪,就知道消息是否需要執(zhí)行了粪牲。
Looper
Handler
中的MessageQueue
對象其實就是Handler
中的Looper
它的MessageQueue
,Handler
往MessageQueue
中添加消息止剖,其實就是往Handler
的Looper
所持有的MessageQueue
中添加對象腺阳。
簡單來說,Looper
和MessageQueue
是一對一的關(guān)系,一個Looper
持有一個MessageQueue
對象穿香。
回到之前Handler構(gòu)造函數(shù)的代碼:
//Handler.java
//省略部分代碼
public Handler(@Nullable Callback callback, boolean async) {
//敲黑板亭引,劃重點就是這一句!Fせ瘛1候尽!
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
}
在這一句中Handler通過Looper.myLooper
方法獲取到了Looper對象洒宝,當然购公,也有可能沒獲取到。不過雁歌,你如果沒獲取到就要拋異常了宏浩。
# Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
TreadLocal
//sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal
是一個ThreadLocal
類,并且它的泛型是Looper
對象靠瞎。ThreadLocal
提供了線程的局部變量比庄,每個線程都可以通過set()和get()來對這個局部變量進行操作,但不會和其他線程的局部變量進行沖突乏盐,實現(xiàn)了線程的數(shù)據(jù)隔離佳窑。簡要言之:往ThreadLocal
中填充的變量屬于當前線程,該變量對其他線程而言是隔離的父能。
可以看到Looper對象是通過ThreadLocal獲取的华嘹,那么ThreadLocal是何時放進去的呢?查看sThreadLoacl.set()
方法的調(diào)用位置:
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));
}
可以看得出法竞,最后調(diào)用了是prepare(boolean quitAllowed)
方法耙厚,而這個方法首先判斷,如果sThreadLocal
有值岔霸,就拋異常薛躬,沒有值才會塞進去一個值。其實很好理解呆细,就是說prepare
方法必須調(diào)用但也只能調(diào)用一次型宝,不調(diào)用沒有值八匠,拋異常,調(diào)用多次也還拋異常趴酣。
如果
Looper
為空就拋異常梨树,現(xiàn)在我們知道了,什么時候Looper
為空呢岖寞?沒有調(diào)用prepare
方法的時候會為null抡四。
也就是說在構(gòu)造Handler
之前,必須得有Looper
對象仗谆,換言之指巡,在構(gòu)造Handler
之前,必須調(diào)用Looper
的prepare
方法創(chuàng)建Looper
隶垮。
接下來再看看這行sThreadLocal.set(new Looper(quitAllowed));
做了什么吧藻雪,它是如何塞進去的呢?
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法首先獲取到了當前的線程狸吞,然后獲取一個map勉耀。這個map是以鍵值對形式存儲內(nèi)容的。如果獲取的map為空蹋偏,就創(chuàng)建一個map瑰排。如果不為空就塞進去值。要注意的是暖侨,這里面的key
是當前的線程,這里面的value
就是Looper崇渗。也就是說字逗,線程和Looper
是一一對應(yīng)的。也就是很多人說的Looper
和線程綁定了宅广,其實就是以鍵值對形式存進了一個map中葫掉。
Looper
中的MessageQueue
是在Looper
的構(gòu)造函數(shù)中創(chuàng)建的:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper
是和當前線程綁定的。并且每個線程中只有一個Looper
和MessageQueue
跟狱。
前面提到俭厚,創(chuàng)建Handler
對象之前需要調(diào)用Looper.prepare()
方法先創(chuàng)建一個Looper
對象,但我們在activity頁面中創(chuàng)建Handler
的時候并沒有這么做驶臊,那是因為主線程默認已經(jīng)創(chuàng)建Looper
了挪挤。因此我們先來看看主線程中是如何處理的。
查看ActivityThread類关翎,這是整個app的入口扛门,內(nèi)部有一個main方法,它是程序的入口:
//ActivityThread.java
public static void main(String[] args) {
···
Looper.prepareMainLooper();
···
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到在ActivityThread
中的main
方法中纵寝,我們先調(diào)用了Looper.prepareMainLooper()
方法论寨,然后獲取當前線程的Handler
,最后調(diào)用Looper.loop()
。先來看一下Looper.prepareMainLooper()
方法
//Looper.java
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
// 設(shè)置不可以退出的Looper
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//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));
}
Looper.loop
Looper雖說要分發(fā)消息葬凳,但是它又不知道你什么時候會發(fā)送消息绰垂,只能開啟一個死循環(huán),不斷的嘗試從隊列中拿數(shù)據(jù)火焰。這個死循環(huán)在哪里開始的劲装?Looper.loop()開啟了一個死循環(huán),然后不斷的嘗試去隊列中拿消息荐健。
// Looper.java
public static void loop() {
//拿到當前線程的Looper
final Looper me = myLooper();
...
//拿到Looper的消息隊列
final MessageQueue queue = me.mQueue;
...
//1 這里開啟了死循環(huán)
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg);
...
// 回收復用
msg.recycleUnchecked();
}
}
在循環(huán)中Looper不停的取出消息酱畅,拿到Message對象以后,會去調(diào)用Message
的target字段的dispatchMessage
方法;target正是發(fā)送這條消息的Handler
江场。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果 callback 處理了該 msg 并且返回 true纺酸, 就不會再回調(diào) handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果Message這個對象有CallBack回調(diào)的話,這個CallBack實際上是個Runnable址否,就只執(zhí)行這個回調(diào)餐蔬,然后就結(jié)束了。
如果Message對象沒有CallBack回調(diào)佑附,進入else分支判斷Handler的CallBack是否為空樊诺,不為空執(zhí)行CallBack的handleMessage方法,然后return音同,構(gòu)建Handler的CallBack代碼如下:
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
//retrun true词爬,就不執(zhí)行下面的邏輯了,可以用于做優(yōu)先級的處理
return false;
}
};
最后才調(diào)用到Handler的handleMessage()函數(shù)权均,也就是我們經(jīng)常去重寫的函數(shù)顿膨,在該方法中做消息的處理。
消息攔截
可以看到Handler.Callback 有優(yōu)先處理消息的權(quán)利 叽赊,當一條消息被 Callback 處理并攔截(返回 true)恋沃,那么 Handler 的 handleMessage(msg) 方法就不會被調(diào)用了;
如果 Callback 處理了消息必指,但是并沒有攔截囊咏,那么就意味著一個消息可以同時被 Callback 以及 Handler 處理。我們可以利用CallBack這個攔截來攔截Handler的消息塔橡。
Handler是如何進行線程切換的
線程間是共享資源的梅割,子線程通過handler.sendXXX,handler.postXXX等方法發(fā)送消息葛家,然后通過Looper.loop()在消息隊列中不斷的循環(huán)檢索消息炮捧,最后交給handle.dispatchMessage方法進行消息的分發(fā)處理。
子線程可以更新UI嗎惦银?
查看以下代碼:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
//創(chuàng)建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();
}
這里需要注意在所有事情處理完成后應(yīng)該調(diào)用quit方法來終止消息循環(huán)末誓,否則這個子線程就會一直處于循環(huán)等待的狀態(tài),因此不需要的時候終止Looper书蚪,調(diào)用
Looper.myLooper().quit()
喇澡。
以上代碼是否能正確彈出Toast呢?答案是肯定的殊校。
在ViewRootImpl中的checkThread方法會校驗mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構(gòu)造器中晴玖,也就是說一個創(chuàng)建ViewRootImpl線程必須和調(diào)用checkThread所在的線程一致,UI的更新并非只能在主線程才能進行为流。
線程中更新UI的重點是創(chuàng)建它的ViewRootImpl和checkThread所在的線程是否一致呕屎。
系統(tǒng)為什么不建議在子線程中訪問UI?
這是因為 Android 的UI控件不是線程安全的敬察,如果在多線程中并發(fā)訪問可能會導致UI控件處于不可預期的狀態(tài)秀睛。
那么為什么系統(tǒng)不對UI控件的訪問加上鎖機制呢?
缺點有兩個:
1.首先加上鎖機制會讓UI訪問的邏輯變得復雜
2.鎖機制會降低UI訪問的效率莲祸,因為鎖機制會阻塞某些線程的執(zhí)行蹂安。
所以最簡單且高效的方法就是采用單線程模型來處理UI操作。
子線程如何通知主線程更新UI(都是通過Handle發(fā)送消息到主線程操作UI的)
- 主線程中定義 Handler锐帜,子線程通過 mHandler 發(fā)送消息田盈,主線程 Handler 的 handleMessage 更新UI。
- 用 Activity 對象的 runOnUiThread 方法缴阎。
- 創(chuàng)建 Handler允瞧,傳入 getMainLooper。
- View.post(Runnable r) 蛮拔。
Looper死循環(huán)為什么不會導致應(yīng)用卡死述暂,會耗費大量資源嗎?
應(yīng)用被卡死本質(zhì)上不是阻塞了主線程语泽,而是阻塞了Looper的loop方法。導致loop方法無法處理其他事件视卢,導致出現(xiàn)了ANR事件踱卵。
從前面的主線程、子線程的分析可以看出据过,Looper會在線程中不斷的檢索消息惋砂,如果是子線程的Looper死循環(huán),一旦任務(wù)完成绳锅,用戶應(yīng)該手動退出西饵,而不是讓其一直休眠等待。
線程其實就是一段可執(zhí)行的代碼鳞芙,當可執(zhí)行的代碼執(zhí)行完成后眷柔,線程的生命周期便該終止了期虾,線程退出。而對于主線程驯嘱,我們是絕不希望會被運行一段時間镶苞,自己就退出,那么如何保證能一直存活呢鞠评?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的茂蚓,死循環(huán)便能保證不會被退出,例如剃幌,binder 線程也是采用死循環(huán)的方法聋涨,通過循環(huán)方式不同與 Binder 驅(qū)動進行讀寫操作,當然并非簡單地死循環(huán)负乡,無消息時會休眠牍白。Android是基于消息處理機制的,用戶的行為都在這個Looper循環(huán)中敬鬓,我們在休眠時點擊屏幕淹朋,便喚醒主線程繼續(xù)進行工作。
主線程的死循環(huán)一直運行是不是特別消耗 CPU 資源呢钉答?其實不然础芍,這里就涉及到 Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時数尿,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里仑性,此時主線程會釋放 CPU 資源進入休眠狀態(tài),直到下個消息到達或者有事務(wù)發(fā)生右蹦,通過往 pipe 管道寫端寫入數(shù)據(jù)來喚醒主線程工作诊杆。這里采用的 epoll 機制,是一種IO多路復用機制何陆,可以同時監(jiān)控多個描述符晨汹,當某個描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進行讀或?qū)懖僮鞔ぃ举|(zhì)同步I/O淘这,即讀寫是阻塞的。所以說巩剖,主線程大多數(shù)時候都是處于休眠狀態(tài)铝穷,并不會消耗大量CPU資源。
主線程的Looper何時退出
在App退出時佳魔,ActivityThread中的mH(Handler)收到消息后曙聂,執(zhí)行退出。
//ActivityThread.java
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
如何處理Handler使用不當造成的內(nèi)存泄漏鞠鲜?
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子線程中通過自定義的 Handler 發(fā)消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定義一個 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主線程:handleMessage: ${msg.what}")
}
}
}
再發(fā)送延時消息之前宁脊,app推出了断国,那么handleMessage方法還會執(zhí)行嗎?答案是會的朦佩。
MyHandler 是 HandlerActivity 的內(nèi)部類并思,會持有 HandlerActivity 的引用。在進入頁面以后语稠,發(fā)送了一個延時 1s 的消息宋彼,如果 HandlerActivity 在 1s 內(nèi)退出了,由于 Handler 會被 Message 持有仙畦,保存在其 target 變量中输涕,而 Message 又會被保存在消息隊列中,這一系列關(guān)聯(lián)慨畸,導致 HandlerActivity 在退出的時候莱坎,依然會被持有,因此不能被 GC 回收寸士,這就是內(nèi)存泄漏檐什!當這個 1s 延時的消息被執(zhí)行完以后,HandlerActivity 會被回收弱卡。
有延時消息乃正,在界面關(guān)閉后及時移除Message/Runnable,調(diào)用handler.removeCallbacksAndMessages(null)
內(nèi)部類導致的內(nèi)存泄漏改為靜態(tài)內(nèi)部類婶博,并對上下文或者Activity/Fragment使用弱引用瓮具。
正確創(chuàng)建Message實例
1.通過 Message 的靜態(tài)方法 Message.obtain() 獲取凡人;
2.通過 Handler 的公有方法 handler.obtainMessage()
// Message.java
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();
}
sPool是消息池名党,obtain會先從消息池中獲取Message對象,避免通過new創(chuàng)建過多的對象挠轴。