1、為什么會產生內存泄漏
當一個對象已經不需要再使用本該被回收時,另外一個正在使用的對象持有它的引用從而導致它不能被回收,這導致本該被回收的對象不能被回收而停留在堆內存中,這就產生了內存泄漏。
2、內存泄漏對程序的影響
內存泄漏是造成應用程序OOM的主要原因之一。我們知道Android系統為每個應用程序分配的內存是有限的,而當一個應用中產生的內存泄漏比較多時,這就難免會導致應用所需要的內存超過系統分配的內存限額,這就造成了內存溢出從而導致應用Crash。
3、如何檢查和分析內存泄漏
因為內存泄漏是在堆內存中,所以對我們來說並不是可見的。通常我們可以借助MAT、LeakCanary等工具來檢測應用程序是否存在內存泄漏。
1、MAT是一款強大的內存分析工具,功能繁多而復雜。
2、LeakCanary則是由Square開源的一款輕量級的第三方內存泄漏檢測工具,當檢測到程序中產生內存泄漏時,它將以最直觀的方式告訴我們哪里產生了內存泄漏和導致誰泄漏了而不能被回收。(LeakCanary使用教程https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/)
4、常見的內存泄漏及解決方法
1、單例造成的內存泄漏
由於單例的靜態特性使得其生命周期和應用的生命周期一樣長,如果一個對象已經不再需要使用了,而單例對象還持有該對象的引用,就會使得該對象不能被正常回收,從而導致了內存泄漏。
示例:防止單例導致內存泄漏的實例
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; } }
這是一個普通的單例模式,當創建這個單例的時候,由於需要傳入一個Context,所以這個Context的生命周期的長短至關重要:
1、如果此時傳入的是 Application 的 Context,因為 Application 的生命周期就是整個應用的生命周期,所以這將沒有任何問題。
2、如果此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 退出時,由於該 Context 的引用被單例對象所持有,其生命周期等於整個應用
程序的生命周期,所以當前 Activity 退出時它的內存並不會被回收,這就造成泄漏了。
應該改成如下:
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; } }
2、非靜態內部類創建靜態實例造成的內存泄漏
例如,有時候我們可能會在啟動頻繁的Activity中,為了避免重復創建相同的數據資源,可能會出現如下寫法:
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){ mResource = new TestResource(); } //... } class TestResource { //... } }
這樣在Activity內部創建了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據。雖然這樣避免了資源的重復創建,但是這種寫法卻會造成內存泄漏。因為非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,從而導致Activity的內存資源不能被正常回收。
解決方法:將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,就使用Application的Context。
3、Handler造成的內存泄漏
示例:創建匿名內部類的靜態對象public class MainActivity extends AppCompatActivity { private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // ... handler.sendEmptyMessage(0x123); } }); } }
1、從Android的角度
當Android應用程序啟動時,該應用程序的主線程會自動創建一個Looper對象和與之關聯的MessageQueue。當主線程中實例化一個Handler對象后,
它就會自動與主線程Looper的MessageQueue關聯起來。所有發送到MessageQueue的Messag都會持有Handler的引用,所以Looper會據此回調
Handle的handleMessage()方法來處理消息。只要MessageQueue中有未處理的Message,Looper就會不斷的從中取出並交給Handler處理。另外,
主線程的Looper對象會伴隨該應用程序的整個生命周期。
2、 Java角度
在Java中,非靜態內部類和匿名類內部類都會潛在持有它們所屬的外部類的引用,但是靜態內部類卻不會。
對上述的示例進行分析,當MainActivity結束時,未處理的消息持有handler的引用,而handler又持有它所屬的外部類也就是MainActivity的引用。
這條引用關系會一直保持直到消息得到處理,這樣阻止了MainActivity被垃圾回收器回收,從而造成了內存泄漏。
解決方法:將Handler類獨立出來或者使用靜態內部類,這樣便可以避免內存泄漏。
5、資源未關閉造成的內存泄漏
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源,應該在Activity銷毀時及時關閉或者注銷,否則這些資源
將不會被回收,從而造成內存泄漏。
1)比如在Activity中register了一個BraodcastReceiver,但在Activity結束后沒有unregister該BraodcastReceiver。
2)資源性對象比如Cursor,Stream、File文件等往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。它們的
緩沖不僅存在於 java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存泄漏。
3)對於資源性對象在不使用的時候,應該調用它的close()函數將其關閉掉,然后再設置為null。在我們的程序退出時一定要確保我們的資源性對象已經關閉。
4)Bitmap對象不在使用時調用recycle()釋放內存。
6、使用ListView時造成的內存泄漏
初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化一定數量的View對象,同時ListView會將這些View對象緩存起來。當向上滾動ListView時,
原先位於最上面的Item的View對象會被回收,然后被用來構造新出現在下面的Item。這個構造過程就是由getView()方法完成的,getView()的第二個形參
convertView就是被緩存起來的Item的View對象(初始化時緩存中沒有View對象則convertView是null)。
構造Adapter時,沒有使用緩存的convertView。
解決方法:在構造Adapter時,使用緩存的convertView。
7、集合容器中的內存泄露
我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
解決方法:在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
8、WebView造成的泄露
當我們不要使用WebView對象時,應該調用它的destory()函數來銷毀它,並釋放其占用的內存,否則其長期占用的內存也不能被回收,從而造成內存泄露。
解決方法:為WebView另外開啟一個進程,通過AIDL與主線程進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到內
存的完整釋放。
如何避免內存泄漏?
1、在涉及使用Context時,對於生命周期比Activity長的對象應該使用Application的Context。凡是使用Context優先考慮Application的Context,當然
它並不是萬能的,對於有些地方則必須使用Activity的Context。對於Application,Service,Activity三者的Context的應用場景如下:
其中,NO1表示Application和Service可以啟動一個Activity,不過需要創建一個新的task任務隊列。而對於Dialog而言,只有在Activity中才能創建。
除此之外三者都可以使用。
2、對於需要在靜態內部類中使用非靜態外部成員變量(如:Context、View ),可以在靜態內部類中使用弱引用來引用外部類的變量來避免內存泄漏。
3、對於不再需要使用的對象,顯示的將其賦值為null,比如使用完Bitmap后先調用recycle(),再賦為null。
4、保持對對象生命周期的敏感,特別注意單例、靜態對象、全局性集合等的生命周期。
5、對於生命周期比Activity長的內部類對象,並且內部類中使用了外部類的成員變量,可以這樣做避免內存泄漏:
1)將內部類改為靜態內部類
2)靜態內部類中使用弱引用來引用外部類的成員變量