ActivityFragmentLeakInstanceFilter implements CaptureObjectInstanceFilter { @Override public Set<InstanceObject> filter(@NotNull Set<InstanceObject> instances, @NotNull ClassDb classDatabase) { Set<ClassDb.ClassEntry> allActivitySubclasses = classDatabase.getEntriesByName(ACTIVTY_CLASS_NAME).stream() .flatMap(classEntry -> classDatabase.getDescendantClasses(classEntry.getClassId()).stream()) .collect(Collectors.toSet()); Set<ClassDb.ClassEntry> allFragmentSubclasses = FRAGMENT_CLASS_NAMES.stream() .flatMap(className -> classDatabase.getEntriesByName(className).stream()) .flatMap(classEntry -> classDatabase.getDescendantClasses(classEntry.getClassId()).stream()) .collect(Collectors.toSet()); return instances.stream().filter(instance -> { if (allActivitySubclasses.contains(instance.getClassEntry()) && isPotentialActivityLeak(instance)) { return true; } else if (allFragmentSubclasses.contains(instance.getClassEntry()) && isPotentialFragmentLeak(instance)) { return true; } return false; }).collect(Collectors.toSet()); }  @NotNull @Override String getDisplayName(); @NotNull @Override String getSummaryDescription(); @Nullable @Override String getDetailedDescription(); @Nullable @Override String getDocumentationLink(); @Override Set<InstanceObject> filter(@NotNull Set<InstanceObject> instances, @NotNull ClassDb classDatabase);  }
@Test public void testActivityLeaks() { long baseClassActivityId = 1; long subClassActivityId = 2; long unrelatedClassId = 3; FakeCaptureObject capture = new FakeCaptureObject.Builder().build(); FakeInstanceObject leakingBaseClassActivity = new FakeInstanceObject.Builder(capture, baseClassActivityId, ActivityFragmentLeakInstanceFilter.ACTIVTY_CLASS_NAME) .addField(ActivityFragmentLeakInstanceFilter.FINISHED_FIELD_NAME, ValueObject.ValueType.BOOLEAN, true) .setDepth(1) .build(); FakeInstanceObject nonLeakingBaseClassActivity = new FakeInstanceObject.Builder(capture, baseClassActivityId, ActivityFragmentLeakInstanceFilter.ACTIVTY_CLASS_NAME) .addField("blah", ValueObject.ValueType.BOOLEAN, true) .build(); FakeInstanceObject leakingSubClassActivity = new FakeInstanceObject.Builder(capture, subClassActivityId, "my.activity.subclass") .setSuperClassId(baseClassActivityId) .addField(ActivityFragmentLeakInstanceFilter.DESTROYED_FIELD_NAME, ValueObject.ValueType.BOOLEAN, true) .setDepth(1) .build(); FakeInstanceObject nonLeakingSubClassActivity1 = new FakeInstanceObject.Builder(capture, subClassActivityId, "my.activity.subclass") .setSuperClassId(baseClassActivityId) .addField("blah", ValueObject.ValueType.BOOLEAN, true) .build(); FakeInstanceObject nonLeakingSubClassActivity2 = new FakeInstanceObject.Builder(capture, subClassActivityId, "my.activity.subclass") .setSuperClassId(baseClassActivityId) .addField(ActivityFragmentLeakInstanceFilter.DESTROYED_FIELD_NAME, ValueObject.ValueType.BOOLEAN, true) .build(); FakeInstanceObject unrelatedClassInstance = new FakeInstanceObject.Builder(capture, unrelatedClassId, "my.other.class") .addField(ActivityFragmentLeakInstanceFilter.DESTROYED_FIELD_NAME, ValueObject.ValueType.BOOLEAN, true) .build(); Set<InstanceObject> instances = ImmutableSet.of(leakingBaseClassActivity, nonLeakingBaseClassActivity, leakingSubClassActivity, nonLeakingSubClassActivity1, nonLeakingSubClassActivity2, unrelatedClassInstance); ActivityFragmentLeakInstanceFilter filter = new ActivityFragmentLeakInstanceFilter(); Set<InstanceObject> result = filter.filter(instances, capture.getClassDatabase()); assertThat(result).containsExactly(leakingBaseClassActivity, leakingSubClassActivity); }
@Test public void testFragmentLeaks() { long baseClassNativeFragmentId = 1; long subClassNativeFragmentId = 2; long baseClassSupportFragmentId = 3; long baseClassAndroidxFragmentId = 5; FakeCaptureObject capture = new FakeCaptureObject.Builder().build(); FakeInstanceObject leakingNativeBaseNativeFragment = new FakeInstanceObject.Builder(capture, baseClassNativeFragmentId, ActivityFragmentLeakInstanceFilter.NATIVE_FRAGMENT_CLASS_NAME) .addField(ActivityFragmentLeakInstanceFilter.FRAGFMENT_MANAGER_FIELD_NAME, ValueObject.ValueType.OBJECT, null) .setDepth(1) .build(); FakeInstanceObject nonLeakingBaseNativeFragment = new FakeInstanceObject.Builder(capture, baseClassNativeFragmentId, ActivityFragmentLeakInstanceFilter.NATIVE_FRAGMENT_CLASS_NAME) .addField(ActivityFragmentLeakInstanceFilter.FRAGFMENT_MANAGER_FIELD_NAME, ValueObject.ValueType.OBJECT, null) .build(); FakeInstanceObject leakingSubClassNativeFragment = new FakeInstanceObject.Builder(capture, subClassNativeFragmentId, "my.native.fragment.subclass") .setSuperClassId(baseClassNativeFragmentId) .addField(ActivityFragmentLeakInstanceFilter.FRAGFMENT_MANAGER_FIELD_NAME, ValueObject.ValueType.OBJECT, null) .setDepth(1) .build(); FakeInstanceObject nonLeakingSubClassNativeFragment = new FakeInstanceObject.Builder(capture, subClassNativeFragmentId, "my.native.fragment.subclass") .setSuperClassId(baseClassNativeFragmentId) .addField(ActivityFragmentLeakInstanceFilter.FRAGFMENT_MANAGER_FIELD_NAME, ValueObject.ValueType.OBJECT, new Object()) .setDepth(1) .build(); FakeInstanceObject leakingSupportBaseNativeFragment = new FakeInstanceObject.Builder(capture, baseClassSupportFragmentId, ActivityFragmentLeakInstanceFilter.SUPPORT_FRAGMENT_CLASS_NAME) .addField(ActivityFragmentLeakInstanceFilter.FRAGFMENT_MANAGER_FIELD_NAME, ValueObject.ValueType.OBJECT, null) .setDepth(1) .build(); FakeInstanceObject nonleakingAndroidXBaseNativeFragment = new FakeInstanceObject.Builder(capture, baseClassAndroidxFragmentId, ActivityFragmentLeakInstanceFilter.ANDROIDX_FRAGMENT_CLASS_NAME) .build(); Set<InstanceObject> instances = ImmutableSet.of(leakingNativeBaseNativeFragment, nonLeakingBaseNativeFragment, leakingSubClassNativeFragment, nonLeakingSubClassNativeFragment, leakingSupportBaseNativeFragment, nonleakingAndroidXBaseNativeFragment); ActivityFragmentLeakInstanceFilter filter = new ActivityFragmentLeakInstanceFilter(); Set<InstanceObject> result = filter.filter(instances, capture.getClassDatabase()); assertThat(result).containsExactly(leakingNativeBaseNativeFragment, leakingSubClassNativeFragment, leakingSupportBaseNativeFragment); }