在分析Android內存泄漏以前,先了解一下JAVA的一些知識
1. JAVA中的對象的建立android
2.Java如何斷定對象存活或死亡?算法
引用計數法
1給對象中添加一個引用計數,假如爲count
2當引用這個對象時:count++
3當count==0時:對象處於,也就是說沒有其它地方在引用這個對象了,對象就處於「死亡」狀態,回收對象數據庫
可達性分析算法
舉個例子:像找人同樣,A認識B,B認識C,C認識D,那麼A就要吧經過這樣的關係認識D,若是能找到D,說明D對象是存活的,不能回收,若是經過全部的關係都找不到D,說明D是「死亡」的,回收D對象。
可達性分析算法的定義:經過一系列的稱爲 GC
Roots 的對象做爲起點,從這些節點開始向下搜索,搜索把走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連(就是從GC Roots 到這個對象不可達)時,則證實此對象是不可用的。以下圖,Object5,Object6,Object7就是不可達對象,是要被回收的對象
網絡
問:哪些對象能夠做爲GC Roots對象呢?app
3.引用分類工具
Object obj = new Object() //強引用
String str=new String("123"); // 強引用 SoftReference softRef=new SoftReference(str); // 軟引用
String str=new String("abc"); WeakReference abcWeakRef = new WeakReference(str); str=null;
注:JAVA中這4種引用的級別由高到低依次爲: 強引用 > 軟引用 > 弱引用 > 虛引用oop
** 4.JAVA中內存分配 **學習
上面的是JAVA的一些預備知識,下面分析Android內存泄漏相關測試
** 1 內存泄漏與內存溢出**gradle
** 2 Android內存泄漏分類 **
(2) 長生命週期引用短生命週期
(1) Handler泄漏
(1) Cursor,InputStream/OutputStream 忘記調用close
(1) 在6.0系統,獲取ConnectivityManager服務,若是第一次使用的是Activity對應的Context去獲取這個服務,就會致使內存泄漏
(1) Handler 的消息未處理完,這時若是Handler是在Activity內存類實現的,消息引用Handler,Handler又引用了Activity,這時若是關閉Activity,就會形成內存泄漏
(1) 好比 EventBus.unregister() 忘記調用
注:非靜態內部類和匿名內部類都會潛在的引用它們所屬的外部類,可是靜態內部類卻不會
** 3 Android內存泄漏分析工具 **
推薦使用LeakCanary,•LeakCanary是一個檢測Java和Android內存泄漏的庫,集成LeakCanary以後,只須要等待內存泄漏出現就能夠了無需認爲進行主動檢測
** 4 LeakCanary的添加 **
完成以上兩步,就添加了LeakCanary,接下來就正常開發測試就好了,若是有內存泄漏,就會在通知欄中會有相應的通知,點開看就能夠了,找到對應的內存泄漏的地方,解決
下面是演示的內存泄漏的幾張圖,能夠看一下:
5 Android內存泄漏的案例
public class ToastUtils { private static String oldMsg; protected static Toast toast = null; private static long oneTime = 0; private static long twoTime = 0; private static long gapTime = 3 * 1000;//3s只顯示一次 public static void show(Context context, String s) { if (context != null && !TextUtils.isEmpty(s)) { if (toast == null) { toast = Toast.makeText(context, s, Toast.LENGTH_SHORT); toast.show(); oneTime = System.currentTimeMillis(); } else { twoTime = System.currentTimeMillis(); if (s.equals(oldMsg)) { if (twoTime - oneTime > gapTime) { toast.show(); } } else { oldMsg = s; toast.setText(s); toast.show(); } } oneTime = twoTime; } } }
在Activity 中使用:
ToastUtils.show(this, "登陸成功");
上面的代碼就會出現內存泄漏,由於在activity中使用ToastUtils.show(this, "登陸成功")的時候,傳的第一個參數 this 表明當時的activity,而ToashTuils中的toast變量是一個靜態變量,
代碼以下
protected static Toast toast = null;
建立toast對象以下代碼
toast = Toast.makeText(context, s, Toast.LENGTH_SHORT);
Toast.makeText的第一參數就是上面傳的activity,Toast類中有一個變量mContext會保存這個activity,就是強引用,可是toast又是一個靜態的變量,靜態變量的生命同期是和當前的APP的進程同樣長的,因此這時咱們若是關閉這個Activity,就會致使Activity被靜態變量強引用,垃圾回收永遠不會回收這個Activity,因此就會出現內存泄漏。
咱們看一下Toast.makeText的源碼
上面圖中,new一個Toast,把context傳給了Toast的構造方法。
因此調用 ToastUtils.show(this, "登陸成功");就會致使 activity 被靜態的toast變量強引用了,致使內存泄漏。
解決方法
用ApplicationContext替代Activity,以下代碼
public static void show(Context context, String s) { //在這裏獲取applicationContext,applicationContext的生命週期是和進程同樣長 //這樣就不會出現內存泄漏了 context = context.getApplicationContext(); if (context != null && !TextUtils.isEmpty(s)) { if (toast == null) { toast = Toast.makeText(context, s, Toast.LENGTH_SHORT); toast.show(); ...... } }
上圖:在MainActivity中有一個匿名內部類Handler,而且有一個此類的對象 uiHandler。
這時咱們若是在MainAcitity 中調用下面代碼,就會出現內存泄漏
uiHandle.sendMessageDelayed(uiHandle.obtainMessage(),60 * 1000);
uiHandler.obtainMessage獲取的msg 中有一個成員變量 target,target保存的就是uiHandle,而uiHandler又是內部類建立的對象,因此uiHandler隱式的會對當前的外部類,也就是MainActivity會有一個強引用,以下
msg -> uiHandler -> MainActivity
msg 引用了uiHandler,uiHandler引用了MainActivity,而後這個msg須要60s後才被處理完,在處理過程當中,若是退出MainActivity,這時候就會致使內存泄漏,MainActivity回收不了。應該被回收的對象沒有被回收掉,就是內存泄漏。
注:handler機制不明白的能夠先看下handler機制,message,handler,loop的關係
解決方法
uiHandle.removeCallbacksAndMessages(null);
使用弱引用的時候,須要做一下判斷是否爲null。
案例三:Activity context的不正確使用
上面的兩個案例中其實也是context的使用場景不當形成的內存泄漏,這裏再也不舉例,咱們一般使用的兩種context是 Acitivty和 Application,只須要注意對context的使用不要超過它的生命同期。部分狀況下可使用applicationContext代替activity的context,由於applicatoinContext會隨着應用程序的存在而存在,而不依賴於activity的生命週期。還有要慎重對context使用static關鍵字。
案例四:一些資源使用完後沒有關閉
如數據庫的遊標 Cursor,輸入輸出流 InputStream/OutputStream沒有close
案例五:註冊的監聽器沒有反註冊
如EventBus.register,ButterKnife等沒有在activity的onDestroy中反註冊或者其它地方反註冊
案例六:系統服務的泄漏
在實際項目中發現的,在6.0系統上在activity中第一次若是用的是activity對應的context獲取ConnectivityManager服務會形成內存泄漏。
代碼對下:
/** * 判斷是否有網絡鏈接 * @param context * @return */ public static boolean isNetworkConnected(Context context) { if (context != null) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mNetworkInfo = cm.getActiveNetworkInfo(); if (mNetworkInfo != null) { return mNetworkInfo.isAvailable(); } } return false; }
若是是第一次在activity中調用以下代碼,會發現內存泄漏
//注意,這個this 是表明的是當前的activity isNetworkConnected(this)
簡單介紹下:先從Context的getSystemService方法開始,咱們知道Activity是從ContextWrapper繼承而來的,ContextWrapper中持有一個mBase實例,這個實例指向一個ContextImpl對象,同時ContextImpl對象持有一個OuterContext對象,對於Activity來講,這個OuterContext就是Activity對象。因此調用getSystemService最終會調用到ContextImpl的getSystemService方法。
在6.0上,在6.0上,ConnectivityManager實現爲單例,建立這個單例對象的時候,把相應的OuterContext就是Activity對象,保存到了ConnectivityManager中,就形成了一個單例對象強引用了activity對象,從而形成了內存泄漏,若是是第一次用的是application,則保存的不是activity而是application,反而不會出現內存泄漏了。
使用LeakCanary檢測 ConnectivityManager 內存泄漏圖以下:
解決方法
使用applicationContext去獲取服務,不要使用activityContext去獲取服務
上面的就是對Android內存泄漏的一些總結,若是有不正確的或者須要補充的地方,請指出,一塊學習進步