個(gè)人主頁:http://shiyiliang.cn
什么是內(nèi)存泄露
通俗的講:不在使用的對(duì)象第美,其內(nèi)存不能回收,導(dǎo)致能使用的內(nèi)存越來越少,這就是內(nèi)存泄露
內(nèi)存泄露的原因
在Android開發(fā)中院刁,最主要的原因就是生命周期長的對(duì)象,持有生命周期短對(duì)象的強(qiáng)引用
內(nèi)存泄露的例子
1. Handler內(nèi)存泄漏
Handler 的使用造成的內(nèi)存泄漏問題應(yīng)該說是最為常見了碟婆,很多時(shí)候我們?yōu)榱吮苊?ANR 而不在主線程進(jìn)行耗時(shí)操作,在處理網(wǎng)絡(luò)任務(wù)或者封裝一些請(qǐng)求回調(diào)等api都借助Handler來處理惕稻,但 Handler 不是萬能的竖共,對(duì)于 Handler 的使用代碼編寫一不規(guī)范即有可能造成內(nèi)存泄漏。另外俺祠,我們知道 Handler公给、Message 和 MessageQueue 都是相互關(guān)聯(lián)在一起的借帘,萬一 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對(duì)象將被線程 MessageQueue 一直持有淌铐。
由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的肺然。因此這種實(shí)現(xiàn)方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導(dǎo)致無法正確釋放腿准。
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 1000 * 60 * 10);
finish();
}
}
該 SampleActivity 中聲明了一個(gè)延遲10分鐘執(zhí)行的消息 Message际起,mLeakyHandler 將其 push 進(jìn)了消息隊(duì)列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時(shí)吐葱,延遲執(zhí)行任務(wù)的 Message 還會(huì)繼續(xù)存在于主線程中街望,它持有該 Activity 的 Handler 引用,所以此時(shí) finish() 掉的 Activity 就不會(huì)被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類弟跑,它會(huì)持有外部類的引用灾前,在這里就是指 SampleActivity)
正確的寫法是:
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
2. 單例模式導(dǎo)致的泄漏
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
這是一個(gè)普通的單例模式,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候窖认,由于需要傳入一個(gè)Context豫柬,所以這個(gè)Context的生命周期的長短至關(guān)重要:
1告希、如果此時(shí)傳入的是 Application 的 Context扑浸,因?yàn)?Application 的生命周期就是整個(gè)應(yīng)用的生命周期,所以這將沒有任何問題燕偶。
2喝噪、如果此時(shí)傳入的是 Activity 的 Context,當(dāng)這個(gè) Context 所對(duì)應(yīng)的 Activity 退出時(shí)指么,由于該 Context 的引用被單例對(duì)象所持有酝惧,其生命周期等于整個(gè)應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收伯诬,這就造成泄漏了晚唇。
正確的寫法是:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
...
context = getApplicationContext();
...
/**
* 獲取全局的context
* @return 返回全局context對(duì)象
*/
public static Context getContext(){
return context;
}
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager() {
this.context = MyApplication.getContext();// 使用Application 的context
}
public static AppManager getInstance() {
if (instance == null) {
instance = new AppManager();
}
return instance;
}
}
3. 匿名內(nèi)部類/非靜態(tài)內(nèi)部類和異步線程
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏
public class MainActivity extends AppCompatActivity {
private static Test mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new Test();
}
}
class Test {
//// TODO: 2017-03-22
}
}
這樣就在Activity內(nèi)部創(chuàng)建了一個(gè)非靜態(tài)內(nèi)部類的單例,每次啟動(dòng)Activity時(shí)都會(huì)使用該單例的數(shù)據(jù)盗似,這樣雖然避免了資源的重復(fù)創(chuàng)建哩陕,不過這種寫法卻會(huì)造成內(nèi)存泄漏,因?yàn)榉庆o態(tài)內(nèi)部類默認(rèn)會(huì)持有外部類的引用赫舒,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例悍及,該實(shí)例的生命周期和應(yīng)用的一樣長,這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用接癌,導(dǎo)致Activity的內(nèi)存資源不能正承母希回收。正確的做法為:
將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個(gè)單例缺猛,如果需要使用Context缨叫,請(qǐng)按照上面推薦的使用Application 的 Context椭符。當(dāng)然,Application 的 context 不是萬能的耻姥,所以也不能隨便亂用艰山,對(duì)于有些地方則必須使用 Activity 的 Context,對(duì)于Application咏闪,Service曙搬,Activity三者的Context的應(yīng)用場景如下:
其中: NO1表示 Application 和 Service 可以啟動(dòng)一個(gè) Activity,不過需要?jiǎng)?chuàng)建一個(gè)新的 task 任務(wù)隊(duì)列鸽嫂。而對(duì)于 Dialog 而言纵装,只有在 Activity 中才能創(chuàng)建
匿名內(nèi)部類
public class MainActivity extends Activity {
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() {
@Override
public void run() {
}
};
}
ref1沒什么特別的。
但ref2這個(gè)匿名類的實(shí)現(xiàn)對(duì)象里面多了一個(gè)引用:
this$0這個(gè)引用指向MainActivity.this据某,也就是說當(dāng)前的MainActivity實(shí)例會(huì)被ref2持有橡娄,如果將這個(gè)引用再傳入一個(gè)異步線程,此線程和此Acitivity生命周期不一致的時(shí)候癣籽,就造成了Activity的泄露挽唉。
4.盡量避免使用 static 成員變量
如果成員變量被聲明為 static,那我們都知道其生命周期將與整個(gè)app進(jìn)程生命周期一樣筷狼。這會(huì)導(dǎo)致一系列問題瓶籽,如果你的app進(jìn)程設(shè)計(jì)上是長駐內(nèi)存的,那即使app切到后臺(tái)埂材,這部分內(nèi)存也不會(huì)被釋放塑顺。按照現(xiàn)在手機(jī)app內(nèi)存管理機(jī)制,占內(nèi)存較大的后臺(tái)進(jìn)程將優(yōu)先回收俏险,如果此app做過進(jìn)程互保毖暇埽活,那會(huì)造成app在后臺(tái)頻繁重啟竖独。當(dāng)手機(jī)安裝了你參與開發(fā)的app以后一夜時(shí)間手機(jī)被消耗空了電量裤唠、流量,你的app不得不被用戶卸載或者靜默莹痢。這里修復(fù)的方法是:不要在類初始時(shí)初始化靜態(tài)成員种蘸。可以考慮lazy初始化格二。架構(gòu)設(shè)計(jì)上要思考是否真的有必要這樣做劈彪,盡量避免。如果架構(gòu)需要這么設(shè)計(jì)顶猜,那么此對(duì)象的生命周期你有責(zé)任管理起來沧奴。
5.避免 override finalize()
1、finalize 方法被執(zhí)行的時(shí)間不確定长窄,不能依賴與它來釋放緊缺的資源滔吠。時(shí)間不確定的原因是:
- 虛擬機(jī)調(diào)用GC的時(shí)間不確定
- Finalize daemon線程被調(diào)度到的時(shí)間不確定
2纲菌、finalize 方法只會(huì)被執(zhí)行一次,即使對(duì)象被復(fù)活疮绷,如果已經(jīng)執(zhí)行過了 finalize 方法翰舌,
再次被 GC 時(shí)也不會(huì)再執(zhí)行了,原因是:含有 finalize 方法的 object 是在 new 的時(shí)候由虛擬機(jī)生成了一個(gè) finalize reference 來引用到該Object的冬骚,而在 finalize 方法執(zhí)行的時(shí)候椅贱,該 object 所對(duì)應(yīng)的 finalize Reference 會(huì)被釋放掉,即使在這個(gè)時(shí)候把該 object 復(fù)活(即用強(qiáng)引用引用住該 object ,再第二次被 GC 的時(shí)候由于沒有了 finalize reference 與之對(duì)應(yīng)只冻,所以 finalize 方法
不會(huì)再執(zhí)行庇麦。
3、含有Finalize方法的object需要至少經(jīng)過兩輪GC才有可能被釋放喜德。
6.資源未關(guān)閉造成的內(nèi)存泄漏
對(duì)于使用了BraodcastReceiver
山橄,ContentObserver
,File
舍悯,游標(biāo) Cursor
航棱,Stream
,Bitmap
等資源的使用萌衬,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷饮醇,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏奄薇。
7. 一些不良代碼造成的內(nèi)存壓力
有些代碼并不造成內(nèi)存泄露驳阎,但是它們抗愁,或是對(duì)沒使用的內(nèi)存沒進(jìn)行有效及時(shí)的釋放馁蒂,或是沒有有效的利用已有的對(duì)象而是頻繁的申請(qǐng)新內(nèi)存。
比如:
-
Bitmap
沒調(diào)用recycle()
方法蜘腌,對(duì)于 Bitmap 對(duì)象在不使用時(shí),我們應(yīng)該先調(diào)用 recycle() 釋放內(nèi)存沫屡,然后才它設(shè)置為 null.為加載 Bitmap 對(duì)象的內(nèi)存空間,一部分是 java 的撮珠,一部分 C 的(因?yàn)?Bitmap 分配的底層是通過 JNI 調(diào)用的 )沮脖。 而這個(gè) recyle() 就是針對(duì) C 部分的內(nèi)存釋放。 - 構(gòu)造 Adapter 時(shí)芯急,沒有使用緩存的 convertView ,每次都在創(chuàng)建新的 converView勺届。這里推薦使用 ViewHolder。