[Android][Fabric] A decoded local image can miss the current frame during a long mount

Đang mở
#59,158 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 2 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
38/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
android, react-native
Lĩnh vực
mobile, performance

Hướng nghiên cứu

Start by tracing the named delivery path from AbstractDraweeController.submitRequest through DataSource.subscribe to SettableDraweeHierarchy.setImage, then reproduce on Android with the linked RNTester example. Compare a cold-cache miss with the synchronous cache-hit case while Fabric is mounting. Done means a result ready before drawing appears in that frame rather than a later one.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Description

On Android with Fabric, a local Image that misses Fresco's bitmap memory cache can be drawn with its placeholder even when its decoded result is ready before that frame is drawn. The image is applied one or more frames later.

The reproducer mounts two Image components with the same local source in one commit:

  • Image A uses cache: 'reload', which evicts that URI before requesting it.
  • A small but nested vote-list subtree is mounted between A and B.
  • A is decoded on a background thread while the UI thread is processing the mount.
  • Image B is submitted later and synchronously hits the bitmap that A has placed in the memory cache.

The first visible frame can therefore contain A's gray placeholder and B's image, even though both use the same source.

The delivery path for A is:

AbstractDraweeController.submitRequest
→ DataSource.subscribe(subscriber, uiThreadExecutor)
→ a Handler message posted to the UI thread
→ SettableDraweeHierarchy.setImage

When the result becomes ready while the UI thread is already inside a long Choreographer.doFrame, the posted message cannot run between Fabric's mount work and traversal/draw because they execute inside the same frame message. The frame is drawn with the placeholder. If another overdue frame starts before the posted result message, the delay is amplified by that frame as well.

sequenceDiagram
    participant UI as UI thread
    participant BG as Fresco background thread
    UI->>BG: Submit A, bitmap memory-cache miss
    UI->>UI: Continue Fabric mount work
    BG-->>UI: Decode A, post result Handler message
    UI->>UI: Submit B, bitmap memory-cache hit
    UI->>UI: Draw frame with A placeholder and B image
    UI->>UI: Run A result message after the frame
    UI->>UI: Draw A image in a later frame

This is intermittent in applications because a bitmap memory-cache hit is delivered synchronously and avoids the problem. A miss occurs on first use, after eviction or cache trimming, and when an app clears Fresco's memory cache. The reproducer uses cache: 'reload' only to make A start cold on every run.

On React Native 0.88.0, screen recording captured the A-placeholder/B-image frame in 10 of 10 runs with the final reproducer on a Vivo V2338A running Android 16 (API 36). The measured B-to-A display delays were:

78, 67, 67, 62, 10, 75, 77, 74, 68, 75 ms

Median: approximately 71 ms. Nine of the ten runs lasted 62–78 ms.

In a production app, the same pattern occurs with a local file:// button image during the first commit of a screen and remains visible for approximately 19–60 ms.

Expected behavior: if an image result is ready before a frame is drawn, that frame should draw the image, matching the behavior of a synchronous bitmap memory-cache hit.

Steps to reproduce

  1. Check out React Native 0.88-stable at 99d4ed39851392b4c65fd38fe3812669cb493376.

  2. Replace packages/rn-tester/js/examples/Playground/RNTesterPlayground.js with the linked reproducer.

  3. Build and install the Android release variant so that the JavaScript bundle and local assets are packaged in the APK:

    ./gradlew --no-daemon --max-workers=1 :packages:rn-tester:android:app:assembleRelease
    adb install -r packages/rn-tester/android/app/build/outputs/apk/release/app-arm64-v8a-release.apk
    
  4. Open the RNTester Playground:

    adb shell "am start -a android.intent.action.VIEW -d 'rntester://example/PlaygroundExample' com.facebook.react.uiapp"
    
  5. After approximately one second, observe the first frame containing A and B. A shows its gray placeholder while B already shows the same image.

  6. Tap Run again to repeat.

The first commit inserts image A, four nested vote-list rows with real local images, and image B. A second commit adds 20 more rows. The list is scrollable.

React Native Version

0.88.0

Tested from 0.88-stable commit:

99d4ed39851392b4c65fd38fe3812669cb493376

The same underlying behavior was also observed on main at:

b8ec87e457c72377dab85599e1ae8c176eb242e7

Affected Platforms

Select:

Runtime - Android

Areas

Select:

Fabric - The New Renderer

Output of npx @react-native-community/cli info

The command was run from the RNTester monorepo checkout. The CLI does not infer RNTester's workspace packages or Gradle architecture setting correctly, so it reports package entries as Not Found and newArchEnabled: false. The actual RNTester Android configuration used for the reproduction has newArchEnabled=true.

System:
  OS: macOS 27.0.1
  CPU: (10) arm64 Apple M5
  Memory: 64.22 MB / 24.00 GB
  Shell:
    version: 4.9.3
    path: /opt/homebrew/bin/fish
Binaries:
  Node:
    version: 22.22.3
    path: ~/.local/state/fnm_multishells/34159_1791960164938/bin/node
  Yarn:
    version: 1.22.22
    path: ~/.local/state/fnm_multishells/34159_1791960164938/bin/yarn
  npm:
    version: 10.9.8
    path: ~/.local/state/fnm_multishells/34159_1791960164938/bin/npm
  Watchman:
    version: 2026.07.27.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/admin/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 27.0
      - iOS 27.0
      - macOS 27.0
      - tvOS 27.0
      - visionOS 27.0
      - watchOS 27.0
  Android SDK: Not Found
IDEs:
  Android Studio: 2026.2 AI-262.9437.185.2621.16467767
  Xcode:
    version: 27.0/27A266a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.20.1
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: false
  newArchEnabled: false
iOS:
  hermesEnabled: true
  newArchEnabled: false

Stacktrace or Logs

No crash or JavaScript error is logged.

Final RNTesterPlayground reproduction on React Native 0.88.0:
10/10 screen recordings captured A's placeholder while B already showed the image.
B-to-A display delays:
78, 67, 67, 62, 10, 75, 77, 74, 68, 75 ms
Median: approximately 71 ms; 9/10 runs lasted 62–78 ms.

MANDATORY Reproducer

https://github.com/fairysword/react-native/blob/9bc67419dfbde083cf258af46aa27f504836034c/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js

Screenshots

Image
Ngôn ngữ chính
C++
Star
127k
Fork
25.5k
Merge trung bình
3 ngày 18 giờ
Pull request đã merge (30 ngày)
188

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của react/react-native

Tất cả issue của react/react-native

Issue tương tự

Thêm issue về C++

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.