Repository navigation
Commit 96c9247
authored
Skip the unchanged scroll state update when a ScrollView mounts on Android
## Summary:
On Android, every `ScrollView` commits one state update on mount that changes nothing. `onLayout` calls `scrollTo`, which calls `ReactScrollViewHelper.updateFabricScrollState`. That method dedupes against `lastStateUpdateScroll`, but this starts at `(-1, -1)`, so the first offset always gets through, even when it's the `(0, 0)` already in the state. The update then runs a full commit on the JS thread (clone, layout and differ) and sends an `UpdateState` item back to Java. On iOS, `_updateStateWithContentOffset` already skips an update when the offset hasn't changed.
Now, before a view's first update, `updateFabricScrollState` reads the offset from the state into `lastStateUpdateScroll`, and the existing dedupe does the rest. This costs one state read per view, not one per scroll frame.
I didn't start `lastStateUpdateScroll` at `(0, 0)`, because a view created for a state that already holds another offset would then skip the update that corrects the state.
## Changelog:
[ANDROID] [CHANGED] - `ScrollView` no longer commits an unchanged scroll state when it mounts
## Test Plan:
Unit tests: the new `ReactScrollViewHelperStateTest` covers a first update that matches the state (not sent), one that differs from it (sent), one without state data (sent), and later updates (sent once per offset, with one state read). The two tests that expect a skipped update fail without this change.
```
./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests 'com.facebook.react.views.scroll.*'
```
25 tests, 0 failures. `yarn format-kotlin` makes no further changes.
Behavior: on a Galaxy A16, a temporary Playground screen read a marker's position with `measureInWindow`, and both builds reported the same positions at every step:
- after mount with `contentOffset`, vertical and horizontal
- after `scrollTo`, vertical and horizontal
- after scrolling back to 0
- after toggling `display: none` on the parent
Measurements on a Galaxy A16 (Android 16): two release RNTester APKs built from this commit's base, one with and one without this change, with default feature flags, compiled with `cmd package compile -m speed`. A temporary Playground screen mounts and unmounts the scenario 12 times. JS thread CPU comes from `/proc/<pid>/task/<tid>/stat`. The APKs were installed in alternating blocks, A/B/B/A twice, which gives 4 fresh processes per arm for each scenario. The table shows medians per mount and unmount:
| Scenario | Without this change | With this change | Change |
|---|--:|--:|--:|
| 100 `ScrollView`s, per `ScrollView` | 1050 µs | 554 µs | −47% |
| Vertical `ScrollView` with 5 horizontal ones and 40 cards | 86.6 ms | 82.5 ms | −4.8% |
| One `ScrollView` with 300 cards | 307.0 ms | 306.2 ms | no change |
| `FlatList` with 40 cards | 92.0 ms | 92.9 ms | no change |
That's about 0.5–0.7 ms of JS time per `ScrollView` on this phone, so it only shows on screens with several of them. The 100-`ScrollView` case is artificial and shows the cost of each commit. In the second scenario the ranges overlap slightly (85.0–88.3 ms and 79.2–85.8 ms). UI thread time didn't change in any scenario.
Not tested on a device: `enableViewRecyclingForScrollView` and `enableViewCulling`. The `display: none` toggle may not recreate the view, so the case of a state that already holds another offset is covered only by the unit test. With `enableViewCulling` or `useTraitHiddenOnAndroid` on, `loadFabricScrollState` already sets `lastStateUpdateScroll`, so the new read doesn't run after that.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Differential Revision: D129045292
Pull Request resolved: #597191 parent 3b3fce3 commit 96c9247
2 files changed
Lines changed: 132 additions & 1 deletion
File tree
- packages/react-native/ReactAndroid/src
- main/java/com/facebook/react/views/scroll
- test/java/com/facebook/react/views/scroll
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| |||
373 | 374 | | |
374 | 375 | | |
375 | 376 | | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
376 | 388 | | |
377 | 389 | | |
378 | 390 | | |
| |||
660 | 672 | | |
661 | 673 | | |
662 | 674 | | |
663 | | - | |
| 675 | + | |
664 | 676 | | |
665 | 677 | | |
666 | 678 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
0 commit comments