从LeakCanary看如何判断对象被回收了

前面已经了解了Service,Fragment,ViewModel对象的销毁时机,那么在触发销毁时机后,我们怎么判断这些对象有没有回收呢?

大家都知道在Java中有强引用,弱引用,软引用,虚引用四种引用方式,而我们判断对象是否回收,就需要通过弱引用来实现,针对弱引用而言。其提供了两种构造方法,如下图所示:

从LeakCanary看如何判断对象被回收了_第1张图片

其中我们重点需要关注第二个构造函数,从函数说明可以看出当该弱引用对象创建后,如果该弱引用所引用的对象被GC,则该弱引用对象会被放入给定的ReferenceQueue中,以便系统回收(需注意WeakReference对象和WeakReference引用的对象的区别,前者是WeakReference类的实例,后者是弱引用对象实例,弱引用对象实例在WeakReference的构造函数中传入),这也就意味着我们可以将应该被销毁的对象收集起来,并为他们依此指定ReferenceQueue,通过比对ReferenceQueue中对象情况和我们收集到的对象情况来判断该对象是否被正常销毁。

WeakReference标志化

从LeakCanary看如何判断对象被回收了_第2张图片

我们可以看到WeakReference本身是范型对象,这种情况下为了存储多种对象,我们通常会考虑范型T直接指定为Object类型,这也就导致指定WeakReference没办法和其他WeakReference区分,进而进行比较,此时就要求我们要为WeakReference生成唯一标识。

这里我们通过自定义WeakReference来实现,为每一个WeakReference对象赋予一个唯一标识mKey,代码如下:

public class KeyedWeakReference extends WeakReference {
    private String mKey;

    public KeyedWeakReference(String key, Object referent, ReferenceQueue q) {
        super(referent, q);
        mKey = key;
    }

    public String getKey() {
        return mKey;
    }

    @NonNull
    @Override
    public String toString() {
        return "KeyedWeakReference{ mKey=" + mKey + ",Object=" + get() + " }";
    }
}

监听对象回收

当某一对象需要回收时,首先我们将该对象包装在WeakHashMap中,以UUID为key,以WeakReference对象为value,随后将该对象的ReferenceQueue指定为我们自定义的,在一段时间后遍历ReferenceQueue,将Queue中存在的所有WeakReference对象按照key从WeakHashMap中移除,HashMap中剩下的就是有可能发生了内存泄漏的对象。

详细的实现代码如下:

public class ObjectWatcher {
    private static final String TAG = "ObjectWatcher";
    private ReferenceQueue mReferenceQueue;
    private WeakHashMap<String, KeyedWeakReference> mReferences;

    private ObjectWatcher() {
        mReferenceQueue = new ReferenceQueue<Object>();
        mReferences = new WeakHashMap<>();
    }

    private static volatile ObjectWatcher mInstance;
    public static ObjectWatcher getInstance() {
        if (mInstance == null) {
            synchronized (ObjectWatcher.class) {
                if (mInstance == null) {
                    mInstance = new ObjectWatcher();
                }
            }
        }
        return mInstance;
    }

    public void watch(Object object) {
        String key = UUID.randomUUID().toString();
        Log.d(TAG, "watch object:" + object + ",key:" + key);
        KeyedWeakReference weakReference = new KeyedWeakReference(key, object, mReferenceQueue);
        mReferences.put(key, weakReference);
        Handler mainHandler = new Handler(Looper.getMainLooper());
        mainHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

                KeyedWeakReference keyedWeakReference = null;
                do {
                    keyedWeakReference = (KeyedWeakReference) mReferenceQueue.poll();
                    Log.d(TAG, "keyedWeakReference:" + keyedWeakReference);
                    if (keyedWeakReference != null) {
                        mReferences.remove(keyedWeakReference.getKey());
                        Log.d(TAG, "object has been destroyed:" + keyedWeakReference.toString());
                    }
                } while (keyedWeakReference != null);
            }
        }, 5000);
    }

}

在上述代码中,我们将需要观察的对象通过watch方法传入,随后创建KeyedWeakReference对象,分别将该对象装入ReferenceQueue(系统底层代码实现)和mReferences WeakHashMap中,随后在5秒后遍历ReferenceQueue,确实监听到了对象被回收,日志打印如下:

从LeakCanary看如何判断对象被回收了_第3张图片

结合上文我们就可以判断一个对象是否已经被回收了,当然针对WeakHashMap中仍然存在的对象,我们可以触发一次GC后,再次遍历观察。

为什么是弱引用,相信有熟悉四大引用的朋友,也看到过软引用和虚引用的构造函数,这两种引用的构造函数也可以指定ReferenceQueue,如下图所示:

从LeakCanary看如何判断对象被回收了_第4张图片

从LeakCanary看如何判断对象被回收了_第5张图片

那么为什么不使用软引用或者虚引用,非要使用弱引用呢?Github issue上也有同样的疑问,如下图:

从LeakCanary看如何判断对象被回收了_第6张图片

从图中可以看到,这里主要的考虑应该是触发的频次,对于弱引用而言,其在下次GC时就会触发。

你可能感兴趣的:(leakcanary,android,android,jetpack,androidx,开发语言,java)