Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@

- Disable URL caching when reading `META-INF/MANIFEST.MF` files during version detection so that the SDK no longer keeps jar file handles open for the life of the process ([#6124](https://github.com/getsentry/sentry-java/pull/6124)
- Keep the `EventListener` wrapped by `SentryOkHttpEventListener` per `Call` ([#6003](https://github.com/getsentry/sentry-java/pull/6003))
- Always remove the first-draw listener after the first frame, even if no layout pass follows it ([#6152](https://github.com/getsentry/sentry-java/pull/6152))

## 8.57.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,15 @@ public void onDraw() {
if (view == null) {
return;
}
// OnDrawListeners cannot be removed within onDraw, so we remove it with a
// GlobalLayoutListener
view.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
view.getViewTreeObserver().removeOnDrawListener(FirstDrawDoneListener.this);
}
});
// OnDrawListeners cannot be removed within onDraw. Posting the removal instead of waiting for
// the next global layout guarantees it happens, even if no layout pass follows the draw.
mainThreadHandler.post(
() -> {
final ViewTreeObserver observer = view.getViewTreeObserver();
if (observer.isAlive()) {
observer.removeOnDrawListener(this);
}
});
mainThreadHandler.postAtFrontOfQueue(callback);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,49 +113,14 @@ class FirstDrawDoneListenerTest {
}

@Test
fun `registerForNextDraw unregister itself after onDraw`() {
fun `registerForNextDraw unregisters itself after onDraw without a layout pass`() {
val view = fixture.getSut()
FirstDrawDoneListener.registerForNextDraw(view, {}, fixture.buildInfo)
assertFalse(fixture.onDrawListeners.isEmpty())

// Does not remove OnDrawListener before onDraw, even if OnGlobalLayout is triggered
view.viewTreeObserver.dispatchOnGlobalLayout()
assertFalse(fixture.onDrawListeners.isEmpty())

// Removes OnDrawListener in the next OnGlobalLayout after onDraw
view.viewTreeObserver.dispatchOnDraw()
view.viewTreeObserver.dispatchOnGlobalLayout()
assertTrue(fixture.onDrawListeners.isEmpty())
}

@Test
fun `OnGlobalLayoutListener is removed after cleanup`() {
val view = fixture.getSut()

// Initialize mOnGlobalLayoutListeners via a dummy add/remove
val dummyGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {}
view.viewTreeObserver.addOnGlobalLayoutListener(dummyGlobalListener)
view.viewTreeObserver.removeOnGlobalLayoutListener(dummyGlobalListener)

// CopyOnWriteArray wraps an internal ArrayList called mData
val copyOnWriteArray: Any = view.viewTreeObserver.getProperty("mOnGlobalLayoutListeners")
val mDataField = copyOnWriteArray.javaClass.getDeclaredField("mData")
mDataField.isAccessible = true

@Suppress("UNCHECKED_CAST")
fun globalLayoutListeners(): ArrayList<*> = mDataField.get(copyOnWriteArray) as ArrayList<*>

assertTrue(globalLayoutListeners().isEmpty())

FirstDrawDoneListener.registerForNextDraw(view, {}, fixture.buildInfo)

// onDraw registers a cleanup OnGlobalLayoutListener
view.viewTreeObserver.dispatchOnDraw()
assertFalse(globalLayoutListeners().isEmpty())
Shadows.shadowOf(Looper.getMainLooper()).idle()

// onGlobalLayout fires the cleanup, which removes both the draw and layout listeners
view.viewTreeObserver.dispatchOnGlobalLayout()
assertTrue(globalLayoutListeners().isEmpty())
assertTrue(fixture.onDrawListeners.isEmpty())
}

Expand Down
Loading