Skip to content

Commit fa044bf

Browse files
committed
test(perf): Add regression test for OnGlobalLayoutListener leak
1 parent 13415cf commit fa044bf

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

firebase-perf/src/test/java/com/google/firebase/perf/util/FirstDrawDoneListenerTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,27 @@ public void onDraw_unregistersItself_inLayoutChangeListener()
125125
assertThat(mOnDrawListeners.size()).isEqualTo(0);
126126
}
127127

128+
@Test
129+
@Config(sdk = 26)
130+
public void onDraw_removesOnGlobalLayoutListenerAfterCleanup()
131+
throws NoSuchFieldException, IllegalAccessException {
132+
ArrayList<OnDrawListener> mOnDrawListeners =
133+
initViewTreeObserverWithListener(testView.getViewTreeObserver());
134+
ArrayList<?> mOnGlobalLayoutListeners =
135+
initViewTreeObserverGlobalLayoutListeners(testView.getViewTreeObserver());
136+
137+
FirstDrawDoneListener.registerForNextDraw(testView, () -> {});
138+
139+
// onDraw registers a cleanup OnGlobalLayoutListener
140+
testView.getViewTreeObserver().dispatchOnDraw();
141+
assertThat(mOnGlobalLayoutListeners.size()).isGreaterThan(0);
142+
143+
// onGlobalLayout fires the cleanup, which removes both listeners
144+
testView.getViewTreeObserver().dispatchOnGlobalLayout();
145+
assertThat(mOnGlobalLayoutListeners).isEmpty();
146+
assertThat(mOnDrawListeners).isEmpty();
147+
}
148+
128149
/**
129150
* Returns ViewTreeObserver.mOnDrawListeners field through reflection. Since reflections are
130151
* employed, prefer to be used in tests with fixed API level using @Config(sdk = X).
@@ -148,6 +169,30 @@ private static ArrayList<OnDrawListener> initViewTreeObserverWithListener(ViewTr
148169
return listeners;
149170
}
150171

172+
/**
173+
* Returns the internal ArrayList backing ViewTreeObserver.mOnGlobalLayoutListeners through
174+
* reflection. The field is a CopyOnWriteArray, so we read its mData field.
175+
*/
176+
private static ArrayList<?> initViewTreeObserverGlobalLayoutListeners(ViewTreeObserver vto)
177+
throws NoSuchFieldException, IllegalAccessException {
178+
ViewTreeObserver.OnGlobalLayoutListener placeHolder = () -> {};
179+
vto.addOnGlobalLayoutListener(placeHolder);
180+
vto.removeOnGlobalLayoutListener(placeHolder);
181+
182+
Field mOnGlobalLayoutListeners =
183+
ViewTreeObserver.class.getDeclaredField("mOnGlobalLayoutListeners");
184+
mOnGlobalLayoutListeners.setAccessible(true);
185+
Object copyOnWriteArray = mOnGlobalLayoutListeners.get(vto);
186+
assertThat(copyOnWriteArray).isNotNull();
187+
188+
Field mData = copyOnWriteArray.getClass().getDeclaredField("mData");
189+
mData.setAccessible(true);
190+
ArrayList<?> listeners = (ArrayList<?>) mData.get(copyOnWriteArray);
191+
assertThat(listeners).isNotNull();
192+
assertThat(listeners).isEmpty();
193+
return listeners;
194+
}
195+
151196
/**
152197
* Simulates {@link View}'s dispatchAttachedToWindow() on API 25 using reflection.
153198
*

0 commit comments

Comments
 (0)