6

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 :)

Ehsan
  • 2,676
  • 6
  • 29
  • 56
  • 1
    This may help: https://futurestud.io/tutorials/retrofit-2-how-to-download-files-from-server – Henry May 27 '17 at 17:16
  • 1
    did u add this android:largeHeap="true" in manifest ? – Quick learner May 27 '17 at 17:18
  • @quicklearner this should only be done if one really needs the memory. This is not the case here because one can use streaming. I.e. write the first bytes of the files to disk before it is completely loaded. – Henry May 27 '17 at 17:22

1 Answers1

21

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.
Royz
  • 145
  • 1
  • 15
sheba
  • 800
  • 6
  • 14
  • I didn't understand number 4 but i did 1,2,3 in my app and now I can download all files, but sometimes, some files downloaded with a different size(Less size than it's original) from server – Ehsan May 30 '17 at 05:37
  • 1
    Number 4 is only in case you are using a logging interceptor to log the requests/responses you are sending using retrofit. You are currently not doing it. In order to understand why you are getting partial data you can try to: 1. Add a logging interceptor and find out if the client (retrofit) or your server is in charge of the data loss. Find instructions in [this answer](https://stackoverflow.com/a/33256827/2297433). 2. Review the `access.log` in your web server (if you can) and see if it is sending the full data. It is important to remove the interceptor in the end! It might cause `OOM`! – sheba Jun 03 '17 at 10:19
  • @sheba , we are working together and using TomEE as the web server. is there any problem in the case like this? – Seyed Ali Roshan Jun 06 '17 at 06:51
  • Thank you @sheba! I was facing an issue when rotating the screen in a specific activity, due to high processing and the tip 1 just solved! – Gláucio Leonardo Sant'ana Mar 01 '18 at 00:29