Skip to content

fix: prevent Android ANR by deferring video player creation in NFT views#9988

Draft
huhuanming wants to merge 10 commits intoxfrom
fix/android-anr-nft-exoplayer-cleanup
Draft

fix: prevent Android ANR by deferring video player creation in NFT views#9988
huhuanming wants to merge 10 commits intoxfrom
fix/android-anr-nft-exoplayer-cleanup

Conversation

@huhuanming
Copy link
Copy Markdown
Contributor

@huhuanming huhuanming commented Feb 3, 2026

Summary

  • CommonAssetImage: Reverse media loading order — default to Image first, fallback to Video on error, then UnSupportedImageContainer. Previously every NFT detail created an ExoPlayer instance upfront.
  • NFTListItem: Apply same Image → Video → Fallback chain. Previously every list item created an ExoPlayer instance, causing N concurrent ExoPlayer cleanups that block the main thread via synchronous Binder IPC (AudioManager.abandonAudioFocus).
  • NFTListContainer: Defer rendering until the NFT tab is first focused, avoiding unnecessary ExoPlayer/network overhead on app startup.

Root Cause

The Android ANR was caused by ReactExoplayerView.cleanUpResources() calling AudioManager.abandonAudioFocus() synchronously on the main thread via Binder IPC. When many ExoPlayer instances were cleaned up during view recycling (e.g. returning to TabHome), the Binder thread pool became saturated (373 threads observed), causing the main thread to hang indefinitely.

Test Plan

  • Open NFT tab on Android — verify images load correctly
  • Verify video NFTs still play in NFT detail view
  • Verify NFT tab content only loads on first tab focus
  • Scroll through large NFT list on Android without ANR
  • Test on low-end Android device if available

Open with Devin

- CommonAssetImage: default to Image first, fallback to Video on error, then UnSupportedImageContainer
- NFTListItem: same Image→Video→Fallback chain instead of creating ExoPlayer upfront
- NFTListContainer: defer rendering until NFT tab is first focused
@huhuanming huhuanming enabled auto-merge (squash) February 3, 2026 17:58
@huhuanming huhuanming marked this pull request as draft February 3, 2026 17:59
auto-merge was automatically disabled February 3, 2026 17:59

Pull request was converted to draft

@revan-zhang
Copy link
Copy Markdown
Contributor

revan-zhang commented Feb 3, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View issue and 5 additional flags in Devin Review.

Open in Devin Review

const { nft, onPress, isAllNetworks } = props;
const [isVideo, setIsVideo] = useState<boolean>(!!nft.metadata?.image);
const { network } = useAccountData({ networkId: nft.networkId });
const [mediaState, setMediaState] = useState<EMediaState>(EMediaState.Image);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 mediaState not reset when NFT prop changes in recycled list cells

The mediaState is not reset when the nft prop changes, causing issues with FlatList cell recycling.

Click to expand

Problem

In NFTListItem, mediaState is initialized to EMediaState.Image but never reset when the nft prop changes:

const [mediaState, setMediaState] = useState<EMediaState>(EMediaState.Image);

When scrolling through a virtualized list, FlatList recycles cells. If a cell previously displayed an NFT whose image failed to load (transitioning mediaState to Video or Fallback), and that cell is recycled to display a different NFT, the mediaState remains in its previous state.

Actual vs Expected

  • Actual: A recycled cell showing a new NFT will start in Video or Fallback state if the previous NFT failed image loading, skipping the Image state entirely.
  • Expected: Each NFT should always try loading as an Image first, regardless of the cell's previous state.

Impact

This defeats the purpose of the PR (preventing ExoPlayer creation by trying Image first). Some valid images will unnecessarily create Video player instances when their cell was previously used by a failed NFT, potentially contributing to the ANR issue this PR aims to fix.

Recommended Fix

Add a useEffect to reset mediaState when the NFT image URL changes:

useEffect(() => {
  setMediaState(EMediaState.Image);
}, [nft.metadata?.image]);

Recommendation: Add a useEffect to reset mediaState when nft.metadata?.image changes: useEffect(() => { setMediaState(EMediaState.Image); }, [nft.metadata?.image]);

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

huhuanming and others added 9 commits February 4, 2026 10:13
Add nil check for traitType in attributes rendering to prevent
Fabric renderer from calling .toString() on undefined, which
crashes the NFT detail page for attributes missing trait_type.
Add LRU-like size limit (500) to mediaStateCache to prevent unbounded
memory growth, skip directly to Fallback when imageUri is undefined,
and use index-based keys for NFT attributes to avoid duplicate key
warnings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants