無用的內(nèi)存(沒有使用的對象)仍然被其他對象持有引用导饲,造成該對象無法被系統(tǒng)回收讯嫂,以致該對象在堆中所占用的內(nèi)存單元無法被釋放而造成內(nèi)存空間浪費(fèi)浇垦,這中情況就是內(nèi)存泄露炕置。
在開發(fā)的過程中,我們的一些編程習(xí)慣有可能會導(dǎo)致app內(nèi)存溢出情況溜族,下面舉簡單的幾個(gè)例子說明:
1讹俊、單利模式:
單利模式在開發(fā)中我們經(jīng)常使用垦沉,如果使用不當(dāng)就會造成內(nèi)存泄漏煌抒,單利模式都是靜態(tài)的,它的生命周期一般都會很長厕倍,如果一個(gè)對象已經(jīng)沒有用處了寡壮,但是單例還持有它的引用,那么在整個(gè)應(yīng)用程序的生命周期它都不能正常被回收讹弯,從而導(dǎo)致內(nèi)存泄露况既。說了這么多,上代碼:
public class Books {
public static Books instance;
private Context context;
private Books(Context context){
this.context = context;`
}
public static Books getInstance(Context context){
if (instance==null){
instance =new Books(context);
}
return instance;
}
}
在上面的代碼片段中,在調(diào)用getInstance(context)方法時(shí)组民,傳入context棒仍,activity,service上下文都會造成內(nèi)存泄漏臭胜。
以activity為例莫其,當(dāng)我們啟動(dòng)一個(gè)activity 在其中調(diào)用getInstance(context)傳入this獲取Books單例,這樣Books類的單例instance就持有了Activity的引用耸三,當(dāng)退出activity時(shí)乱陡,activity便銷毀,然而Books單利為靜態(tài)單利仪壮,在應(yīng)用程序的整個(gè)生命周期中存在 會繼續(xù)持有這個(gè)activity的引用憨颠,導(dǎo)致這個(gè)activity對象無法被回收釋放,這就造成了內(nèi)存泄露积锅。
為了避免這樣單例導(dǎo)致內(nèi)存泄露爽彤,我們可以將context參數(shù)改為全局的上下文:
private Books(Context context){
this.context = context.getApplication();
}
全局的上下文Application Context就是應(yīng)用程序的上下文养盗,和單例的生命周期一樣長,這樣就避免了內(nèi)存泄漏适篙。
單例模式對應(yīng)應(yīng)用程序的生命周期爪瓜,所以我們在構(gòu)造單例的時(shí)候盡量避免使用activity的上下文,而是使用Application的上下文匙瘪。
2铆铆、靜態(tài)變量:
靜態(tài)變量存儲在方法區(qū),它的生命周期從類加載開始丹喻,到整個(gè)進(jìn)程結(jié)束薄货。一旦靜態(tài)變量初始化后,它所持有的引用只有等到進(jìn)程結(jié)束才會釋放碍论。
在Android開發(fā)中谅猾,靜態(tài)持有很多時(shí)候都有可能因?yàn)槠涫褂玫纳芷诓灰恢露鴮?dǎo)致內(nèi)存泄露,所以我們在新建靜態(tài)持有的變量的時(shí)候需要多考慮一下各個(gè)成員之間的引用關(guān)系鳍悠,并且盡量少地使用靜態(tài)持有的變量税娜,以避免發(fā)生內(nèi)存泄露。當(dāng)然藏研,我們也可以在適當(dāng)?shù)臅r(shí)候講靜態(tài)量重置為null敬矩,使其不再持有引用,這樣也可以避免內(nèi)存泄露蠢挡。
3弧岳、動(dòng)畫及吐司:
動(dòng)畫在開發(fā)中是一個(gè)不能少的環(huán)節(jié),也是一個(gè)很耗時(shí)的操作业踏,然而在開發(fā)中很多時(shí)候都忘記對動(dòng)畫進(jìn)行關(guān)閉禽炬,即使當(dāng)前界面已經(jīng)銷毀,我們看不見動(dòng)畫的存在勤家,只要沒有調(diào)用cancel() 便依然執(zhí)行腹尖,從而導(dǎo)致activity或者fragment中的內(nèi)存無法釋放,資源無法得到及時(shí)的回收伐脖。
@Override
protected void onDestroy() {
super.onDestroy();
if (objectAnimator!=null){
objectAnimator.cancel();
objectAnimator = null;
}
if(toast!=null){
toast.cancel();
}
}
4热幔、非靜態(tài)內(nèi)部類:
非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認(rèn)就會持有外部類的引用,當(dāng)非靜態(tài)內(nèi)部類對象的生命周期比外部類對象的生命周期長時(shí)晓殊,就會導(dǎo)致內(nèi)存泄露断凶。
非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄露在Android開發(fā)中有一種典型的場景就是使用Handler,很多開發(fā)者在使用Handler是這樣寫的:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
// todo
}
}
};
}
在上面的代碼片段中雖然沒有內(nèi)部類的寫法巫俺,也不會造成內(nèi)存认烁。
熟悉Handler消息機(jī)制的都知道,Handler會作為成員變量保存在發(fā)送的消息msg中,即msg持有Handler的引用却嗡,而Handler是Activity的非靜態(tài)內(nèi)部類實(shí)例舶沛,即Handler持有Activity的引用,那么我們就可以理解為msg間接持有Activity的引用窗价。msg被發(fā)送后先放到消息隊(duì)列MessageQueue中如庭,然后等待Looper的輪詢處理(MessageQueue和Looper都是與線程相關(guān)聯(lián)的,MessageQueue是Looper引用的成員變量撼港,而Looper是保存在ThreadLocal中的)坪它。那么當(dāng)Activity退出后,msg可能仍然存在于消息對列MessageQueue中未處理或者正在處理帝牡,那么這樣就會導(dǎo)致Activity無法被回收往毡,以致發(fā)生Activity的內(nèi)存泄露。
通常在Android開發(fā)中如果要使用內(nèi)部類靶溜,但又要規(guī)避內(nèi)存泄露开瞭,一般都會采用靜態(tài)內(nèi)部類+弱引用的方式。
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// todo
}
}
}
}
}
mHandler通過弱引用的方式持有Activity罩息,當(dāng)GC執(zhí)行垃圾回收時(shí)嗤详,遇到Activity就會回收并釋放所占據(jù)的內(nèi)存單元。這樣就不會發(fā)生內(nèi)存泄露了瓷炮。
上面的做法確實(shí)避免了Activity導(dǎo)致的內(nèi)存泄露葱色,發(fā)送的msg不再已經(jīng)沒有持有Activity的引用了,但是msg還是有可能存在消息隊(duì)列MessageQueue中崭别,所以更好的是在Activity銷毀時(shí)就將mHandler的回調(diào)和發(fā)送的消息給移除掉冬筒。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
5恐锣、未取消注冊
比如我們在activity中注冊了廣播茅主,在銷毀activity時(shí)沒有對廣播進(jìn)行取消,那么這個(gè)廣播會一直存在系統(tǒng)中土榴,同上面所說的非靜態(tài)內(nèi)部類一樣持有Activity引用诀姚,導(dǎo)致內(nèi)存泄露。因此注冊廣播后在Activity銷毀后一定要取消注冊玷禽。
6赫段、集合和資源未關(guān)閉和置空
這個(gè)比較好理解,如果一個(gè)對象放入到ArrayList矢赁、HashMap等集合中糯笙,這個(gè)集合就會持有該對象的引用。當(dāng)我們不再需要這個(gè)對象時(shí)撩银,也并沒有將它從集合中移除给涕,這樣只要集合還在使用(而此對象已經(jīng)無用了),這個(gè)對象就造成了內(nèi)存泄露。并且如果集合被靜態(tài)引用的話够庙,集合里面那些沒有用的對象更會造成內(nèi)存泄露了恭应。所以在使用集合時(shí)要及時(shí)將不用的對象從集合remove,或者clear集合耘眨,以避免內(nèi)存泄漏昼榛。
在使用IO、File流或者Sqlite剔难、Cursor等資源時(shí)要及時(shí)關(guān)閉胆屿。這些資源在進(jìn)行讀寫操作時(shí)通常都使用了緩沖,如果及時(shí)不關(guān)閉偶宫,這些緩沖對象就會一直被占用而得不到釋放莺掠,以致發(fā)生內(nèi)存泄露。因此我們在不需要使用它們的時(shí)候就及時(shí)關(guān)閉读宙,以便緩沖能及時(shí)得到釋放彻秆,從而避免內(nèi)存泄露。
總結(jié)
開發(fā)過程中內(nèi)存管理是一個(gè)重點(diǎn)结闸,也培養(yǎng)程序員對系統(tǒng)資源的管理唇兑,內(nèi)存泄漏最終會導(dǎo)致內(nèi)存溢出 OOM異常。
構(gòu)建單例模式的時(shí)候盡量不要用activity做引入桦锄。
使用到靜態(tài)變量的時(shí)候扎附,在內(nèi)存回收的時(shí)候?qū)⑵潇o態(tài)變量置空。
使用動(dòng)畫和吐司的時(shí)候结耀,雖然給我們的感覺是已經(jīng)結(jié)束了留夜,只要沒有調(diào)用cancel() 內(nèi)存都會一直被占用,得不到釋放图甜。
使用靜態(tài)內(nèi)部類+軟引用代替非靜態(tài)內(nèi)部類碍粥。
及時(shí)取消廣播或者觀察者注冊。