Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix OutOfMemoryError thrown while trying to throw OutOfMemoryError with retrofit

I am using retrofit to download some media files like video,mp3, jpg, pdf,... in my application.threre is a problem when I want to download a large file with 55MB with the format of mp4. when I want to download this file I get an error like this:

OutOfMemoryError threw while trying to throw OutOfMemoryError; no stack trace available

this is my code:

  private void downloadFile() {

    ArrayList<FileModel> filesInDB = G.bootFileFromFileDB();

    for (final FileModel fm : filesInDB) {

      APIService downloadService = ServiceGenerator.createServiceFile(APIService.class, "username", "password");

      //Id of apk file that you want to download
      Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync("file/download/" + String.valueOf(fm.getFileId()));
      call.enqueue(new Callback<ResponseBody>() {

        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
          if (response.isSuccess()) {

            Log.d("LOGOO", "server contacted and has file");

            boolean writtenToDisk = writeResponseBodyToDisk(response.body(), fm.getFileName(), fm.getFileExtension());

            response = null;


            Log.d("LOGOO", "file download was a success? " + writtenToDisk);


          } else {
            Log.d("LOGOO", "server contact failed");
          }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
          Log.i("LOGO", "Error is : " + t.getMessage());

          Toast.makeText(ActivityInternet.this, R.string.internet_error, Toast.LENGTH_LONG).show();
          Intent intent = new Intent(ActivityInternet.this, ActivityStartup.class);
          startActivity(intent);
        }
      });
    }

and this is 'writeResponseBodyToDisk' method that I'm using:

  private boolean writeResponseBodyToDisk(ResponseBody body, String fileName, String fileExtension) {

    try {

      // Location to save downloaded file and filename
      File futureStudioIconFile = new File(G.DIR_APP + fileName + fileExtension);
      InputStream inputStream = null;
      OutputStream outputStream = null;
      try {
        byte[] fileReader = new byte[4096];
        long fileSize = body.contentLength();
        long fileSizeDownloaded = 0;
        inputStream = body.byteStream();
        outputStream = new FileOutputStream(futureStudioIconFile);
        while (true) {
          int read = inputStream.read(fileReader);
          if (read == -1) {
            break;
          }
          outputStream.write(fileReader, 0, read);
          fileSizeDownloaded += read;
          Log.d("LOGO", "file download: " + fileSizeDownloaded + " of " + fileSize);
        }
        outputStream.flush();
        return true;
      } catch (IOException e) {
        return false;
      } finally {
        if (inputStream != null) {
          inputStream.close();
        }
        if (outputStream != null) {
          outputStream.close();
        }
      }
    } catch (IOException e) {
      return false;
    }


  }

finally this is my createServiceFile method:

public static <S> S createServiceFile(Class<S> serviceClass, String username, String password) {
        if (username != null && password != null) {

            String credentials = username + ":" + password;
            final String basic =
              "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
            httpClient.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request original = chain.request();
                    Request.Builder requestBuilder = original.newBuilder()
                      .header("Authorization", basic)
                      .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,audio/mp4,image/jpeg,*/*;q=0.8")
                      .method(original.method(), original.body());
                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }
        OkHttpClient client = httpClient.build();
        Retrofit retrofit = builder.client(client).build();
        return retrofit.create(serviceClass);
    }

I really appreciate if you can help me :)

like image 729
Ehsan Avatar asked May 27 '17 17:05

Ehsan


People also ask

What is OutOfMemoryError in thread main?

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space Usually, this error is thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.

What is the OutOfMemoryError exception in Java?

The OutOfMemoryError Exception in Java looks like this: Usually, this error is thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.

Why does Java throw an OutOfMemoryError when using finalizers?

Because the Java heap is insufficient to allocate this array, it throws a java.lang.OutOfMemoryError: Java heap space A java.lang.OutOfMemoryError: Java heap space can also occur in applications that use finalizers excessively.

How much memory does an OutOfMemoryError really use?

As you can see, the first graph, the app is never able to really regain some of the memory used. It used up to about 300MB at one point before the OutOfMemoryError occurred. The second graph shows that the app is able to garbage collect, regain some memory and stays pretty consistent in its memory usage.


1 Answers

There are 4 things to notice when handling large file downloads in retrofit:

  1. Make sure you are using android:largeHeap="true" in your AndroidManifest.xml, as an attribute for the <application>.
  2. Make sure you are using @Streaming annotation from Retrofit, in order to stream the solution and not read it as a whole, which encumbers memory.
  3. Handle the response using an AsyncTask as described in this link. In your case, this means calling writeResponseBodyToDisk from within the AsyncTask.
  4. The last thing to avoid is using a Level.BODY okhttp3 logging interceptor. Using it alongside a streamed response still keeps the entire response body in memory, negating the advantage of the @Streaming support provided by retrofit.
like image 173
sheba Avatar answered Nov 11 '22 16:11

sheba