首先我們關(guān)注一個(gè)內(nèi)存泄露的場(chǎng)景,相信大家都知道在Android中非靜態(tài)的內(nèi)部類或匿名內(nèi)部類都很有可能造成Context泄露。主要原因就是在某些情況下,Context的生命周期已經(jīng)走完,但是這些類的生命還未到盡頭,而他們又持有Context的引用,導(dǎo)致GC時(shí)無(wú)法回收該回收的內(nèi)存空間從而導(dǎo)致類存泄露。
上面這段話應(yīng)該不難理解,下面就用一些簡(jiǎn)單的例子說(shuō)明這個(gè)問(wèn)題。
一、普通內(nèi)部類或匿名類造成內(nèi)存泄露
public class SecondActivity extends Activity {
private static final String TAG = "WeakReferenceTest";
private ImageView ivTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_2);
ivTest = (ImageView) findViewById(R.id.image);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
ivTest.setImageBitmap(bitmap);
// 匿名內(nèi)部類會(huì)持有外部類的引用
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000 * 100);
Log.i(TAG, "This log is from SecondActivity!");
}catch (InterruptedException e){
}
}
});
Button button = (Button) findViewById(R.id.btn_2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread.start();
finish();
}
});
}
}
上面的代碼中,有一個(gè)匿名的Runnable類讓其所在線程sleep 100秒,在這個(gè)Activity中有一個(gè)ImageView并為其設(shè)置了一張圖片。我們連續(xù)的進(jìn)行打開(kāi)->關(guān)閉Activity這項(xiàng)操作,發(fā)現(xiàn)越到后面卡頓越嚴(yán)重。看下面兩張圖,這是某兩個(gè)時(shí)刻的內(nèi)存使用情況(一前一后):
可以發(fā)現(xiàn),在連續(xù)進(jìn)行上述同一操作的時(shí)候,程序內(nèi)存增大了很多!再看看Dalvikvm(4.4以上系統(tǒng)可能是ART)打印的日志:
GC操作顯示當(dāng)前活動(dòng)對(duì)象占用的內(nèi)存越來(lái)越多,最后直至程序崩潰!這里可以肯定,我們上面寫(xiě)的代碼確實(shí)造成了內(nèi)存泄露。就是這個(gè)匿名內(nèi)部類,它持有外部Activity的引用,當(dāng)我們點(diǎn)擊Button開(kāi)啟了線程的同時(shí)結(jié)束了當(dāng)前Activvity,此時(shí)GC正要回收此Activity占用的內(nèi)存空間,發(fā)現(xiàn)還有對(duì)象持有它的引用所以無(wú)法進(jìn)行內(nèi)存回收;當(dāng)我們多次進(jìn)行打開(kāi)->關(guān)閉Activity操作的時(shí)候,就導(dǎo)致了內(nèi)存泄露,最后程序也崩了。
問(wèn)題來(lái)了,如何避免。其實(shí)這里相信大家都知道,將其聲明為靜態(tài)的就行,如下:
private static class MyRunnable implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000 * 100);
Log.i(TAG, "This log is from SecondActivity!");
}catch (InterruptedException e){
}
}
}
// 使用
final Thread thread = new Thread(new MyRunnable());
Button button = (Button) findViewById(R.id.btn_2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread.start();
finish();
}
});
修改后Dalvikvm打印日志如下圖:
程序的內(nèi)存不在一直飆升,而是穩(wěn)定在一個(gè)范圍內(nèi)。這里的主要原因就在于內(nèi)部類和靜態(tài)內(nèi)部類的區(qū)別:
- 靜態(tài)內(nèi)部類不同于普通內(nèi)部類,它不會(huì)持有外部類的引用;而普通內(nèi)部類或匿名類則相反
- 普通內(nèi)部類或匿名類因?yàn)槌钟型獠款惖囊茫钥梢栽L問(wèn)外部類的資源屬性成員變量等;靜態(tài)內(nèi)部類不行
- 因?yàn)槠胀▋?nèi)部類或匿名類依賴外部類,所以必須先創(chuàng)建外部類,再創(chuàng)建普通內(nèi)部類或匿名類;而靜態(tài)內(nèi)部類隨時(shí)都可以在其他外部類中隨時(shí)創(chuàng)建
所以上面的代碼中,由于使用的是靜態(tài)內(nèi)部類,當(dāng)外部類Activity需要被GC回收內(nèi)存時(shí),Activity的引用數(shù)為0,所以能被正常回收。
二、Handler造成Context泄露
先看代碼:
public class SecondActivity extends Activity {
private static final String TAG = "WeakReferenceTest";
private ImageView ivTest;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(TAG, msg.obj.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_2);
ivTest = (ImageView) findViewById(R.id.image);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
ivTest.setImageBitmap(bitmap);
Message msg = mHandler.obtainMessage();
msg.obj = "This is a message!";
mHandler.sendMessageDelayed(msg, 1000 * 10);
finish();
}
}
當(dāng)我們寫(xiě)下這段代碼的時(shí)候,IDE會(huì)提示一個(gè)警告如下:
提示Handler類應(yīng)該是靜態(tài)的,否則可能會(huì)發(fā)生泄露。
其實(shí)這里發(fā)生泄露和上面說(shuō)的普通/匿名內(nèi)部類是類似的。根據(jù)Android的消息機(jī)制,每個(gè)Message對(duì)象都保存著處理其Handler的引用,而在Activity中實(shí)例化一個(gè)非靜態(tài)的Handler類,此類又會(huì)持有Activity的引用;當(dāng)消息沒(méi)處理完或者需要延遲處理就結(jié)束了當(dāng)前Activity,此時(shí)Activity引用數(shù)不為0,就會(huì)造成Context泄露。問(wèn)題就是這樣,對(duì)策是不是也同樣出來(lái)了,將Handler類聲明為靜態(tài)內(nèi)部類,代碼如下:
static class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
警告確實(shí)沒(méi)有了,但是問(wèn)題又來(lái)了。一般情況下,我們使用Handler就是為了配合Thread進(jìn)行耗時(shí)操作然后更新UI,但是這里的Handler類是靜態(tài)內(nèi)部類,不能訪問(wèn)外部類的成員變量,怎么破!接下來(lái),就該WeakReference派上用場(chǎng)了!
Google對(duì)WeakReference介紹不多,下面是官方文檔中的介紹(以下”入隊(duì)”指將該引用加入引用隊(duì)列(Reference Queen)):
弱引用(WeakReference)是三種引用中間的一種。一旦GC判定一個(gè)對(duì)象時(shí)弱引用可到達(dá),會(huì)發(fā)生以下情況:
- 有一組引用ref,這組引用包含以下元素:
指向該對(duì)象的所有弱引用
所有弱引用指向的軟引用/強(qiáng)引用可到達(dá)對(duì)象
- 所有在這組ref中的引用會(huì)被自動(dòng)清除
- 所以之前被ref引用的對(duì)象都可以被析構(gòu)(回收)
- 在未來(lái)的某個(gè)時(shí)候,ref中所有的引用會(huì)根據(jù)自己的相應(yīng)的引用隊(duì)列(如果有)入隊(duì)
弱引用在Map中很有用,如果一個(gè)弱引用沒(méi)有被外部任何地方引用,它就會(huì)自動(dòng)被移除。SoftReference和WeakReference的區(qū)別就在于對(duì)象被回收、引用入隊(duì)的時(shí)間點(diǎn)不同:- 如果一個(gè)對(duì)象是軟引用可到達(dá),那么這個(gè)對(duì)象會(huì)盡可能晚的被回收,這個(gè)引用同樣會(huì)盡可能晚的入隊(duì)。比如當(dāng)VM內(nèi)存不足時(shí)這種情形。
- 如果一個(gè)對(duì)象被判定是弱引用可到達(dá),那么這個(gè)對(duì)象會(huì)盡快被回收,這個(gè)引用也會(huì)盡快入隊(duì)。
- 弱引用不能阻擋GC對(duì)對(duì)象進(jìn)行回收,由GC決定引用的對(duì)象何時(shí)回收并且將對(duì)象從內(nèi)存移除
- 使用get()方法獲取其引用的對(duì)象
介紹完了弱引用,看看我們修改后的代碼:
static class MyHandler extends Handler{
private final WeakReference<Context> mWeakReference;
public MyHandler(Context context){
mWeakReference = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Activity mActivity;
if ((mActivity = (Activity)mWeakReference.get()) != null){
// Activity operation
// ...
}
}
}
這樣我們就可以在靜態(tài)內(nèi)部類中使用操作Activity。
除了弱引用(WeakReference)和上面稍微提到的軟引用(SoftReference),還有強(qiáng)引用(StrongReference
)和虛引用 (PhantomReference)。
軟引用(SoftReference)
一旦GC判定一個(gè)對(duì)象時(shí)弱引用可到達(dá),會(huì)發(fā)生以下情況:
- 有一組引用ref,這組引用包含以下元素:
指向該對(duì)象的所有弱引用
所有軟引用指向的強(qiáng)引用可到達(dá)的對(duì)象
- 所有在這組ref中的引用會(huì)被自動(dòng)清除
- 在同一時(shí)間或是未來(lái)的某一時(shí)間,ref中所有的引用會(huì)根據(jù)自己的相應(yīng)的引用隊(duì)列(如果有)入隊(duì)
- 系統(tǒng)會(huì)延遲清除軟引用指向的對(duì)象,該軟引用也會(huì)延遲入隊(duì),但是再系統(tǒng)拋出OutOfMemoryError異常的時(shí)候所有的軟引用可到達(dá)的對(duì)象會(huì)被回收。當(dāng)系統(tǒng)需要回收內(nèi)存來(lái)滿足分配,軟引用可到達(dá)的對(duì)象會(huì)才會(huì)被回收,軟引用入隊(duì)。簡(jiǎn)單來(lái)說(shuō)就是軟引用阻止GC回收其指向的對(duì)象的能力相對(duì)弱引用強(qiáng)。
軟引用上面說(shuō)到了當(dāng)內(nèi)存不足時(shí)才會(huì)回收這些軟引用指向的對(duì)象,所以挺適合做緩存用。但是Google可不推薦這么做,因?yàn)楹芏嘣蛳拗屏怂`活的處理緩存相關(guān)的事情。所以關(guān)于SoftReference官方文檔提到這樣一句:Most applications should use an android.util.LruCache instead of soft references. LruCache has an effective eviction policy and lets the user tune how much memory is allotted. 所以要做緩存還是得用LruCache。
強(qiáng)引用(StrongReference)
我們使用的最多的就是強(qiáng)引用,比如一句簡(jiǎn)單的賦值代碼:
Button button = new Button(this); // 創(chuàng)建一個(gè)Button對(duì)象,并將這個(gè)對(duì)象的引用存到button中。
虛引用 (PhantomReference)
虛引用是幾類引用中最弱的一種,當(dāng)一個(gè)對(duì)象被判定是虛引用可到達(dá)時(shí),該引用就會(huì)被加入到引用隊(duì)列(也就是當(dāng)一個(gè)對(duì)象被回收之后),但是它的指向不會(huì)被清除。虛引用適合在一個(gè)對(duì)象回收前做一些清理操作,因?yàn)樗萬(wàn)inalize()方法更靈活。
關(guān)于Java中的弱引用,這篇文章(譯文)關(guān)于WeakReference寫(xiě)的很好,推薦。
參考
[Android最佳性能實(shí)踐][1]
[http://developer.android.com/reference][2]
[1]:http://blog.csdn.net/guolin_blog/article/details/42238633/
[2]:http://developer.android.com/reference