在Android开发里,Reference体系包含强引用、软引用、弱引用和虚引用四种类型,它们决定了对象在不同内存压力下的存活边界。弱引用(WeakReference)不会阻止垃圾回收器回收其指向的对象,这一特性常被用于构建不会泄漏的缓存或监听器持有关系。但在编写单元测试时,开发者往往希望验证某个对象确实因为只被弱引用持有而被释放,从而确认架构上没有多余的长生命周期依赖。

Java引用类型与垃圾回收底层逻辑
要写好Android参考测试,必须先理解Reference在虚拟机中的工作原理。强引用是最普通的对象赋值,只要强引用链存在,对象就绝对不会被回收。软引用在内存不足时才会被清理,弱引用则在下次GC时只要没有强引用就会被回收,而虚引用必须配合ReferenceQueue使用,主要用于跟踪对象被回收的事件本身。
在Dalvik与ART虚拟机中,垃圾回收的触发时机并不完全由代码控制。即便调用了System.gc,虚拟机也仅仅是“建议”回收,真正执行要看当前堆状态和GC策略。因此在测试中如果简单写一个断言assertNull(weakRef.get()),很可能因为GC尚未运行而失败。理解这一点,才能设计出可靠的验证方式。
ReferenceQueue是引用机制里非常关键的一环。当弱引用指向的对象被回收后,这个WeakReference实例本身会被加入与之关联的ReferenceQueue。通过轮询队列,我们可以确认回收确实发生,而不依赖get方法的即时返回值。下面代码展示了基本用法:
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
public class RefDemo {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
Object target = new Object();
WeakReference<Object> wr = new WeakReference<Object>(target, queue);
target = null;
Runtime.getRuntime().gc();
Thread.sleep(100);
// 从队列中取出被回收的引用
WeakReference<Object> polled = (WeakReference<Object>) queue.poll();
System.out.println(polled == wr);
}
}
在Android单元测试中稳定验证弱引用回收
Android常用的单元测试运行在JVM上(如JUnit4/5本地测试),并不依赖真机或模拟器的完整ART环境,但引用行为依旧遵循Java规范。为了让参考测试不“抖动”,应当显式调用Runtime.getRuntime().gc()并配合短暂停顿,再从ReferenceQueue中确认。这样比单纯检查get更稳健,因为对象进入队列意味着回收流程已经走完。
另一个常见误区是在测试里持有局部强引用而不自知。例如把被测对象先赋值给一个方法内的局部变量,再创建WeakReference,此时局部变量在断言前一直存活,弱引用自然不会清空。正确做法是在创建引用后立即将原始变量置为null,并尽量缩小作用域,必要时可抽取成独立方法让栈帧退出。
如果测试涉及Android上下文(如Activity的弱引用防泄漏),可以使用Robolectric提供的内存环境,或仅在本地测试中验证自定义引用持有逻辑。以下示例展示了一个JUnit测试方法的写法:
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
public class WeakRefTest {
@Test
public void testWeakRefCollected() throws Exception {
ReferenceQueue<Object> q = new ReferenceQueue<Object>();
Object data = new Object();
WeakReference<Object> ref = new WeakReference<Object>(data, q);
data = null;
Runtime.getRuntime().gc();
Thread.sleep(200);
assertTrue(q.poll() == ref);
}
}
Mockito等框架对Reference对象的桩处理对比
当业务代码将Reference作为依赖注入或返回值使用时,单元测试可能需要用Mockito来模拟。直接mock一个WeakReference会使其get方法返回默认值null,这通常不符合“对象暂时存活”的语义。更好的方式是用真实对象构造WeakReference,或使用mock配合thenReturn指定某个实例,从而控制测试分支。
对比而言,单纯用mock忽略了Reference本身的回收语义,适合测试“拿到引用后如何处理”的逻辑;而前述基于ReferenceQueue的真实回收测试,则适合验证“架构上是否产生了不必要的强持有”。两者目的不同,不应互相替代。在CI流水线中,建议将回收类测试标记为中等优先级,并允许一定重试,因为GC在资源受限的构建机上偶尔延迟。
下表归纳了三种验证策略的差异:
| 策略 | 稳定性 | 适用场景 |
|---|---|---|
| 直接断言get为null | 低 | 快速但不推荐 |
| ReferenceQueue轮询 | 高 | 验证回收发生 |
| Mockito模拟引用 | 高 | 验证消费逻辑 |
综合来看,Android参考测试的核心在于分清“引用类型语义”和“回收时机不可控”这两件事。用对工具与方法,测试既能守护架构又能稳定通过。