如何使用 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? @CarsonHolzheimer 或更好,当您可以使用 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 中创建顶部导航栏