You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 04c288e
Browse filesBrowse the repository at this point in the historyBrowse files
Build Android touch event payloads in one JNI call (#59554)
Summary:
On Android, every touch event goes through `TouchesHelper.sendTouchEvent`, which builds the payload JS receives as nested `WritableNativeMap`s.
**Current behavior.** For each pointer of the `MotionEvent`, `createPointersArray` creates a native map and puts eight values into it, one JNI call each. Then, for each changed pointer, the payload is assembled from copies of those maps: the event itself is a copy of the changed pointer, and `changedTouches` and `touches` are arrays of further copies (`copy()`, `pushMap()`, `putArray()`, each one or more JNI calls). With one finger moving, that is about 20–25 JNI calls and 8 native-backed objects for every `ACTION_MOVE`.
**With this change.** `TouchesHelper` collects the pointer values once into a `DoubleArray`. For each event it creates an empty `WritableNativeMap` and passes it with the pointer data to a new native method, `TouchesHelper.nativePutTouchPayload`, which fills it in C++ through the map's existing `putDouble`, `putInt` and `putNativeArray`. That is two JNI calls per event (creating the map and filling it) instead of 20–25. `TouchesHelper` still decides which pointers go into `changedTouches` and `touches`, now as bit masks of pointer indices (start: the new pointer / all; end: the lifted pointer / all but it; move: all / all; cancel: all / none). The keys, values and value types of the payload are the same as before, and the events are dispatched exactly as before.
`WritableNativeMap` is unchanged. The native method is registered like `TransformHelper`'s, from `TouchesHelper.cpp` in `reactnativejni_common`, and `TouchesHelper` is `DoNotStrip` because the class is looked up by name when the library loads. `TouchesHelper` is `internal`, so this adds no public Kotlin API; the C++ API snapshots get the new `TouchesHelper` class. The layout of the pointer data (eight values per pointer) is documented on `nativePutTouchPayload`, and the C++ side and the test shadow point to it.
## Changelog:
[ANDROID] [CHANGED] - Build touch event payloads in native code, making each touch event cheaper on the main thread
Pull Request resolved: #59554
Test Plan:
- `TouchEventDispatchTest` passes. It checks the payloads of the start/move/end, start/move/cancel and multi-pointer start/pointer-up/cancel sequences, now through `ShadowTouchesHelper`, a Kotlin version of `nativePutTouchPayload`, and compares the map contents. With the end case deliberately broken (lifted pointer kept in `touches`), 2 of its 3 tests fail.
- `ktfmt` and the repository's `clang-format` report no changes for the touched files. The C++ API snapshots were regenerated with `scripts/cxx-api`.
- `./gradlew :packages:rn-tester:android:app:assembleRelease -PreactNativeArchitectures=arm64-v8a` succeeds, from `main` at 01d5527.
- **Same payloads.** A local self-test (not part of this PR) built synthetic `MotionEvent`s for 1 to 5 pointers, a pointer going down and up at every index, a move and a cancel, ran them through the previous implementation and through the first version of this PR, and compared the payloads with `toHashMap()`: 40 events, 60 payloads, no differences, on a moto g57 power. The current version builds the payload with the same C++ code; only the way the result is put into the map changed.
- **Release builds of this PR and of `main`.** Two separate RNTester release APKs (this PR, and `main` at 01d5527), RNTester Playground screen with a `PanResponder` drag area, 36 drags of 1.8 s per launch driven by `adb shell input swipe` (about 4,200 touch events per launch), installs alternated, moto g57 power (Android 16). On-CPU time of the main and JS threads during the drags from `/proc`, trace sections from Perfetto:
| | `main` | this PR | change |
|---|--:|--:|--:|
| Main thread, 36 drags | 9.07 / 8.75 / 8.99 s | 8.22 / 8.12 / 8.15 s | −8.7% |
| JS thread, 36 drags | 5.01 / 4.88 / 5.05 s | 4.97 / 4.92 / 5.00 s | no change |
| `TouchesHelper.sentTouchEventModern(topTouchMove)` trace section, ~700 events | 449 µs | 309 µs | −31% |
| Native heap after the drags (`dumpsys meminfo`) | 44.3 / 42.5 / 42.4 MB | 33.2 / 33.3 / 33.1 MB | −10 MB |
The trace-section times include the tracing overhead. `FabricEventEmitter.receiveEvent('topTouchMove')` is about 11–17 µs slower with this PR (95 → 111 µs), which is small next to the saving in `sentTouchEventModern`. In every run JS received all touch moves, 10 out of 10 taps on `Pressable` rows were handled, and there were no errors in logcat.
- Not run: a C++ unit test of `nativePutTouchPayload` (JVM tests can't load the native library; it is covered on the device by the runs above and, through the shadow, by `TouchEventDispatchTest`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Internal Buck validation
- `buck build fbsource//xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/jni/react/jni:jniAndroid fbsource//xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events:events-commonAndroid fbsource//xplat/js/react-native-github/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows:shadowsAndroid` — passed.
- `buck test fbsource//xplat/js/react-native-github/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events:eventsAndroid -- TouchEventDispatchTest` — 4 passed, 0 failed. Test run: https://www.internalfb.com/intern/testinfra/testrun/14355224025898138
- `arc lint -a` — passed.
- `arc lint -e extra --take CLANGTIDY --never-apply-patches xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp` — no lint issues.
- Repository-managed `clang-format --dry-run --Werror` on `TouchesHelper.cpp` — passed.
- Compared the eight Kotlin/C++ field-index constants and the pointer stride; all match, and the producer, native readers and shadow use named indices.
Reviewed By: christophpurrer
Differential Revision: D128055442
Pulled By: javache
0 commit comments