App性能优化工具----LeakCanary

本文详细介绍LeakCanary在Android应用中的使用方法及内存泄漏检测原理,涵盖配置、常见泄漏场景及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


1、LeakCanary的简介:
在Android的性能优化中,内存优化是必不可少的点,而内存优化最重要的一点就是解决内存泄漏的问题,在Android的内存泄漏分析工具也不少,比如PC端的有:AndroidStudio自带的Android Profiler、MAT等工具;手机端也有,就是我们今天要介绍的
Square公司基于MAT开源了LeakCanary
LeakCanary显示内存泄漏的页面:



2、LeakCanary的使用

在app build.gradle 中加入引用:

dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}

在 Application 中:

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

如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。



代码检测Activity的泄露以及其他类的泄露,需要使用RefWatcher来进行监控。改写Application,如下所示:
 

public class MyApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher= setupLeakCanary();
    }

    private RefWatcher setupLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        MyApplication leakApplication = (MyApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。

3、举个栗子:
内存泄漏的代码中引入LeakCanary监控  
(1)、LeakThread

public class MainActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }

    class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                CommonUtils.getInstance(MainActivity.this);
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。

运行程序,会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来。

解决方法就是将LeakThread改为静态内部类,再次运行程序LeakThread就不会给出内存泄漏的提示了。
如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入

RefWatcher refWatcher = MyApplication.getRefWatcher(this);
     refWatcher.watch(this);

下面举个单例造成的内存泄漏
(2)Singleton

public class Singleton {
    private static Singleton singleton;
    private Context context;
    private Singleton(Context context) {
        this.context = context;
    }

    public static Singleton newInstance(Context context) {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null){//双重检查锁定
                    singleton = new Singleton(context);
                }
            }
        }
        return singleton;
    }
}

在需要监听的对象中调用RefWatcher的watch方法进行监听,我们可以在该Acitivity中onCreate方法中添加getRefWatcher().watch(this);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getRefWatcher().watch(this);
    setContentView(R.layout.activity_main);
    Singleton singleton = Singleton.newInstance(this);
}

解决方案就是 单列模式来产生内存泄漏 Singleton singleton = Singleton.newInstance(this)的调用传入Contxt时使用ApplicationContext

(3)Handler

 private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    mTextView.setText(msg.obj + "");
                    break;
                default:
            }
        }
    };

    private void doInBackground() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟耗时操作
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //耗时操作完成,发送消息给UI线程
                Message msg = Message.obtain();
                msg.what = 1;
                msg.obj = "更新UI";
                mHandler.sendMessage(msg);
            }
        }).start();
    }

}

handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露,所以handler要定义为static。

(4)、非静态内部类创建的静态实例造成的内存泄漏

public class MainActivity extends AppCompatActivity {
 
    private static TestResource mResource = null;
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
    }
 
    class TestResource {
        //...
    } 
}

这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:
1、将内部类改为静态内部类。      2、将这个内部类封装成一个单例,Context使用Application的Context
 

4、针对LeakCanary解决方案
      出现的场景:     出现的详细场景
      (1)、单例设计模式造成的内存泄漏
      (2)、非静态内部类创建的静态实例造成的内存泄漏
      (3)、Handler造成的内存泄漏
      (4)、线程造成的内存泄漏
      (5)、资源未关闭造成的内存泄漏
      (6)、集合类造成内存泄漏
      (7)、WebView造成内存泄漏
      (8)EditText造成内存泄漏
      (9)属性动画造成内存泄漏

       常见的解决方案:  内存泄漏检测工具:Android Profiler     LeakCanary
        1、尽量使用Application的Context而不是Activity的
        2、使用弱引用或者软引用漏
        3、手动设置null,解除引用关系漏
        4、将内部类设置为static,不隐式持有外部的实例漏
        5、在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存,


5、LeakCanary检测原理     源文

(1)、ReftWatcher是leakcancay检测内存泄露的发起点。使用方法为,在对象生命周期即将结束的时候,调用

RefWatcher.watch(Object object)
public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    //如果处于debug模式,则直接返回
    if (debuggerControl.isDebuggerAttached()) {
      return;
    }
    //记住开始观测的时间
    final long watchStartNanoTime = System.nanoTime();
    //生成一个随机的key,并加入set中
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    //生成一个KeyedWeakReference
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //调用watchExecutor,执行内存泄露的检测
    watchExecutor.execute(new Runnable() {
      @Override public void run() {
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }
  • watchExecutor: 执行内存泄露检测的executor
  • debuggerControl :用于查询是否正在调试中,调试中不会执行内存泄露检测
  • queue : 用于判断弱引用所持有的对象是否已被GC。
  • gcTrigger: 用于在判断内存泄露之前,再给一次GC的机会
  • headDumper: 用于在产生内存泄露室执行dump 内存heap
  • heapdumpListener: 用于分析前面产生的dump文件,找到内存泄露的原因
  • excludedRefs: 用于排除某些系统bug导致的内存泄露
  • retainedKeys: 持有那些呆检测以及产生内存泄露的引用的key。

所以最后的核心函数是在ensureGone这个runnable里面。要理解其工作原理,就得从keyedWeakReference说起

从watch函数中,可以看到,每次检测对象内存是否泄露时,我们都会生成一个KeyedReferenceQueue,这个类其实就是一个WeakReference,只不过其额外附带了一个key和一个name

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

在构造时我们需要传入一个ReferenceQueue,这个ReferenceQueue是直接传入了WeakReference中

WeakReference与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中;通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。

为了确保最大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在极小程度的误差的。

(2)、检测的流程:

  • 移除不可达引用,如果当前引用不存在了,则不继续执行
  • 手动触发GC操作,gcTrigger中封装了gc操作的代码
  • 再次移除不可达引用,如果引用不存在了,则不继续执行
  • 如果两次判定都没有被回收,则开始分析这个引用,最终生成HeapDump信息

(3)、监测时机
什么时候去检测能判定内存泄露呢?这个可以看AndroidWatchExecutor的实现


public final class AndroidWatchExecutor implements Executor {
    private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
        // This needs to be called from the main thread.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
            return false;
          }
        });
      }
  }

这里又看到一个比较少的用法,IdleHandler,IdleHandler的原理就是在messageQueue因为空闲等待消息时给使用者一个hook。那AndroidWatchExecutor会在主线程空闲的时候,派发一个后台任务,这个后台任务会在DELAY_MILLIS时间之后执行。LeakCanary设置的是5秒。

6、总结:

LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。
      <1>无法检测出Service中的内存泄漏问题
       <2>如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏。

所以说LeakCanary针对Activity/Fragment的内存泄漏检测非常好用,但是对于以上检测不到的情况,还得配合Android Monitor + MAT 来分析。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值