內存泄漏總結


內存泄漏主要為activity泄漏有幾大情景:

1,內部類泄漏          內部類持有外部類,但外部類關閉時內部類依然被持有造成泄漏

2,靜態常量泄漏       靜態變量長期維持到大數據對象的引用,阻止垃圾回收

3,資源未關閉泄漏    資源性對象如Cursor、Stream、Socket,Bitmap

4,注冊反注冊泄漏    我們常常寫很多的Listener,未反注冊會導致觀察者列表里維持着對象的引用,阻止垃圾回收。

5,圖片太大

6,listview使用

最主要的最普遍的是activity泄漏  主要情景: (在 activity關閉時,檢查靜態變量是否需要賦值為null,檢查內部類是否還存在)

 

內部類泄漏常見情景:

1,匿名內部類

匿名內部類和非靜態內部類相同,會持有外部類對象,也就是activity,因此如果你在Activity里創建一個匿名內部類,則可能會發生內存泄漏,如果這個內部類在Activity銷毀后還一直存在,就會造成activity泄漏。

解決方案:將匿名內部類,改為靜態內部類。或者用靜態變量名引用匿名內部類,並在activity結束時將靜態內部類變量賦值null

2,非靜態內部類的靜態實例  

由於內部類默認持有外部類的引用,而靜態實例屬於類。所以,當外部類被銷毀時,內部類仍然持有外部類的引用,致使外部類無法被GC回收。因此造成內存泄露

 

解決方案:將內部類聲明為靜態內部類,或者在創建靜態內部類的時候使用靜態變量名引用,並在activity關閉時將靜態變量賦值為null

3,Handler臨時性內存泄露

Handler通過發送Message與主線程交互,Message發出之后是存儲在MessageQueue中的,有些Message也不是馬上就被處理的。

Runnable類屬於非靜態匿名類,同樣會引用外部類。
為了解決遇到的問題,我們要明確一點:靜態內部類不會持有對外部類的引用。所以,我們可以把handler類放在單獨的類文件中,或者使用靜態內部類便可以避免泄漏。
另外,如果想要在handler內部去調用所在的外部類Activity,那么可以在handler內部使用弱引用的方式指向所在Activity,這樣統一不會導致內存泄漏。
對於匿名類Runnable,同樣可以將其設置為靜態類。因為靜態的匿名類不會持有對外部類的引用。

解決方案:可以由上面的結論看出,產生泄漏的根源在於匿名類持有Activity的引用,因此可以自定義Handler和Runnable類並聲明成靜態的內部類,來解除和Activity的引用。或者在activity 結束時,將發送的Message移除

 

4.內部線程泄露

new Thread(){}.start();Java中的Thread有一個特點就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對所有被激活狀態的線程都是持有強引用,導致GC永遠都無法回收掉這些線程對象,除非線程被手動停止並置為null或者用戶直接kill進程操作。看到這相信你應該也是心中有答案了吧 : 我在每一個MainActivity中都創建了一個線程,此線程會持有MainActivity的引用,即使退出Activity當前線程因為是直接被GC Root引用所以不會被回收掉,導致MainActivity也無法被GC回收。

解決方案:當使用線程時,一定要考慮在Activity退出時,及時將線程也停止並釋放掉

5.單例(主要原因還是因為一般情況下單例都是全局的,有時候會引用一些實際生命周期比較短的變量,導致其無法釋放)

有的單例模式,在創建的時候需要傳遞上下文,這是把當前activity對象傳入,創建了靜態單例。從此靜態單例持有activity對象,造成activity泄漏

解決方案: 傳遞activity的弱飲用或者軟引用,或者傳遞上下文時傳遞生命周期為全程的上下文。比如application上下文,全程都存在的activity

6. Timer Tasks

  這里內存泄漏在於Timer和TimerTask沒有進行Cancel,從而導致Timer和TimerTask一直引用外部類Activity。根本原因就是內部類的使用造成持有外部類activity的引用

解決方案:在適當的時機進行Cancel。

 

靜態常量常見情景:

1.靜態Activities(static Activities)

activity有個靜態activity成員變量,然后在生命周期中將自己賦值給這個靜態常量,這樣導致被持有的activity泄漏

解決方案:不使用靜態activity,或給靜態activity賦值時,考慮賦值的activity生命周期是不是全局的,或者在靜態activity使用完后及時釋放

2.靜態View

在Activity里聲明一個靜態變量view,然后初始化。問題出在這里,View一旦被加載到界面中將會持有一個Context對象的引用,這個context對象是我們的Activity,聲明一個靜態變量引用這個View,也就引用了activity,所以當activity生命周期結束了,靜態View沒有清除掉,還持有activity的引用,因此內存泄漏了。

解決方案:不使用靜態view,或在activity關閉時將靜態view賦值為null

3,靜態Drawable

當一個Drawable附加到一個 View上時,
View會將其作為一個callback設定到Drawable上。上述的代碼片段,意味着Drawable擁有一個TextView的引用,

 

4,集合中對象沒清理造成的內存泄漏

我們通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

 

資源未關閉泄漏:

1.數據庫的cursor沒有關閉。

操作Sqlite數據庫時,Cursor是數據庫表中每一行的集合,Cursor提供了很多方法,可以很方便的讀取數據庫中的值,
可以根據索引,列名等獲取數據庫中的值,通過游標的方式可以調用moveToNext()移到下一行
當我們操作完數據庫后,一定要記得調用Cursor對象的close()來關閉游標,釋放資源。

2,未關閉InputStream/OutputStream。

3,Bitmap對象不在使用時調用recycle()釋放內存

4,BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap

 

 

注冊反注冊泄漏情景:

1,注冊沒有反注冊

通過Context調用getSystemService獲取系統服務,這些服務運行在他們自己的進程執行一系列后台工作或者提供和硬件交互的接口,如果Context對象需要在一個Service內部事件發生時隨時收到通知,則需要把自己作為一個監聽器注冊進去,這樣服務就會持有一個Activity,如果開發者忘記了在Activity被銷毀前注銷這個監聽器,這樣就導致內存泄漏。

解決方案:在onDestroy方法里注銷監聽器。

 

 

圖片處理情況:

1,使用三級緩存    使用LruCache

2,進行圖片壓縮

3,進行圖片裁剪

4,Bitmap對象不在使用時調用recycle()釋放內存

 

直接使用ImageView顯示bitmap會占用較多資源,特別是圖片較大的時候,可能導致崩潰。
使用BitmapFactory.Options設置inSampleSize, 這樣做可以減少對系統資源的要求。
屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
bitmapFactoryOptions.inJustDecodeBounds = true;
bitmapFactoryOptions.inSampleSize = 2;
// 這里一定要將其設置回false,因為之前我們將其設置成了true
// 設置inJustDecodeBounds為true后,decodeFile並不分配空間,即,BitmapFactory解碼出來的Bitmap為Null,但可計算出原始圖片的長度和寬度
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);

 

ListView 情況:

1,構造Adapter時,沒有使用緩存的convertView

解決方案:使用convertView

 

2,  Adapter中引用了Activity如何避免內存泄漏

有時需要點擊ListView條目里的某個按鈕實現界面跳轉,getView()方法inflate布局的時候需要上下文,而且點擊按鈕后的跳轉邏輯也需要上下文。所以我們經常會把Activity傳入到Adapter中,如果Adapter中有很多耗時操作,可能就會防止Activity finish的時候被回收。

 

解決方案:

inflate布局是可以使用getView()方法里的parent參數的,通過parent.getActivity()獲得上下文。

 

 

 

在涉及到Context時先考慮ApplicationContext,當然它並不是萬能的,對於有些地方則必須使用Activity的Context,對於Application,Service,Activity三者的Context的應用場景如下:
這里寫圖片描述
**其中:**NO1表示Application和Service可以啟動一個Activity,不過需要創建一個新的task任務隊列。而對於Dialog而言,只有在Activity中才能創建

 

內存監測工具

1.  Android Studio自帶工具Memory Monitor    ,Device Monitor 

2.DDMS dump + MAT分析

3.LeakCanary

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM