内存泄漏检测工具LeakCanary的简单使用

1.引入

dependencies {    
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
    // Optional, if you use support library fragments:
    debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

2.在application中进行初始化

import android.app.Application;
import com.squareup.leakcanary.LeakCanary;

public class MyApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
    }

}

两部就可以了;

3.写一个内存泄漏的例子

package com.ysl.myandroidbase.leak;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

import com.ysl.myandroidbase.R;

public class LeakActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv1);
        tv.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("点击");
                Message msg = Message.obtain();
                handler.sendMessageDelayed(msg,1000*60*5);
                finish();
            }
        });

        MyLeak.getInstance(this);
    }

    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}


//单例

import android.content.Context;

public class MyLeak {
    private static MyLeak myLeak;
    private Context context;

    private MyLeak(){}
    public static MyLeak getInstance(Context context){
        if (myLeak == null)
            myLeak = new MyLeak();
        myLeak.context = context;
        return myLeak;
    }
}

上面验证了一个handler,一个单例中对activity的引用;

我们看一下结果:

当我们按返回按键的时候,activity调用了ondestory方法;

然后在LeakCanary就可以在onActivityDestroyed方法中为所有的Activity调用refWatcher.watch(activity)

可以看一下它的主要检测步骤:

  • 1.分析是否有可疑的泄漏对象,主要是通过弱引用机制来检测;
  • 2.如果第一步发现了可疑泄漏对象,那么就会dump内存快照,然后分析.hprof文件确定是否真的泄漏了。
  • 3.展示分析的结果。

参考:https://www.jianshu.com/p/1fdcdd64c3a5

4.handler和单例应该是比较常见的内存泄漏地方了;

我们这样修改一下即可:

public static MyLeak getInstance(Context context){
        if (myLeak == null)
            myLeak = new MyLeak();
        myLeak.context = context.getApplicationContext();
        return myLeak;
    }

handler的话AS都会警告我们,要使用static修饰一下即可。

5.哪些地方容易内存泄漏

单例造成的内存泄露

非静态内部类 / 匿名类

集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收

网络、文件等流忘记关闭

手动注册广播时,退出时忘记 unregisterReceiver()

Service 执行完后忘记 stopSelf()

EventBus 等观察者模式的框架忘记手动解除注册

static 关键字修饰的成员变量

ListView 的 Item 泄露

参考:https://www.jianshu.com/p/65f914e6a2f8

内存泄漏就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。此时,如果Obj4是一个没有用的对象,但它仍与GC Roots是可达的,那么Obj4就会内存泄漏。
内存泄漏产生的原因,主要分为三大类:
1.由开发人员自己编码造成的泄漏。
2.第三方框架造成的泄漏。
3.由Android 系统或者第三方ROM造成的泄漏。
其中第二种和第三种有时是不可控的,但是第一种是可控的,既然是可控的,我们就要尽量在编码时避免造成内存泄漏,下面就来列举出常见的内存泄漏的场景。

2.1 非静态内部类的静态实例
非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接的长期维持着外部类的引用,阻止被系统回收。

2.2 匿名内部类的静态实例
和前面的非静态内部类一样,匿名内部类也会持有外部类实例的引用。

2.3 Handler内存泄漏
Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。

2.4 未正确使用Context
对于不是必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们可以考虑使用Application Context来代替Activity的Context,这样可以避免Activity泄露,比如如下的单例模式;

2.5 静态View
使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestory方法中将静态View置为null。

2.6 WebView
不同的Android版本的WebView会有差异,加上不同厂商的定制ROM的WebView的差异,这就导致WebView存在着很大的兼容性问题。WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

2.7 资源对象未关闭
资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭它们。把他们的引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。

2.8 集合中对象没清理
通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。

2.9 Bitmap对象
临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。

2.10 监听器未关闭
很多系统服务(比如TelephonyMannager、SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener。

参考:http://liuwangshu.cn/application/performance/ram-3-memory-leak.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值