Skip to content

Commit

Permalink
Swallow exceptions on draw, forward to onError (#46964)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #46964

Graceful degradation is better than outright crashing. When rendering images that exceed Android's memory limits, React Native applications will fatally crash. This change intervenes by swallowing the exception that Android raises and forwards it to the `onError` handler. As a result, no image will be rendered instead of fatally crashing.

Fresco already intervenes by default. It will raise a `PoolSizeViolationException` if the bitmap size exceeds memory limits set by the OS and applies a 2048 pixel maximum dimension to JPEG images, but it's still possible for these configuration limits to be removed and for images to fall just short of Fresco's memory limit. The exception is raised when we attempt to draw the bitmap, not when the bitmap is allocated in memory, so we must handle the exception here and not in Fresco.

## Changelog

[Android][Fixed] - Apps will no longer fatally crash when trying to draw large images

Reviewed By: tdn120

Differential Revision: D64144596

fbshipit-source-id: 32b69ad4ecef0564c2cad7a287a31b56688f38b8
  • Loading branch information
Abbondanzo authored and facebook-github-bot committed Oct 11, 2024
1 parent 44fe064 commit 483b928
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,16 @@ public class ReactImageView(

public override fun onDraw(canvas: Canvas) {
BackgroundStyleApplicator.clipToPaddingBox(this, canvas)
super.onDraw(canvas)
try {
super.onDraw(canvas)
} catch (e: RuntimeException) {
// Only provide updates if downloadListener is set (shouldNotify is true)
if (downloadListener != null) {
val eventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id)
eventDispatcher?.dispatchEvent(createErrorEvent(UIManagerHelper.getSurfaceId(this), id, e))
}
}
}

public fun maybeUpdateView() {
Expand Down
Binary file added packages/rn-tester/js/assets/very-large-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 15 additions & 6 deletions packages/rn-tester/js/examples/Image/ImageExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

'use strict';

import type {ImageProps} from 'react-native/Libraries/Image/ImageProps';
import type {LayoutEvent} from 'react-native/Libraries/Types/CoreEventTypes';

import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
Expand Down Expand Up @@ -228,12 +229,8 @@ type NetworkImageExampleState = {|
progress: $ReadOnlyArray<number>,
|};

type NetworkImageExampleProps = $ReadOnly<{|
source: ImageSource,
|}>;

class NetworkImageExample extends React.Component<
NetworkImageExampleProps,
ImageProps,
NetworkImageExampleState,
> {
state: NetworkImageExampleState = {
Expand All @@ -248,7 +245,7 @@ class NetworkImageExample extends React.Component<
) : (
<>
<Image
source={this.props.source}
{...this.props}
style={[styles.base, styles.visibleOverflow]}
onLoadStart={e => this.setState({loading: true})}
onError={e =>
Expand Down Expand Up @@ -970,6 +967,18 @@ exports.examples = [
);
},
},
{
title: 'Error Handler for Large Images',
render: function (): React.Node {
return (
<NetworkImageExample
resizeMethod="none"
// 6000x5340 ~ 128 MB
source={require('../../assets/very-large-image.png')}
/>
);
},
},
{
title: 'Image Download Progress',
render: function (): React.Node {
Expand Down

0 comments on commit 483b928

Please sign in to comment.