Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image does not load on Android when blob is bigger than 64kB #31774

Closed
tomekzaw opened this issue Jun 25, 2021 · 1 comment
Closed

Image does not load on Android when blob is bigger than 64kB #31774

tomekzaw opened this issue Jun 25, 2021 · 1 comment
Labels
Needs: Triage 🔍 Resolution: Locked This issue was locked by the bot.

Comments

@tomekzaw
Copy link
Contributor

tomekzaw commented Jun 25, 2021

Description

When an object URL for a blob of size greater than 64 kB is passed as source of <Image /> component, the image does not load on Android (but it works fine on iOS).

video.mp4

React Native version:

System:
    OS: macOS 11.2.3
    CPU: (8) x64 Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz
    Memory: 1.93 GB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 12.16.1 - /var/folders/nj/gnv84lfn0r78lkr4c64wbtgh0000gn/T/yarn--1624602004676-0.054501887345412614/node
    Yarn: 1.22.10 - /var/folders/nj/gnv84lfn0r78lkr4c64wbtgh0000gn/T/yarn--1624602004676-0.054501887345412614/yarn
    npm: 6.13.4 - ~/.asdf/installs/nodejs/12.16.1/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.10.1 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 14.5, DriverKit 20.4, macOS 11.3, tvOS 14.5, watchOS 7.4
    Android SDK:
      API Levels: 20, 21, 22, 23, 27, 28, 29, 30
      Build Tools: 23.0.1, 28.0.3, 29.0.2, 29.0.3, 30.0.3
      System Images: android-29 | Intel x86 Atom_64, android-29 | Google APIs Intel x86 Atom, android-29 | Google Play Intel x86 Atom, android-30 | Google APIs Intel x86 Atom
      Android NDK: 21.3.6528147
  IDEs:
    Android Studio: 4.2 AI-202.7660.26.42.7351085
    Xcode: 12.5/12E262 - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_282 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.1 => 17.0.1
    react-native: 0.64.2 => 0.64.2
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Steps To Reproduce

  1. Register BlobProvider as a Content Provider according to the following instructions:
    /**
    * To allow Blobs be accessed via `content://` URIs,
    * you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
    *
    * ```xml
    * <manifest>
    * <application>
    * <provider
    * android:name="com.facebook.react.modules.blob.BlobProvider"
    * android:authorities="@string/blob_provider_authority"
    * android:exported="false"
    * />
    * </application>
    * </manifest>
    * ```
    * And then define the `blob_provider_authority` string in `res/values/strings.xml`.
    * Use a dotted name that's entirely unique to your app:
    *
    * ```xml
    * <resources>
    * <string name="blob_provider_authority">your.app.package.blobs</string>
    * </resources>
    * ```
    */
  2. Create an object URL for a blob of size greater than 64 kB using URL.createObjectURL:
const url = 'https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg'; // 83261 bytes
const result = await fetch(url);
const blob = await result.blob();
const objectURL = URL.createObjectURL(blob);
  1. Pass the above object URL as source for <Image /> component.
<Image source={{uri: objectURL}} />

Expected Results

It should work for blobs bigger than 64 kB on Android as well.

Snack, code example, screenshot, or link to a repository:

https://github.com/tomekzaw/blobissue

@tomekzaw
Copy link
Contributor Author

tomekzaw commented Jun 25, 2021

When loading the image, BlobProvider calls blobModule.resolve(uri) to get blob data, then creates a pipe with ParcelFileDescriptor.createPipe(), ensures that the pipe will be closed using new ParcelFileDescriptor.AutoCloseOutputStream(writeSide) and finally writes the bytes using outputStream.write(data).

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if (!mode.equals("r")) {
throw new FileNotFoundException("Cannot open " + uri.toString() + " in mode '" + mode + "'");
}
BlobModule blobModule = null;
Context context = getContext().getApplicationContext();
if (context instanceof ReactApplication) {
ReactNativeHost host = ((ReactApplication) context).getReactNativeHost();
ReactContext reactContext = host.getReactInstanceManager().getCurrentReactContext();
blobModule = reactContext.getNativeModule(BlobModule.class);
}
if (blobModule == null) {
throw new RuntimeException("No blob module associated with BlobProvider");
}
byte[] data = blobModule.resolve(uri);
if (data == null) {
throw new FileNotFoundException("Cannot open " + uri.toString() + ", blob not found.");
}
ParcelFileDescriptor[] pipe;
try {
pipe = ParcelFileDescriptor.createPipe();
} catch (IOException exception) {
return null;
}
ParcelFileDescriptor readSide = pipe[0];
ParcelFileDescriptor writeSide = pipe[1];
try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) {
outputStream.write(data);
} catch (IOException exception) {
return null;
}
return readSide;
}

The issue may be related to the fact that pipe capacity is 65536 bytes and the write operation is blocking or it performs only a partial write if there is more bytes to be written (https://man7.org/linux/man-pages/man7/pipe.7.html, sections "Pipe capacity" and "PIPE_BUF").

grabbou pushed a commit that referenced this issue Jul 16, 2021
Summary:
Fixes #31774.

This pull request resolves a problem related to accessing blobs greater than 64 KB on Android. When an object URL for such blob is passed as source of `<Image />` component, the image does not load.

This issue was related to the fact that pipe buffer has a limited capacity of 65536 bytes (https://man7.org/linux/man-pages/man7/pipe.7.html, section "Pipe capacity"). If there is more bytes to be written than free space in the buffer left, the write operation blocks and waits until the content is read from the pipe.

The current implementation of `BlobProvider.openFile` first creates a pipe, then writes the blob data to the pipe and finally returns the read side descriptor of the pipe. For blobs larger than 64 KB, the write operation will block forever, because there are no readers to empty the buffer.

https://github.com/facebook/react-native/blob/41ecccefcf16ac8bcf858dd955af709eb20f7e4a/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.java#L86-L95

This pull request moves the write operation to a separate thread. The read side descriptor is returned immediately so that both writer and reader can work simultaneously. Reading from the pipe empties the buffer and allows the next chunks to be written.

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[Android] [Fixed] - Fix support for blobs larger than 64 KB

Pull Request resolved: #31789

Test Plan:
A new example has been added to RN Tester app to verify if the new implementation properly loads the image of size 455 KB from a blob via object URL passed as image source.

<img src="https://user-images.githubusercontent.com/20516055/123859163-9eba6d80-d924-11eb-8a09-2b1f353bb968.png" alt="Screenshot_1624996413" width="300" />

Reviewed By: ShikaSD

Differential Revision: D29674273

Pulled By: yungsters

fbshipit-source-id: e0ac3ec0a23690b05ab843061803f95f7666c0db
@facebook facebook locked as resolved and limited conversation to collaborators Jul 14, 2022
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs: Triage 🔍 Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants