最近在研究Retrofit下载文件,之前也写了两篇关于Retrofit下载上传文件以及下载上传进度的监听的问题.Retrofit上传/下载文件 和 Retrofit上传/下载文件扩展实现进度的监听.使用之中发现还是不是很方便.于是在想能不能想GsonConverterFactory那样自定义一个FileConverterFactory在响应回调中直接返回File呢?
想到就做,于是写了一个FileConverterFactory继承于Converter.Factory
,以及FileConverter继承于FileConverter
.
FileConverterFactory:1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* Created by Cmad on 2016/5/4.
*/
public class FileConverterFactory extends Converter.Factory{
public static FileConverterFactory create(){
return new FileConverterFactory();
}
public Converter<ResponseBody, File> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return FileConverter.INSTANCE;
}
}
重写了Converter.Factory的responseBodyConverter
,当我们返回体需要File的时候即Call<T>
中的T为File的时候就会调用FileConverterFactory,然后调用FileConverter将ResponseBody转化为File再回调到前台.
FileConverter:
1 | /** |
FileConverter实现了Converter接口,并实现了唯一的方法convert方法,将ResponseBody转化为File,这里写了一个方法writeResponseBodyToDisk
将ResponseBody内容保存到文件.
接下来看看怎么使用:
1 | public interface DownloadService { |
1 | private void download() { |
打印结果file path:/storage/emulated/0/test.jpg
,查看对应路径确实多了一个test.jpg的图片.说明我们的FileConverterFactory确实可用.
但是上面的代码有个问题,那就是文件的保存路径是写死的,这样在正式开发中使用明显是不可行的,那么我们要怎样将这个保存路径在请求的时候进行动态设置呢?
于是进行了如下几种尝试:
- 我最开始的想法是能不能自定义一个注解,然后在api接口的参数上进行注解,但是最后结果失败了参数上只能使用Retrofit提供的注解.在方法体上倒是可以使用自定义注解,并且在ConverterFactory中也能准确获取到注解的内容,我们可以看到在ConverterFactory的
responseBodyConverter
方法中第二个参数是Annotation[]
一个注解的数组,这其实就是API接口方法体上的注解. 貌似很可行的样子,但是实验后的结果却不是很理想,固然在responseBodyConverter
里能获取到注解的值,但是在Call<File> download(@Url String fileUrl)
上注解其实也是一个常量值,跟上面的代码是一样的问题.
第一种办法宣告失败!
- 思考良久,后来想到一种办法,能不能通过header来实现? 在请求的时候我们可以添加header,那么我们能不能在header里添加保存路径,然后再在FileConverterFactory里获取header值? 一番实验发现在FileConverterFactory里或者从FileConverter的ResponseBody里获取不到请求的header,于是这种办法也宣告失败了,但是在实验这个办法的时候却发现了另一个可行的办法.
- 在实验方法二的时候,在debug下发现FileConverter中convert方法中的ResponseBody其实是一个
ExceptionCatchingRequestBody
里面有一个属性delegate
持有的却是上一篇文件设置文件下载监听自定义的ResponseBody.于是我在想能不能在okhttpClient添加拦截器的时候讲header的值取出来设置到自定义的ResponseBody中然后再在FileConverter中获取呢?
HttpClientHelper:
1 | /** |
在拦截里通过request获得请求的header的FileConverter.SAVE_PATH
key对应的值并将其值设置的到我们自定义的ResponseBody中.
FileConverter中:
1 | /** |
在convert中我们通过反射的办法拿到了delegate
的ResponseBody判断是否为我们自定义的ProgressResponseBody,然后取出保存路径值.
至于这里为啥要用反射的办法? 因为ExceptionCatchingRequestBody
类在外面是不可用的,并且他的成员变量delegate
也是私有的,所以这里采用了反射的办法拿到对应的值.
看看怎么使用:
1 | public interface DownloadService { |
参数里添加@Header(FileConverter.SAVE_PATH)
其中FileConverter.SAVE_PATH
是我们在FileConverter中自定义的key值.
ServiceGenerator :
1 | public class ServiceGenerator { |
在通过Retrofit获得service的时候添加使用HttpClientHelper
获得已经添加了自定义的进度监听的ResponseBody的OkhttpClient.
使用
1 | private void download() { |
至此,我们的FileConverterFactory就完成了.
后面还完善了FileConverter种获取下载文件的文件名,如果没有设置下载保存路径默认保存到sdcard根目录,已经如果设置的保存路径是一个目录的话默认保存到这个目录,文件名则为下载文件名.
完整项目地址:convert-file
欢迎大家提出问题优化完善!
library已经上传到jcenter可以在Gradle里添加compile 'com.cm.retrofit2:converter-file:1.0.1'
进行使用.