【问题标题】:How to download file in Android using Retrofit library?如何使用 Retrofit 库在 Android 中下载文件?
【发布时间】:2015-10-01 02:22:00
【问题描述】:

我需要在我的应用程序中使用 Retrofit 库下载所有类型的文件(二进制、图像、文本等)。网上所有的例子都是使用 HTML GET 方法。我需要使用 POST 来防止自动缓存。

我的问题是如何在 Retrofit 中使用 POST 方法下载文件?

【问题讨论】:

标签: android download retrofit


【解决方案1】:

kotlin,执行此操作:

在您的服务添加方法中:

    @Streaming
    @GET
    suspend fun downloadFile(@Url fileUrl:String): Response<ResponseBody>

从 ViewModel 调用这个方法:

viewModelScope.launch {
     val responseBody=yourServiceInstance.downloadFile(url).body()
     saveFile(responseBody,pathWhereYouWantToSaveFile)
}

保存文件:

fun saveFile(body: ResponseBody?, pathWhereYouWantToSaveFile: String):String{
        if (body==null)
            return ""
        var input: InputStream? = null
        try {
            input = body.byteStream()
            //val file = File(getCacheDir(), "cacheFileAppeal.srl")
            val fos = FileOutputStream(pathWhereYouWantToSaveFile)
            fos.use { output ->
                val buffer = ByteArray(4 * 1024) // or other buffer size
                var read: Int
                while (input.read(buffer).also { read = it } != -1) {
                    output.write(buffer, 0, read)
                }
                output.flush()
            }
            return pathWhereYouWantToSaveFile
        }catch (e:Exception){
            Log.e("saveFile",e.toString())
        }
        finally {
            input?.close()
        }
        return ""
    }

注意:

  1. 确保您的 refrofit 客户端的基本 url 和传递给 downloadFile 的 url 使文件 url 有效:

Retrofit 的 Base url + downloadFile 的方法 url = 文件 url

  1. 这里我在downloadFile 之前使用suspend 关键字从ViewModel 调用它,我使用了viewModelScope.launch {} 你可以根据你的调用端使用不同的协程范围。

  2. 现在pathWhereYouWantToSaveFile,如果你想将文件存储到项目的文件目录中,你可以这样做:

val fileName=url.substring(url.lastIndexOf("/")+1)
val pathWhereYouWantToSaveFile = myApplication.filesDir.absolutePath+fileName
  1. 如果将下载的文件存储在file或cache目录下,则不需要获取权限,否则公用存储,你知道流程。

【讨论】:

    【解决方案2】:

    使用@Streaming

    异步

    编辑 1

    //On your api interface
    @POST("path/to/your/resource")
    @Streaming
    void apiRequest(Callback<POJO> callback);
    
    restAdapter.apiRequest(new Callback<POJO>() {
            @Override
            public void success(POJO pojo, Response response) {
                try {
                    //you can now get your file in the InputStream
                    InputStream is = response.getBody().in();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void failure(RetrofitError error) {
    
            }
        });
    

    同步

    //On your api interface
    @POST("path/to/your/resource")
    @Streaming
    Response apiRequest();
    
    Response response = restAdapter.apiRequest();
    
    try {
        //you can now get your file in the InputStream
        InputStream is = response.getBody().in();
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    【讨论】:

    • 感谢您的回复,我会尝试并回复您。
    • 这是一个对象类,你可以把它改成Object
    • 我收到此错误:只有将 Response 作为数据类型的方法才允许使用 @Streaming 注释。我正在使用异步模式。
    • 我通过将 POJO 更改为 Response 类来解决它。谢谢。
    • @Ehsan 您可以使用 Response (import retrofit.client.Response;) 作为模型并从该 Response 对象中获取一个字符串。
    【解决方案3】:

    这是如何在 Retrofit 2 中下载文件

    public interface ServerAPI {
            @GET
            Call<ResponseBody> downlload(@Url String fileUrl);
    
            Retrofit retrofit =
                    new Retrofit.Builder()
                            .baseUrl("http://192.168.43.135/retro/") // REMEMBER TO END with /
                            .addConverterFactory(GsonConverterFactory.create())
                     .build();
    
    }
    
        //How To Call
    public void download(){
            ServerAPI api = ServerAPI.retrofit.create(ServerAPI.class);
            api.downlload("http://192.168.43.135/retro/pic.jpg").enqueue(new Callback<ResponseBody>() {
                        @Override
                        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                            try {
                                File path = Environment.getExternalStorageDirectory();
                                File file = new File(path, "file_name.jpg");
                                FileOutputStream fileOutputStream = new FileOutputStream(file);
                                IOUtils.write(response.body().bytes(), fileOutputStream);
                            }
                            catch (Exception ex){
                            }
                        }
    
                        @Override
                        public void onFailure(Call<ResponseBody> call, Throwable t) {
                        }
                    });
    }
    

    【讨论】:

    • 这段代码甚至无法编译。您不能在 Interface 中初始化事物。
    • 你试过编译吗?它对我有用,这就是我以前的方式
    • 对于IOUtils,添加依赖实现'org.apache.directory.studio:org.apache.commons.io:2.4'
    【解决方案4】:

    如果你使用Retrofit 2.0.0,可以在问题下参考我的answer -- Use retrofit to download image file

    关键是使用okhttp3.ReponseBody接收原始二进制数据,而不是任何POJO。

    而你想用POST方法来获取文件,很简单,把@GET改成@POST就行了,不过要看你的服务器是否支持POST方法!

    【讨论】:

      【解决方案5】:

      您可以使用以下代码进行下载(Kotlin)

      改造 API 服务

      @Streaming
      @GET
      fun downloadFile(@Url fileUrl: String): Observable<Response<ResponseBody>>
      

      确保添加@Streaming 以下载大文件

      并将以下代码粘贴到您的 Activity 或 Fragment 中

      fun downloadfileFromRetrofit() {
          val retrofit = Retrofit.Builder()
              .baseUrl("ENTER_YOUR_BASE_URL")
              .client(OkHttpClient.Builder().build())
              .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build()
          val downloadService = retrofit.create(RetrofitApi::class.java)
      
         downloadService.downloadFile("FILE_URL_PATH").observeOn(AndroidSchedulers.mainThread())
              .subscribeOn(Schedulers.io()).subscribe({
                  val task = object : AsyncTask<Void, Integer, Void>() {
                      override fun doInBackground(vararg voids: Void): Void? {
                          val writtenToDisk =writeResponseBodyToDisk(it.body()!!)
                          println("file download was a success? $writtenToDisk")
                          return null
                      }
                  }
                  task.execute()
              }, {
                  print(it.message)
              })
      }
      

      下面是writeResponseBodyToDisk方法

      fun writeResponseBodyToDisk(body: ResponseBody): Boolean {
          val appDirectoryName = "YOUR_DIRECTORY_NAME"
          val filename = "YOUR_FILE_NAME"
          val apkFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename)
          try {
      
              var inputStream: InputStream? = null
              var outputStream: OutputStream? = null
              try {
                  val fileReader = ByteArray(4096)
                  val fileSize = body.contentLength()
                  var fileSizeDownloaded: Long = 0
                  inputStream = body.byteStream()
                  outputStream = FileOutputStream(apkFile)
                  while (true) {
                      val read = inputStream!!.read(fileReader)
                      if (read == -1) {
                          break
                      }
                      outputStream.write(fileReader, 0, read)
                      fileSizeDownloaded += read.toLong()
      
                 calulateProgress(fileSize.toDouble(),fileSizeDownloaded.toDouble()
                      println("file downloading $fileSizeDownloaded of $fileSize")
      
                  outputStream.flush()
      
                  return true
              } catch (e: Exception) {
                  println(e.toString())
                  return false
              } finally {
                  if (inputStream != null) {
                      inputStream!!.close()
                  }
                  outputStream?.close()
              }
          } catch (e: Exception) {
              println(e.toString())
              return false
          }
      
      }
      

      以下方法用于计算进度

       fun calulateProgress(totalSize:Double,downloadSize:Double):Double{
          return ((downloadSize/totalSize)*100)
      }
      

      【讨论】:

      • 此解决方案需要 RxJava 和 AsyncTask。
      • 当你有 RxJava 的时候为什么还要使用 AsyncTask?
      • @CarsonH​​olzheimer 或更好,当您可以使用 Kotlin 协程时,为什么还要使用 RxJava 甚至 AsyncTask? :D
      • 如果您阅读此内容,请不要使用上面的答案。它混合了不同的编程方法,没有任何优势。这会使您的应用程序容易受到错误、错误、缺少可读性等的影响......
      • 如果有更好的方法,请发布替代方法
      【解决方案6】:

      我使用以下代码通过改造下载任何类型的文件...

       File file = new File("Your_File_path/name");
      
        private void startDownload() {
      
          if (!NetWorkUtils.getInstance(context).isNetworkAvailable()) {
              Toast.makeText(context, "No data connection available", Toast.LENGTH_SHORT).show();
              return;
          }
      
          showProgressDialog();
      
          Retrofit retrofit = new Retrofit.Builder()
                  .baseUrl(FILE_BASE_URL)
                  .build();
      
          FileHandlerService handlerService = retrofit.create(FileHandlerService.class);
      
          Call<ResponseBody> call = handlerService.downloadFile(mFileName);
          call.enqueue(new Callback<ResponseBody>() {
              @Override
              public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                  dismissProgressDialog();
                  if (response.isSuccessful()) {
                      if (writeResponseBodyToDisk(response.body())) {
                          listener.onFileLoaded(file);
                      }
                  } else {
                      listener.onDownloadFailed("Resource not Found");
                  }
              }
      
              @Override
              public void onFailure(Call<ResponseBody> call, Throwable t) {
                  dismissProgressDialog();
                  listener.onDownloadFailed("Download Failed");
                  t.printStackTrace();
              }
          });
      
      }
      
      
      interface FileHandlerService {
      
          @GET("uploads/documents/{file_name}")
          Call<ResponseBody> downloadFile(
                  @Path("file_name") String imageName);
      }
      
      private boolean writeResponseBodyToDisk(ResponseBody body) {
          try {
      
              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(file);
      
                  while (true) {
                      int read = inputStream.read(fileReader);
      
                      if (read == -1) {
                          break;
                      }
      
                      outputStream.write(fileReader, 0, read);
      
                      fileSizeDownloaded += read;
      
                      Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
                  }
      
                  outputStream.flush();
      
                  return true;
              } catch (IOException e) {
                  e.printStackTrace();
                  return false;
              } finally {
                  if (inputStream != null) {
                      inputStream.close();
                  }
                  if (outputStream != null) {
                      outputStream.close();
                  }
              }
          } catch (IOException e) {
              e.printStackTrace();
              return false;
          }
      }
      

      【讨论】:

      • 它适用于我的 android 5.1,但不适用于其他设备。我已经在 android 6 中测试过它并不起作用 0
      【解决方案7】:

      在 MainActivity.java 中包含以下函数:

      void getRetrofitImage() {
      
          Retrofit retrofit = new Retrofit.Builder()
                  .baseUrl(url)
                  .addConverterFactory(GsonConverterFactory.create())
                  .build();
      
          RetrofitImageAPI service = retrofit.create(RetrofitImageAPI.class);
      
          Call<ResponseBody> call = service.getImageDetails();
      
          call.enqueue(new Callback<ResponseBody>() {
              @Override
              public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {
      
                  try {
      
                      Log.d("onResponse", "Response came from server");
      
                      boolean FileDownloaded = DownloadImage(response.body());
      
                      Log.d("onResponse", "Image is downloaded and saved ? " + FileDownloaded);
      
                  } catch (Exception e) {
                      Log.d("onResponse", "There is an error");
                      e.printStackTrace();
                  }
      
              }
      
              @Override
              public void onFailure(Throwable t) {
                  Log.d("onFailure", t.toString());
              }
          });
      }
      

      图像下载的文件处理部分将是:

      private boolean DownloadImage(ResponseBody body) {
      
          try {
              Log.d("DownloadImage", "Reading and writing file");
              InputStream in = null;
              FileOutputStream out = null;
      
              try {
                  in = body.byteStream();
                  out = new FileOutputStream(getExternalFilesDir(null) + File.separator + "AndroidTutorialPoint.jpg");
                  int c;
      
                  while ((c = in.read()) != -1) {
                      out.write(c);
                  }
              }
              catch (IOException e) {
                  Log.d("DownloadImage",e.toString());
                  return false;
              }
              finally {
                  if (in != null) {
                      in.close();
                  }
                  if (out != null) {
                      out.close();
                  }
              }
      
              int width, height;
              ImageView image = (ImageView) findViewById(R.id.imageViewId);
              Bitmap bMap = BitmapFactory.decodeFile(getExternalFilesDir(null) + File.separator + "AndroidTutorialPoint.jpg");
              width = 2*bMap.getWidth();
              height = 6*bMap.getHeight();
              Bitmap bMap2 = Bitmap.createScaledBitmap(bMap, width, height, false);
              image.setImageBitmap(bMap2);
      
              return true;
      
          } catch (IOException e) {
              Log.d("DownloadImage",e.toString());
              return false;
          }
      }
      

      你可以看到这个完整的教程:Image Download using Retrofit 2.0

      【讨论】:

        【解决方案8】:

        使用 Kotlin,这有点简单。

        API 服务

        @GET
        @Streaming
        fun download(@Url url: String): Call<ResponseBody>
        

        API 客户端

        object ApiClient {
            private val retrofit = ...
        
            val service: ApiService = retrofit.create(ApiService::class.java)
        }
        

        下载功能

        fun download(urlString: String, target: File) {
            val response = ApiClient.service.download(urlString).execute()
            response.body()?.byteStream()?.use {
                target.parentFile?.mkdirs()
            
                FileOutputStream(target).use { targetOutputStream ->
                    it.copyTo(targetOutputStream)
                }
            } ?: throw RuntimeException("failed to download: $urlString")
        }
        

        【讨论】:

          【解决方案9】:

          下载文件的请求声明如下所示

          // option 1: a resource relative to your base URL
          @GET("/resource/example.zip")
          Call<ResponseBody> downloadFileWithFixedUrl();
          
          // option 2: using a dynamic URL
          @GET
          Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);  
          

          像这样声明你的请求调用后

          FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);
          
          Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
          
          call.enqueue(new Callback<ResponseBody>() {  
              @Override
              public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                  if (response.isSuccess()) {
                      Log.d(TAG, "server contacted and has file");
          
                      boolean writeToDisk = writeToDisk(response.body());
          
                      Log.d(TAG, "file downloaded " + writtenToDisk);
                  } else {
                      Log.d(TAG, "server error");
                  }
              }
          
              @Override
              public void onFailure(Call<ResponseBody> call, Throwable t) {
                  Log.e(TAG, "error");
              }
          });
          

          也实现此方法以归档到您的 sdcard。

          private boolean writeToDisk(ResponseBody body) {  
              try { File mediaStorageDir = new File(
                          Environment
                                  .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                          "ProfileImage");
          
                  // Create the storage directory if it does not exist
                  if (!mediaStorageDir.exists()) {
                      if (!mediaStorageDir.mkdirs()) {
                          Log.e("ProfileImage", "Oops! Failed create "
                                  + "ProfileImage" + " directory");
                      }
                  }
                  File futureStudioIconFile = new File(mediaStorageDir.getPath() + File.separator
                          + "userImage.png");
          
                  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(TAG, "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;
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-11-20
            • 2018-11-13
            • 2017-02-24
            • 2011-08-18
            • 1970-01-01
            • 2016-05-28
            相关资源
            最近更新 更多