Problem
ViewUtils.getResourceId used exception-driven control flow: it threw Resources.NotFoundException for views with View.NO_ID or a generated id, and every caller caught and discarded it. This runs per view during view-hierarchy snapshots and gesture target resolution, so in Compose-heavy apps — where most views have generated ids — the SDK constructs an exception (plus a native fillInStackTrace stack walk) for nearly every view, on the main thread.
Found while analyzing this project's showcase Perfetto trace (sentryinvest): in a single view-hierarchy snapshot, 24 of 72 getResourceId calls threw Resources.NotFoundException on the main thread — pure overhead with no functional result.
Fix
Add a non-throwing ViewUtils.resolveResourceId(View) that returns null for unresolved ids, and route the hot callers (ViewHierarchyEventProcessor.viewToNode, AndroidViewGestureTargetLocator.createUiElement, getResourceIdWithFallback) through it. The public getResourceId(...) throws Resources.NotFoundException stays as a thin wrapper for backward compatibility. Emitted identifiers and fallbacks are unchanged.
On-device verification
Pixel 3 / Android 12, ART method tracing analyzed in Perfetto trace_processor, 2048 calls over NO_ID views:
|
old (getResourceId) |
new (resolveResourceId) |
Resources$NotFoundException.<init> |
2048 |
0 |
Throwable.fillInStackTrace |
2048 |
0 |
| total methods executed |
30,757 |
12,324 |
| inclusive time / call (traced) |
30,470 ns |
6,467 ns |
One exception (+ native stack fill) per unresolved view removed; ~60% fewer executed methods, ~4.7× cheaper per unresolved view (absolute ns inflated by tracing — the counts and ratio are the reliable signal).
PR
#5631
Related follow-ups (from the same trace, not in this PR)
- Speed up
ViewUtils.findTarget (per-touch LinkedList → ArrayDeque; cache resource-name lookups).
- Background JSON serialization cost in the vendored Gson
JsonWriter.
Problem
ViewUtils.getResourceIdused exception-driven control flow: it threwResources.NotFoundExceptionfor views withView.NO_IDor a generated id, and every caller caught and discarded it. This runs per view during view-hierarchy snapshots and gesture target resolution, so in Compose-heavy apps — where most views have generated ids — the SDK constructs an exception (plus a nativefillInStackTracestack walk) for nearly every view, on the main thread.Found while analyzing this project's showcase Perfetto trace (
sentryinvest): in a single view-hierarchy snapshot, 24 of 72getResourceIdcalls threwResources.NotFoundExceptionon the main thread — pure overhead with no functional result.Fix
Add a non-throwing
ViewUtils.resolveResourceId(View)that returnsnullfor unresolved ids, and route the hot callers (ViewHierarchyEventProcessor.viewToNode,AndroidViewGestureTargetLocator.createUiElement,getResourceIdWithFallback) through it. The publicgetResourceId(...) throws Resources.NotFoundExceptionstays as a thin wrapper for backward compatibility. Emitted identifiers and fallbacks are unchanged.On-device verification
Pixel 3 / Android 12, ART method tracing analyzed in Perfetto
trace_processor, 2048 calls overNO_IDviews:getResourceId)resolveResourceId)Resources$NotFoundException.<init>Throwable.fillInStackTraceOne exception (+ native stack fill) per unresolved view removed; ~60% fewer executed methods, ~4.7× cheaper per unresolved view (absolute ns inflated by tracing — the counts and ratio are the reliable signal).
PR
#5631
Related follow-ups (from the same trace, not in this PR)
ViewUtils.findTarget(per-touchLinkedList→ArrayDeque; cache resource-name lookups).JsonWriter.