fix: prevent Android ANR by deferring video player creation in NFT views#9988
fix: prevent Android ANR by deferring video player creation in NFT views#9988huhuanming wants to merge 10 commits intoxfrom
Conversation
- 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
Pull request was converted to draft
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
| 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); |
There was a problem hiding this comment.
🔴 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
VideoorFallbackstate if the previous NFT failed image loading, skipping theImagestate entirely. - Expected: Each NFT should always try loading as an
Imagefirst, 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]);
Was this helpful? React with 👍 or 👎 to provide feedback.
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.
Summary
Imagefirst, fallback toVideoon error, thenUnSupportedImageContainer. Previously every NFT detail created an ExoPlayer instance upfront.Image → Video → Fallbackchain. Previously every list item created an ExoPlayer instance, causing N concurrent ExoPlayer cleanups that block the main thread via synchronous Binder IPC (AudioManager.abandonAudioFocus).Root Cause
The Android ANR was caused by
ReactExoplayerView.cleanUpResources()callingAudioManager.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