單例導致內存泄漏
首先來看一下一種單例的寫法:多種單例寫法參考
public class Utils{
private static Utils mInstance;
private Context mContext;
private Utils(Context context){
this.mContext = context;
}
public static Utils getInstance(Context context){
if(mInstance == null){
Util = new Utils(context);
}
return mInstance;
}
}
以Activity為例扎瓶,當我們啟動一個Activity窃这,并調用getInstance(Context context)Utils,傳入Activity.this作為context,這樣Utils類的單例mInstance就持有了Activity的引用,當我們退出Activity時,該Activity就沒有用了左胞,但是因為mIntance作為靜態(tài)單例(在應用程序的整個生命周期中存在)會繼續(xù)持有這個Activity的引用瑰抵,導致這個Activity對象無法被回收釋放你雌,這就造成了內存泄露。
為了避免這樣單例導致內存泄露二汛,我們可以將context參數改為全局的上下文:
java private Utils(Context context){ this.mContext=context.getApplicationContext(); }
全局的上下文Application Context就是應用程序的上下文婿崭,和單例的生命周期一樣長,這樣就避免了內存泄漏肴颊。單例模式對應應用程序的生命周期氓栈,所以我們在構造單例的時候盡量避免使用Activity的上下文,而是使用Application的上下文婿着。
非靜態(tài)內部類導致內存泄漏
我們都知道內部類會持有外部類引用授瘦。 原因通過編譯查看.class文件可知,編譯器會為內部類構造傳入外部類實例.所以非靜態(tài)內部類會持有外部類引用竟宋。
這樣在特定情況下就會產生一個問題提完。就是如果內部類的生命周期比外部類的生命周期長,那么在外部類無用時丘侠,內部類依然持有外部類引用徒欣,導致外部類無法釋放,從而導致內存泄漏蜗字。
Android 中典型的使用場景就是Handler.通常我們的寫法是這樣:
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start(){
Messagemsg=Message.obtain();
msg.what=1;
mHandler.sendMessage(msg);
}
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg){
if(msg.what==1){
//做相應邏輯
}
}
};
}
熟悉Handler消息機制的都知道打肝,mHandler會作為成員變量保存在發(fā)送的消息msg中,即msg持有mHandler的引用挪捕,而mHandler是Activity的非靜態(tài)內部類實例粗梭,即mHandler持有Activity的引用,那么我們就可以理解為msg間接持有Activity的引用级零。msg被發(fā)送后先放到消息隊列MessageQueue中断医,然后等待Looper的輪詢處理: MessageQueue和Looper都是與線程相關聯(lián)的,MessageQueue是Looper引用的成員變量妄讯,而Looper是保存在ThreadLocal中的孩锡。那么當Activity退出后,msg可能仍然存在于消息對列MessageQueue中未處理或者正在處理亥贸,那么這樣就會導致Activity無法被回收躬窜,以致發(fā)生Activity的內存泄露。
通常在Android開發(fā)中如果要使用內部類炕置,但又要規(guī)避內存泄露荣挨,一般都會采用靜態(tài)內部類+弱引用的方式男韧。
在Activity中創(chuàng)建靜態(tài)內部類:
public class MainActivity extends AppcompatActivity{
private static class MyHandler extends Handler{
? ? ? ? private WeakReference<MainActivity> activityWeakReference;
? ? ? ? public MyHandler(MainActivity activity){
? ? ? ? ? ? activityWeakReference = new WeakReference<>(activity);
? ? ? ? }
? ? ? ? @Override
? ? ? ? public void handleMessage(@NonNull Message msg) {
? ? ? ? ? ? if(activityWeakReference.get() != null){
? ? ? ? ? ? ? ? if(msg.what == 1 ){
? ? ? ? ? ? ? ? ? ? //do something
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
上面的做法確實避免了Activity導致的內存泄露,發(fā)送的msg不再已經沒有持有Activity的引用了默垄,但是msg還是有可能存在消息隊列MessageQueue中此虑,所以更好的是在Activity銷毀時就將mHandler的回調和發(fā)送的消息給移除掉。
@Override
protected void onDestroy(){
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非靜態(tài)內部類造成內存泄露還有一種情況就是使用Thread或者AsyncTask口锭。 同樣類似于handler,生命周期的不一致導致內存泄漏朦前。
總之,在使用內部類時鹃操,要時刻注意生命周期是否一致韭寸,如果不一致就要考慮是否會發(fā)生內存泄漏的問題。
未取消注冊或回調導致內存泄露
在注冊觀察則模式的時候荆隘,如果不及時取消也會造成內存泄露恩伺。比如使用Retrofit+RxJava注冊網絡請求的觀察者回調,同樣作為匿名內部類持有外部引用椰拒,所以需要記得在不用或者銷毀的時候取消注冊`
靜態(tài)變量導致內存泄露
靜態(tài)變量存儲在方法區(qū)晶渠,它的生命周期從類加載開始,到整個進程結束燃观。一旦靜態(tài)變量初始化后褒脯,它所持有的引用只有等到進程結束才會釋放。
盡量少地使用靜態(tài)持有的變量仪壮,以避免發(fā)生內存泄露憨颠。當然,我們也可以在適當的時候講靜態(tài)量重置為null积锅,使其不再持有引用,這樣也可以避免內存泄露养盗。
不要在類初始時初始化靜態(tài)成員缚陷。可以考慮lazy初始化往核。
架構設計上要思考是否真的有必要這樣做箫爷,盡量避免。如果架構需要這么設計聂儒,那么此對象的生命周期你有責任管理起來虎锚。
集合中的對象未清理造成內存泄露
這個比較好理解,如果一個對象放入到ArrayList衩婚、HashMap等集合中窜护,這個集合就會持有該對象的引用。當我們不再需要這個對象時非春,也并沒有將它從集合中移除柱徙,這樣只要集合還在使用(而此對象已經無用了)缓屠,這個對象就造成了內存泄露。并且如果集合被靜態(tài)引用的話护侮,集合里面那些沒有用的對象更會造成內存泄露了敌完。所以在使用集合時要及時將不用的對象從集合remove,或者clear集合羊初,以避免內存泄漏
屬性動畫造成內存泄露
動畫同樣是一個耗時任務滨溉,比如在Activity中啟動了屬性動畫(ObjectAnimator),但是在銷毀的時候长赞,沒有調用cancle方法业踏,雖然我們看不到動畫了,但是這個動畫依然會不斷地播放下去涧卵,動畫引用所在的控件勤家,所在的控件引用Activity,這就造成Activity無法正常釋放柳恐。因此同樣要在Activity銷毀的時候cancel掉屬性動畫伐脖,避免發(fā)生內存泄漏。
資源未關閉或釋放導致內存泄露
在使用IO乐设、File流或者Sqlite讼庇、Cursor等資源時要及時關閉。這些資源在進行讀寫操作時通常都使用了緩沖近尚,如果及時不關閉蠕啄,這些緩沖對象就會一直被占用而得不到釋放,以致發(fā)生內存泄露戈锻。因此我們在不需要使用它們的時候就及時關閉歼跟,以便緩沖能及時得到釋放,從而避免內存泄露
Bitmap 沒調用 recycle()方法格遭,對于 Bitmap 對象在不使用時,我們應該先調用 recycle() 釋放內存哈街,然后才它設置為 null. 因為加載 Bitmap 對象的內存空間,一部分是 java 的拒迅,一部分 C的(因為 Bitmap 分配的底層是通過 JNI 調用的 )骚秦。 而這個 recyle() 就是針對 C 部分的內存釋放。
總結
內存泄露在Android內存優(yōu)化是一個比較重要的一個方面璧微,很多時候程序中發(fā)生了內存泄露我們不一定就能注意到作箍,所有在編碼的過程要養(yǎng)成良好的習慣。
總結下來只要做到以下這幾點就能避免大多數情況的內存泄漏:
構造單例的時候盡量別用Activity的引用前硫;
靜態(tài)引用時注意應用對象的置空或者少用靜態(tài)引用胞得;
使用靜態(tài)內部類+軟引用代替非靜態(tài)內部類;
及時取消廣播或者觀察者注冊开瞭;
耗時任務懒震、屬性動畫在Activity銷毀時記得cancel罩息;
文件流、Cursor等資源及時關閉个扰;
Activity銷毀時WebView的移除和銷毀(android5.1上的bug,當Activity銷毀時瓷炮,webView并沒有被真正移除掉,還持有Activity的引用)递宅。