前面已经了解了Service,Fragment,ViewModel对象的销毁时机,那么在触发销毁时机后,我们怎么判断这些对象有没有回收呢?
大家都知道在Java中有强引用,弱引用,软引用,虚引用四种引用方式,而我们判断对象是否回收,就需要通过弱引用来实现,针对弱引用而言。其提供了两种构造方法,如下图所示:
其中我们重点需要关注第二个构造函数,从函数说明可以看出当该弱引用对象创建后,如果该弱引用所引用的对象被GC,则该弱引用对象会被放入给定的ReferenceQueue中,以便系统回收(需注意WeakReference对象和WeakReference引用的对象的区别,前者是WeakReference类的实例,后者是弱引用对象实例,弱引用对象实例在WeakReference的构造函数中传入),这也就意味着我们可以将应该被销毁的对象收集起来,并为他们依此指定ReferenceQueue,通过比对ReferenceQueue中对象情况和我们收集到的对象情况来判断该对象是否被正常销毁。
WeakReference标志化
我们可以看到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,确实监听到了对象被回收,日志打印如下:
结合上文我们就可以判断一个对象是否已经被回收了,当然针对WeakHashMap中仍然存在的对象,我们可以触发一次GC后,再次遍历观察。
为什么是弱引用,相信有熟悉四大引用的朋友,也看到过软引用和虚引用的构造函数,这两种引用的构造函数也可以指定ReferenceQueue,如下图所示:
那么为什么不使用软引用或者虚引用,非要使用弱引用呢?Github issue上也有同样的疑问,如下图:
从图中可以看到,这里主要的考虑应该是触发的频次,对于弱引用而言,其在下次GC时就会触发。