在分析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内存泄漏的一些总结,若是有不正确的或者须要补充的地方,请指出,一块学习进步