ivpusic / react-native-image-crop-picker

iOS/Android image picker with support for camera, video, configurable compression, multiple images and cropping
MIT License
6.13k stars 1.56k forks source link

Android crashed with OutOfMemoryError while select videos #2037

Open krmao opened 7 months ago

krmao commented 7 months ago

Version

Tell us which versions you are using:

Platform

Tell us to which platform this issue is related

Expected behaviour

not crash

Actual behaviour

crash

Steps to reproduce

  1. select big video

  2. crash

Attachments

Sentry crash logs

OutOfMemoryError: Failed to allocate a 295420368 byte allocation with 6291456 free bytes and 250MB until OOM, target footprint 11852424, growth limit 268435456

?
58.34.154.234
ID:035070fa-1147-48fd-afc7-d2532ae4d6d9

Android
版本:13

V2283A
型号:V2283A

最新

OutOfMemoryError
Failed to allocate a 295420368 byte allocation with 6291456 free bytes and 250MB until OOM, target footprint 11852424, growth limit 268435456
mechanism
UncaughtExceptionHandler
handled
false

com.reactnative.ivpusic.imagepicker.PickerModule 位置 createExternalStoragePrivateFile 行 627
com.reactnative.ivpusic.imagepicker.PickerModule 位置 resolveRealPath 行 602
com.reactnative.ivpusic.imagepicker.PickerModule 位置 getAsyncSelection 行 496
com.reactnative.ivpusic.imagepicker.PickerModule 位置 imagePickerResult 行 783
com.reactnative.ivpusic.imagepicker.PickerModule 位置 onActivityResult 行 877
com.facebook.react.bridge.ReactContext 位置 onActivityResult 行 375
com.facebook.react.ReactInstanceManager 位置 onActivityResult 行 822
com.facebook.react.ReactDelegate 位置 onActivityResult 行 107
com.facebook.react.ReactActivityDelegate 位置 onActivityResult 行 136
com.facebook.react.ReactActivity 位置 onActivityResult 行 70
android.app.Activity 位置 dispatchActivityResult 行 9154
android.app.ActivityThread 位置 deliverResults 行 5814
android.app.ActivityThread 位置 handleSendResult 行 5860
android.app.servertransaction.ActivityResultItem 位置 execute 行 67
android.app.servertransaction.ActivityTransactionItem 位置 execute 行 45
android.app.servertransaction.TransactionExecutor 位置 executeCallbacks 行 135
android.app.servertransaction.TransactionExecutor 位置 execute 行 95
android.app.ActivityThread$H 位置 handleMessage 行 2627
android.os.Handler 位置 dispatchMessage 行 106
android.os.Looper 位置 loopOnce 行 223
android.os.Looper 位置 loop 行 324
android.app.ActivityThread 位置 main 行 8679
java.lang.reflect.Method 位置 invoke
com.android.internal.os.RuntimeInit$MethodAndArgsCaller 位置 run 行 582

here


private File createExternalStoragePrivateFile(Context context, Uri uri) throws FileNotFoundException {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
    String extension = this.getExtension(context, uri);
    File file = new File(context.getExternalCacheDir(), "/temp/" + System.currentTimeMillis() + "." + extension);
    File parentFile = file.getParentFile();
    if (parentFile != null) {
        parentFile.mkdirs();
    }

    try {
        // Very simple code to copy a picture from the application's
        // resource into the external file.  Note that this code does
        // no error checking, and assumes the picture is small (does not
        // try to copy it in chunks).  Note that if external storage is
        // not currently mounted this will silently fail.
        OutputStream outputStream = new FileOutputStream(file);
        byte[] data = new byte[inputStream.available()];
        inputStream.read(data);
        outputStream.write(data);
        inputStream.close();
        outputStream.close();
    } catch (IOException e) {
        // Unable to create file, likely because external storage is
        // not currently mounted.
        Log.w("image-crop-picker", "Error writing " + file, e);
    }

    return file;
}


### the key is
>             byte[] data = new byte[inputStream.available()];
krmao commented 7 months ago

I will try with patch by chatgpt for a test

diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
index 5de0845..5042117 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
@@ -599,7 +599,8 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
                     && !path.startsWith(externalFilesDirPath)
                     && !path.startsWith(cacheDirPath)
                     && !path.startsWith(FilesDirPath)) {
-                File copiedFile = this.createExternalStoragePrivateFile(activity, uri);
+                // File copiedFile = this.createExternalStoragePrivateFile(activity, uri);
+                File copiedFile = this.createExternalStoragePrivateFileV2(activity, uri);
                 path = RealPathUtil.getRealPathFromURI(activity, Uri.fromFile(copiedFile));
             }
         }
@@ -638,6 +639,50 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
         return file;
     }

+    private File createExternalStoragePrivateFileV2(Context context, Uri uri) throws IOException {
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+        File file = null;
+        try {
+            inputStream = context.getContentResolver().openInputStream(uri);
+            if (inputStream == null) {
+                throw new FileNotFoundException("InputStream is null for URI: " + uri);
+            }
+            String extension = this.getExtension(context, uri);
+            file = new File(context.getExternalCacheDir(), "/temp/" + System.currentTimeMillis() + "." + extension);
+            File parentFile = file.getParentFile();
+            if (parentFile != null) {
+                parentFile.mkdirs();
+            }
+            outputStream = new FileOutputStream(file);
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            Log.w("image-crop-picker", "Error writing " + file, e);
+            throw e;
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    Log.e("image-crop-picker", "Error closing InputStream", e);
+                }
+            }
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    Log.e("image-crop-picker", "Error closing OutputStream", e);
+                }
+            }
+        }
+        return file;
+    }
+
+
     public String getExtension(Context context, Uri uri) {
         String extension;

and add the code to AndroidManifest.xml

        android:largeHeap="true"
        android:hardwareAccelerated="true"
truongnguyenceres commented 7 months ago

It worked!

atultiwaree commented 5 months ago

I will try with patch by chatgpt for a test

diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
index 5de0845..5042117 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
@@ -599,7 +599,8 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
                     && !path.startsWith(externalFilesDirPath)
                     && !path.startsWith(cacheDirPath)
                     && !path.startsWith(FilesDirPath)) {
-                File copiedFile = this.createExternalStoragePrivateFile(activity, uri);
+                // File copiedFile = this.createExternalStoragePrivateFile(activity, uri);
+                File copiedFile = this.createExternalStoragePrivateFileV2(activity, uri);
                 path = RealPathUtil.getRealPathFromURI(activity, Uri.fromFile(copiedFile));
             }
         }
@@ -638,6 +639,50 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
         return file;
     }

+    private File createExternalStoragePrivateFileV2(Context context, Uri uri) throws IOException {
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+        File file = null;
+        try {
+            inputStream = context.getContentResolver().openInputStream(uri);
+            if (inputStream == null) {
+                throw new FileNotFoundException("InputStream is null for URI: " + uri);
+            }
+            String extension = this.getExtension(context, uri);
+            file = new File(context.getExternalCacheDir(), "/temp/" + System.currentTimeMillis() + "." + extension);
+            File parentFile = file.getParentFile();
+            if (parentFile != null) {
+                parentFile.mkdirs();
+            }
+            outputStream = new FileOutputStream(file);
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            Log.w("image-crop-picker", "Error writing " + file, e);
+            throw e;
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    Log.e("image-crop-picker", "Error closing InputStream", e);
+                }
+            }
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    Log.e("image-crop-picker", "Error closing OutputStream", e);
+                }
+            }
+        }
+        return file;
+    }
+
+
     public String getExtension(Context context, Uri uri) {
         String extension;

and add the code to AndroidManifest.xml

        android:largeHeap="true"
        android:hardwareAccelerated="true"

@krmao I tried and it worked now I'm able to select media greater than 1GB but as i exceed 1.5 Gb I'm getting negative media size and app is getting freeze (App not responding)

atultiwaree commented 5 months ago

Is there anyway in android where I can get the size of media without reading the whole file

krmao commented 5 months ago

@atultiwaree

import { getImageMetaData, getVideoMetaData, Video } from 'react-native-compressor';

const getImageInfo = (filePath: string) => {
  return getImageMetaData(filePath).then(result => ({
    ...result,
    size: displayUtil.IS_IOS ? result.size : result.size * 1024, // android 下 size 发现少了 1024倍
  }));
};

const getVideoInfo = (filePath: string) => {
  return getVideoMetaData(filePath).then(result => ({
    ...result,
    size: displayUtil.IS_IOS ? result.size : result.size * 1024, // android 下 size 发现少了 1024倍
  }));
};

or

use RNFS to read file size

atultiwaree commented 5 months ago

getVideoMetaD

Thanks @krmao but to get the file path we have to use react-native-image-crop-picker and that's when the app is getting freeze how do I tackle that is there any native way of doing such things or any library I was using react-native-image-picker but it takes lot of time so I switched to RNICP