内存泄漏分析框架LeakCanary的使用与原理解析

本文深入探讨了Android中常见的内存泄漏类型,如单例模式、静态实例、Handler、线程和WebView所造成的内存泄漏,并提供了相应的优化策略。接着,文章详细介绍了内存泄漏检测工具LeakCanary的使用方法和实现原理,包括其如何通过弱引用和ReferenceQueue监控Activity,以及在检测到内存泄漏时如何进行堆转储分析和结果展示,帮助开发者有效避免和解决内存泄漏问题。
摘要由CSDN通过智能技术生成

 在《Android性能优化(1):常见内存泄漏与优化(一)》和《Android性能优化(1):常见内存泄漏与优化(二)》文章中,我们详细剖析了垃圾回收器机制、内存泄漏的产生以及分析内存泄漏的各种工具,本文将此基础上,首先回顾一下什么是内存泄漏、Android常见的内存泄漏有哪些以及该如何优化,然后重点介绍内存泄漏分析工具LeakCanary的基本使用和实现原理。

1. 常见内存泄漏

 内存泄漏的表述可为:当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被垃圾收集器回收,结果它们就一直存在于内存中(通常指Java堆内存),占用有效空间,永远无法被删除。随着内存不断泄漏,堆中的可用空间就不断变小,这意味着为了执行常用的程序,垃圾清理需要启动的次数越来越多,非常严重的话会直接造成应用程序报OOM异常。通常,使用可达性分析算法判断对象是否死亡:

1.1 “单例模式” 造成的内存泄漏

 由于单例模式的静态特性(instance对象被static关键词修饰),Commontils对象instance的生命周期将于应用进程的一致。假如我们向CommonUtils的构造方法中传入一个Activity,后面如果这个Activity对象已经不再需要了,而Commontils对象该持有该对象的引用就会使得GC无法对其进行正常回收,从而导致了内存泄漏。优化:对于需要传入Context参数的情况,尽量使用Application的Context,因为它会伴随着应用进程的存在而存在。

/** 单例模式内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/8 17:23
 * @Description:
 */
public class CommonUtils {
   
    // static修饰,生命周期与Application一致
    private static CommonUtils instance;
    private Context mCtx;
	
    private CommonUtils(Context context){
   
        this.mCtx = context;
    }

    public static CommonUtils getInstance(Context context) {
   
        if(instance == null) {
   
            instance = new CommonUtils(context);
        }
        return instance;
    }
}

1.2 “静态实例” 造成内存泄漏

 有时为了防止一个对象的反复重建,就会使用static修饰关键字将这个对象声明为静态实例,以确保该实例始终存在的是同一个,且它的生命周期与应用相同,假设这个实例为SomeResources。然而,由于SomeResources类是一个非静态内部类,它默认持有外部类StaticInstanceActivity的引用,就会导SomeResources的对象一直持有该引用,造成内存泄漏。优化:使用单例模式实现SomeResources,或者将其改成静态内部类。如果需要传入Context参数,必须使用Application的Context。

/**非静态内部类创建静态实例造成的内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/9 10:43
 * @Description:
 */
public class StaticInstanceLeakActivity extends AppCompatActivity {
   
    // 静态属性,生命周期与Application一致
    private static SomeResources mSomeResources;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        
        if(mSomeResources == null) {
   
            mSomeResources = new SomeResources(this);
        }
    }

    class SomeResources {
   
        private Context mCtx;
        
        public SomeResources(Context context) {
   
            this.mCtx = context;
        }
    }
}

1.3 “Handler” 造成的内存泄漏

 由于Handler匿名内部类默认持有外部类HandlerActivity的引用,假如HandlerActivity已经不再被使用了,但是由于MessageQueue仍然有消息要处理,那么就会导致HandlerActivity对象被Handler一直持有,从而导致HandlerActivity对象无法被GC正常回收,进而造成内存泄漏。优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。如果使用持有外部类HandlerActivity对象,可以使用弱引用实现。

/** 使用Handler造成内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/8 17:55
 * @Description:
 */
public class HandlerLeakActivity extends AppCompatActivity {
   
	
    // 匿名内部类
    // 默认持有外部类HandlerActivity对象的引用
    private Handler mUIHandler = new Handler() {
   
        @Override
        public void handleMessage(Message msg) {
   
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);

        new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                // 处理耗时任务
                // ...
                
                mUIHandler.sendEmptyMessage(0x00);
            }
        });
    }
}

1.4 “线程” 造成的内存泄漏

 由于Java类中的非静态内部类和匿名内部类默认持有外部类的引用。对于下述示例中的MyRunnable来说,它是一个非静态内部类,将默认持有ThreadActivity对象的引用。假如子线程的任务在ThreadActivity销毁之前还未完成,就会导致ThreadActivity无法被GC正常回收,造成内存泄漏。优化:将MyRunnable独立出来或使用静态内部类(static关键字修饰类),因为静态内部类不持有外部类的引用。

/** 使用线程造成的内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/9 10:04
 * @Description:
 */
public class ThreadLeakActivity extends AppCompatActivity {
   

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        // 开启一个子线程
        new Thread(new MyRunnable()).start();
    }

    class MyRunnable implements Runnable {
   

        @Override
        public void run() {
   
			// 执行耗时任务
            Thread.sleep(100000);
        }
    }
}

1.5 “使用WebView” 造成的内存泄漏

 使用WebView造成泄漏的原因是在不使用WebView时没有调用其destory方法来销毁它,导致其长期占用内存且不能被回收。在优化时,可以为WebView开启另外一个进程,通过AIDL与主线程进行通信,便于WebVIew所在的进程可以根据业务需要选择合适的时机进行销毁。

/** 使用WebView造成的内存泄漏案例与优化
 * @Auther: Jiangdg
 * @Date: 2019/12/26 10:04
 * @Description:
 */
public class WebViewLeakActivity extends AppCompatActivity {
   
    private WebView mWebView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mWebView = (WebView)findViewById(R.id.webview)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值