該文章屬于Android Handler系列文章遮精,如果想了解更多丽惭,請點擊
《Android Handler機(jī)制之總目錄》
前言
整個Handler機(jī)制系列文章到此就結(jié)束了陨享,相信大家基本已經(jīng)將整個Handler機(jī)制消化的差不多了鄙陡,現(xiàn)在就剩下最后一個知識點乃正,在平時開發(fā)中使用Handler有可能會導(dǎo)致內(nèi)存泄漏
的問題诺苹。下面我們就一起去了解了解~~
內(nèi)存泄漏
內(nèi)存泄漏在官方的定義如下:
內(nèi)存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放咕晋,造成系統(tǒng)內(nèi)存的浪費,導(dǎo)致程序運行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果筝尾。
那么翻譯成人話捡需,就是當(dāng)一個對象不再被使用時,本該被系統(tǒng)回收筹淫,但卻因為有另外一個正在使用的對象持有它的引用站辉,導(dǎo)致其不能被回收呢撞,造成內(nèi)存的浪費。
那么針對于Android系統(tǒng)來說饰剥,在Android系統(tǒng)中會為每個應(yīng)用程序分配相應(yīng)的內(nèi)存(根據(jù)手機(jī)廠商的不同殊霞,分配的內(nèi)存大小會有所差異)。也就是說對于每一個應(yīng)用程序來說其內(nèi)存是有限的汰蓉。那么當(dāng)某個應(yīng)用程序產(chǎn)生的內(nèi)存泄漏較多時绷蹲,導(dǎo)致達(dá)到應(yīng)用總的內(nèi)存閥值,那么就會導(dǎo)致應(yīng)用崩潰顾孽。
Handler內(nèi)存泄漏的情況討論
為了分析Handler內(nèi)存泄漏的具體情況祝钢,請參看以下示例代碼:
public class HandlerLeakageActivity extends BaseActivity {
public static final int UPDATE_UI = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_UI) {
updateUI();
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_leakage);
Message message = Message.obtain();
message.what = UPDATE_UI;
mHandler.sendMessageDelayed(message, 1000 * 3600 * 24);//發(fā)送延時24小時消息
}
//更新ui
private void updateUI() {...}
}
上述代碼邏輯很簡單,我們在HandlerLeakageActivity 中創(chuàng)建了內(nèi)部類Handler若厚,同時發(fā)送了一個延時為24小時的消息拦英。當(dāng)HandlerLeakageActivity 收到這個延遲消息后,那么接著會來更新UI测秸,同時我們可以得到以下引用鏈:
其中的內(nèi)部類Handler 擁有當(dāng)前Activity的引用疤估,是因為
在Java中,非靜態(tài)內(nèi)部類會持有外部類的引用
霎冯,而Messagey擁有Handler的引用铃拇,是因為Message通過Looper的loop()方法取出后,需要相應(yīng)的Handler來處理消息(msg.target ==發(fā)送消息的Handler
)沈撞。
那么在整個Handler機(jī)制下的引用關(guān)系如下圖所示:
參照上圖慷荔,我們設(shè)想一種情況,假設(shè)我們在程序啟動的時候关串,首先進(jìn)入HandlerLeakageActivity 拧廊,然后又將其finish掉。那么就會出現(xiàn)晋修,因為延遲消息的遲遲不能被取出執(zhí)行,導(dǎo)致該Activity不能被系統(tǒng)回收倦春。從而造成上文我們提到過的內(nèi)存泄漏
。
那么問題來了睁本,什么時候引用鏈會斷開?
在文章《Android Handler機(jī)制之Message及Message回收機(jī)制 》
中忠怖,我們曾經(jīng)提到過呢堰,當(dāng)消息被Looper通過Loop()方法取出并執(zhí)行的時候凡泣,會執(zhí)行recycleUnchecked()方法來重置消息中的數(shù)據(jù)皮假,具體代碼如下:
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;//將關(guān)聯(lián)handler對象置為null
callback = null;
data = null;
//省略部分代碼
}
在該方法中將target =null
,其中消息中的target
為當(dāng)前發(fā)送該消息的Handler對象骂维。也就說只有消息被取出執(zhí)行后惹资,整個引用鏈才會斷開,那么相應(yīng)的Handler與使用該Handler的Activity才會被系統(tǒng)回收航闺。
解決方法
通過上文Handler內(nèi)存泄漏的問題分析潦刃,導(dǎo)致這種情況的發(fā)生的原因是內(nèi)部類Handler擁有當(dāng)前Activity的引用侮措。那么解決只要解決這個問題萝毛,我們就能處理Handler內(nèi)存泄漏啦滑黔。
使用靜態(tài)內(nèi)部類+弱引用的方式
public class HandlerLeakageActivity extends BaseActivity {
public static final int UPDATE_UI = 1;
private MyHandler mHandler = new MyHandler(this);
//使用靜態(tài)內(nèi)部類
private static class MyHandler extends Handler {
private final WeakReference<HandlerLeakageActivity> mWeakReference;
MyHandler(HandlerLeakageActivity activity) {
mWeakReference = new WeakReference<HandlerLeakageActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerLeakageActivity activity = mWeakReference.get();
if (activity != null) {
activity.updateUI();
}
}
}
//更新ui
private void updateUI() {...}
}
為了保證不再持有當(dāng)前Activity的引用环揽,我們采用靜態(tài)內(nèi)部類的方式(靜態(tài)內(nèi)部類不會持有外部類引用
),同時為了讓Handler在處理消息的時候歉胶,能夠調(diào)用外部類Activity的方法,所以我們這里采用弱引用
的方式粥谬。
為什么要使用弱引用辫塌?
在Java中判斷一個對象到底是不是需要回收,都跟引用相關(guān)臼氨。在Java中引用分為了4類。
- 強(qiáng)引用:只要引用存在感耙,垃圾回收器永遠(yuǎn)不會回收Object obj = new Object();而這樣 obj對象對后面new Object的一個強(qiáng)引用持隧,只有當(dāng)obj這個引用被釋放之后,對象才會被釋放掉只酥。
- 軟引用:是用來描述,一些還有但并非必須的對象层皱,對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前草冈,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收瓮增。(SoftReference)
- 弱引用:也是用來描述非必須的對象,但是它的強(qiáng)度要比軟引用更弱一些绷跑。被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前,當(dāng)垃圾收集器工作時谬运,無論當(dāng)前內(nèi)存是否足夠垦藏,都回回收掉被弱引用關(guān)聯(lián)的對象。(WeakReference)
- 虛引用:也被稱為幽靈引用轰驳,它是最弱的一種關(guān)系弟灼。一個對象是否有引用的存在,完全不會對其生存時間構(gòu)成影響田绑,也無法通過一個虛引用來取得一個實例對象。
另創(chuàng)建一個類+弱引用的方式
如果你不想使用靜態(tài)內(nèi)部類+弱引用的方式俺陋,你也可以采用新建一個Handler類文件+弱引用的方式昙篙。這兩種代碼基本差不多,這里就不過多進(jìn)行介紹了苔可。
當(dāng)外部類生命周期結(jié)束時焚辅,清空消息
如果你不想采用上述的兩種方式苟鸯,還有一種方法就是在當(dāng)前Activity被finish掉的時候棚点,移除掉整個消息隊列中的所有消息。這樣就能保證Activity與Handler沒有直接的引用關(guān)系啦瘫析。
關(guān)于消息的刪除主要有三種方法,大家可以根據(jù)自己的項目需求來選擇相應(yīng)的方法咸包。具體如下所示:
(關(guān)于消息的刪除杖虾,如果有同學(xué)不是很熟悉,請參看《Android Handler機(jī)制之Message及Message回收機(jī)制》
void removeMessages(Handler h, int what, Object object)
void removeMessages(Handler h, Runnable r, Object object)
void removeCallbacksAndMessages(Handler h, Object object)
void removeCallbacksAndMessages(Object token)
結(jié)合Activity的生命周期坟比,具體代碼如下所示:
@Override
protected void onDestroy() {
super.onDestroy();
//這里token傳null,會移除消息隊列中所有當(dāng)前Handler發(fā)送且未被執(zhí)行的消息
mHandler.removeCallbacksAndMessages(null);
這里我使用removeCallbacksAndMessages(Object token) 方法來清空消息嚷往,需要注意的是如果token=null,該方法會移除消息隊列中所有當(dāng)前Handler發(fā)送且未被執(zhí)行的消息
。
總結(jié)
- Handler在使用時,如果直接采用內(nèi)部類茄茁,有可能會導(dǎo)致內(nèi)存泄漏。
- Handler內(nèi)存泄漏的主要原因是付燥,內(nèi)部類Handler擁有外部類Activity的引用愈犹,且不能保證消息的發(fā)送是否有較長延時。
- 解決Handler內(nèi)存泄漏的主要方法有漩怎,采用靜態(tài)內(nèi)部類+弱引用,當(dāng)外部類生命周期結(jié)束時饭玲,清空消息等叁执。