1

I am trying to send a lot of images to an API using okHttp3 and retrofit2. I have tried sending the images from one device (5 images at a time) and it worked well. Then I tried sending 5 images from 5 devices at the same time and it worked well, but when I tried to send 30 images from 5 devices, I got the following error:

    2020-01-17 14:57:07.416 32244-32264/my.example.com.puri W/zygote: Throwing OutOfMemoryError "Failed to allocate a 102554120 byte allocation with 8388608 free bytes and 97MB until OOM, max allowed footprint 174912000, growth limit 268435456"
2020-01-17 14:57:07.422 32244-32264/my.example.com.puri E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: my.example.com.puri, PID: 32244
    java.lang.OutOfMemoryError: Failed to allocate a 102554120 byte allocation with 8388608 free bytes and 97MB until OOM, max allowed footprint 174912000, growth limit 268435456
        at java.lang.StringFactory.newStringFromBytes(StringFactory.java:178)
        at java.lang.StringFactory.newStringFromBytes(StringFactory.java:209)
        at okio.Buffer.readString(Buffer.java:620)
        at okio.Buffer.readString(Buffer.java:603)
        at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:198)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
        at okhttp3.RealCall$AsyncCall.execute(RealCall.java:135)
        at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

This error does not come on the first device that sends request, rather it comes to the one that is 3rd or last. The device where I click the send button first always succeeds.

I also made these changes to the Manifest.

<application
    android:hardwareAccelerated="false"
    android:largeHeap="true"
>

And here is how I make the request.

@Multipart
@POST("installationdocument")
Call<Void> createNewDocument(@Header(AUTH_HEADER) String authorization, @Part ArrayList<MultipartBody.Part> images, @Part("document") RequestBody json);

And:

@Override
public void callRestApi(PuriRestApi restApi, RestApiJobService.ApiRequestor apiRequestor) {
    MediaType JSON = MediaType.parse("application/json");
    MediaType JPG = MediaType.parse("image/jpg");

    ArrayList<MultipartBody.Part> imageParts = new ArrayList<>();
        for (String imagePath : imagePaths) {
            File image = new File(imagePath);
            RequestBody imageBody = RequestBody.create(JPG, image);
            imageParts.add(MultipartBody.Part.createFormData("images", image.getName(), imageBody));
        }
    RequestBody jsonPart = RequestBody.create(JSON, documentString);

    apiRequestor.request(restApi.createNewDocument(authentication, imageParts, jsonPart), response -> {
        if (response.isSuccessful()) {
            if (imagePaths != null && imagePaths.length > 0) {
                for (String imagePath : imagePaths) {
                    File image = new File(imagePath);
                    image.delete();
                }
            }
            return true;
        } else {
            return false;
        }
    });
}

Maybe creating a large variable is what throws this error. Because variable ArrayList<MultipartBody.Part> imageParts will contain around 150mb of image data with 30 images. But other than that I have no idea what could be causing this error.

Any kind of help is really appreciated.

Richard
  • 1,087
  • 18
  • 52

3 Answers3

4

Unless I've misread that error stack it looks to me like that error is coming from HttpLoggingInterceptor trying to allocate a 102Mb buffer to quite literally log the whole of your Image data.

If so I presume you've set the logging level to BODY, in which case setting it to any other level would fix the problem.

greatape
  • 324
  • 3
  • 5
  • 1
    Well by disabling logs it works now so it does seem to be the problem, thank you a LOT. I am using `implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'`. And I disabled logs by doing `buildConfigField 'boolean', 'LOGGING_ENABLED', 'false'`. Is this the correct way to disable logs? Also do you know if by default the `LOGGING_ENABLED` is set to true or false? – Richard Jan 20 '20 at 12:13
  • Sorry can't help with how your logging is configured, in my code I create my own HttpLoggingInterceptor and just set the level to BASIC. It does seem strange that whatever setup you've got is defaulting to level BODY, because that's asking for trouble. Something I'd have thought was strictly for debugging only, and when you know there isn't an excessive amount of data that's going to get logged. – greatape Jan 21 '20 at 07:55
  • Well it was enable only on dev version and not in production but still.. didn't even know to look there so thank you.. you learn something new every day – Richard Jan 22 '20 at 10:14
1

Add below entities in your manifest file android:hardwareAccelerated="false" , android:largeHeap="true" it will work for some environment's.

<application
android:allowBackup="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">

See the StackOverflow answer for details...

Md. Enamul Haque
  • 926
  • 8
  • 14
0

You can use compress method before sending images to server

public  Bitmap compressInputImage(Uri inputImageData){
try {
bitmapInputImage=MediaStore.Images.Media.getBitmap(context.getContentResolver(),     inputImageData); 

        if (bitmapInputImage.getWidth() > 2048 && bitmapInputImage.getHeight() > 2048)
        {
            dpBitmap = Bitmap.createScaledBitmap(bitmapInputImage, 1024, 1280, true);            }
        else if (bitmapInputImage.getWidth() > 2048 && bitmapInputImage.getHeight() < 2048)
        {
            dpBitmap = Bitmap.createScaledBitmap(bitmapInputImage, 1920, 1200, true);            }
        else if (bitmapInputImage.getWidth() < 2048 && bitmapInputImage.getHeight() > 2048)
        {
            dpBitmap = Bitmap.createScaledBitmap(bitmapInputImage, 1024, 1280, true);            }
        else if (bitmapInputImage.getWidth() < 2048 && bitmapInputImage.getHeight() < 2048)
        {
            dpBitmap = Bitmap.createScaledBitmap(bitmapInputImage, bitmapInputImage.getWidth(), bitmapInputImage.getHeight(), true);            }
    } catch (Exception e)
    {
        dpBitmap = bitmapInputImage;

    }
    return dpBitmap;
}

It can minimise the chances of OutOfMemory Exception. Besides this you can handle exception by adding try - catch .

S.Ambika
  • 292
  • 1
  • 14
  • Catching `Fatal exception` which is same as `Error` should be quite dangerous to catch. But won't compressing images add to the time and thus cause the same error? But thank you for the answer, will try – Richard Jan 20 '20 at 10:36