如何使用 Retrofit 库在 Android 中下载文件?

Posted

技术标签:

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

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

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

【问题讨论】:

试试这个:***.com/questions/32878478/… 不要尝试这个:) 这是问题的第二个答案。 【参考方案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 ""
    

注意:

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

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

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

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

val fileName=url.substring(url.lastIndexOf("/")+1)
val pathWhereYouWantToSaveFile = myApplication.filesDir.absolutePath+fileName
    如果将下载的文件存储在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;
    

【讨论】:

以上是关于如何使用 Retrofit 库在 Android 中下载文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用快速的 android 网络库在 Android 中显示 Json 结果

如何使用 Retrofit Android Kotlin 发布 [重复]

如何在 Android 中使用 Retrofit 发送 JSON POST 请求,并接收字符串响应

如何使用 Leanback 库在 Android TV 中创建顶部导航栏

如何使用快速的Android网络库在Android中显示Json结果

如何使用 Retrofit Android 解析对象内部的数组