Repository navigation
Conversation
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: react#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
|
@javache has exported this pull request. If you are a Meta employee, you can view the originating Diff in D128208007. |
Summary: Pull Request resolved: react#59595 Widen Android event payload contracts from `WritableMap` to `ReadableMap` across the event emitters, UI managers and Fabric dispatch pipeline, including `Event.getEventData`. Existing event producers can continue returning writable maps. Build native touch payloads as a complete `folly::dynamic` and return them through `ReadableNativeMap::createWithContents` without constructing and populating a writable Java map. Document Fabric’s native-backed, consumed-payload contract, update the touch test shadow to return a read-only native map and regenerate the Android API snapshot. Changelog: [Android][Breaking] - Change Android event payload APIs from `WritableMap` to `ReadableMap`; update custom emitter and UI manager implementations to match. Differential Revision: D128208007
javache
force-pushed
the
export-D128208007
branch
from
October 29, 2026 00:23
965141e to
f8ea32a
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Widen Android event payload contracts from
WritableMaptoReadableMapacross the event emitters, UI managers and Fabric dispatch pipeline, includingEvent.getEventData. Existing event producers can continue returning writable maps. Build native touch payloads as a completefolly::dynamicand return them throughReadableNativeMap::createWithContentswithout constructing and populating a writable Java map.Document Fabric’s native-backed, consumed-payload contract, update the touch test shadow to return a read-only native map and regenerate the Android API snapshot.
Changelog:
[Android][Breaking] - Change Android event payload APIs from
WritableMaptoReadableMap; update custom emitter and UI manager implementations to match.Differential Revision: D128208007