项目中使用了Retrofit2 网络框架,对Retrofit的文件上传和下载进行记录。
文件上传
文件上传 一般采用POST 的方式,并使用@Multipart 声明为多部分,可同时上传文本和文件,接口设置如下:
interface UploadService {
@Multipart
@POST("uploadImg")
Observable<BaseResult<String>> upload(
@Part List<MultipartBody.Part> partList);
}
step1 创建UploadService
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) //连接超时
.readTimeout(10, TimeUnit.SECONDS) //读取超时
.build();
Retrofit retrofit = new Retrofit.Builder().baseUrl(Contacts.BASE_URL) //配置Retrofit 端
.addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClient)
.build();
UploadService uploadService = retrofit.create(UploadService.class);
step2 构建文件RequestBody
// 创建 RequestBody,用于封装 请求RequestBody
File imageFile = new File(filePath); //上传文件对象
RequestBody requestFile =
RequestBody.create(guessMimeType(imageFile.getName()), imageFile); //这里使用了工具类,获取文件类型
guseeMimeType方法为:
private static MediaType guessMimeType(String path) {
FileNameMap fileNameMap = URLConnection.getFileNameMap();
path = path.replace("#", ""); //解决文件名中含有#号异常的问题
String contentType = fileNameMap.getContentTypeFor(path);
if (contentType == null) {
contentType = "application/octet-stream";
}
return MediaType.parse(contentType);
}
step3 构建MultipartBody 进行上传
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)//表单类型 "multipart/form-data"
.addFormDataPart("version", Contacts.version) // 项目的接口公共参数
.addFormDataPart("data", data_json) //json 字符串
.addFormDataPart("sign", sign)
.addFormDataPart("file", imageFile.getName(), requestFile); //上传的文件
List<MultipartBody.Part> parts = builder.build().parts();
//进行上传
uploadService.upload(parts)
.compose(RxSchedulerHepler.<BaseResult<String>>io_main()); //线程切换
文件下载
文件下载相较于 文件上传复杂一点,其中包含了 ResponseBody 流的读取,文件的写入,下载进度的更新
之前进度之类的更新,采用的是接口回调的形式进行更新,自从RxJava的出现改变了这样的方式,采用观察者的订阅方式进行进度的更新。
下载接口的设计如下:
@GET
@Streaming //使用Streaming 方式 Retrofit 不会一次性将ResponseBody 读取进入内存,否则文件很多容易OOM
Flowable<ResponseBody> download2(@Url String url); //返回值使用 ResponseBody 之后会对ResponseBody 进行读取
Step 1 创建ApiService (so easy!)
OkHttpClient httpClient = new OkHttpClient.Builder()
.build();
Retrofit retrofit = new Retrofit.Builder().baseUrl(Contacts.BASE_URL)
.addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClient)
.build();
ApiService apiService = retrofit.create(ApiService.class);
Step2 获取ResponseBody 进行文件流的读写
apiService.download2(url)
.flatMap(new Function<ResponseBody, Publisher<Boolean>>() {
@Override
public Publisher<Boolean> apply(@NonNull final ResponseBody responseBody) throws Exception {
return Flowable.create(new FlowableOnSubscribe<Boolean>() {
@Override
public void subscribe(FlowableEmitter<Boolean> e) throws Exception {
File saveFile = new File(filePath, fileName);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
try {
int readLen;
byte[] buffer = new byte[1024];
inputStream = responseBody.byteStream();
outputStream = new FileOutputStream(saveFile);
while ((readLen = inputStream.read(buffer)) != -1 && !e.isCancelled()) {
outputStream.write(buffer, 0, readLen);
}
outputStream.flush(); // This is important!!!
e.onComplete();
} finally {
closeQuietly(inputStream);
closeQuietly(outputStream);
closeQuietly(responseBody);
}
} catch (Exception exception) {
e.onError(exception);
}
}
}, BackpressureStrategy.LATEST);
}
})
其中包含了一个关闭资源方法:
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
方法分析:
采用RxJava2 的Flowable ,Flowable 在Observable 的基础上进行了背压的处理
接着获取到ResponseBody,使用RxJava的flatMap 对数据源进行变换,利用FlowableEmitter可以将下载成功的消息发射出去
接着就是熟悉的文件读写操作文件读写完成之后,发射 成功的信息
Step 3 下载进度
Step 2中完成了文件的读写,但是缺少了挺重要的进度回调操作,对Step 2 的代码进行改进
apiService.download2(url)
.flatMap(new Function<ResponseBody, Publisher<DownloadStatus>>() {
@Override
public Publisher<DownloadStatus> apply(@NonNull final ResponseBody responseBody) throws Exception {
return Flowable.create(new FlowableOnSubscribe<DownloadStatus>() {
@Override
public void subscribe(FlowableEmitter<DownloadStatus> e) throws Exception {
//创建文件
File saveFile = new File(filePath, fileName);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
try {
int readLen;
int downloadSize = 0;
byte[] buffer = new byte[8192];
DownloadStatus status = new DownloadStatus();
inputStream = responseBody.byteStream(); //获取 输入流
outputStream = new FileOutputStream(saveFile); //文件的输出流
long contentLength = responseBody.contentLength(); //文件的总长度
status.setTotalSize(contentLength);
while ((readLen = inputStream.read(buffer)) != -1 && !e.isCancelled()) {
outputStream.write(buffer, 0, readLen);
downloadSize += readLen;
status.setDownloadSize(downloadSize);
e.onNext(status); //读取完一段就将下载进度发射出去
}
outputStream.flush(); // This is important!!!
e.onComplete(); //发射下载完成的信息
} finally {
closeQuietly(inputStream);
closeQuietly(outputStream);
closeQuietly(responseBody);
}
} catch (Exception exception) {
}
}
}, BackpressureStrategy.LATEST);
}
})
.toObservable() //转换成我们熟悉的Observable
.debounce(200, TimeUnit.MICROSECONDS) //进行200秒的过滤操作
.compose(RxSchedulerHepler.<DownloadStatus>io_main()); //线程切换
DownLoadStatus对下载的状态进行了包装,包含了下载长度,总长度 字段
public class DownloadStatus implements Parcelable {
public static final Creator<DownloadStatus> CREATOR
= new Creator<DownloadStatus>() {
@Override
public DownloadStatus createFromParcel(Parcel source) {
return new DownloadStatus(source);
}
@Override
public DownloadStatus[] newArray(int size) {
return new DownloadStatus[size];
}
};
private long totalSize;
private long downloadSize;
public DownloadStatus() {
}
public DownloadStatus(long downloadSize, long totalSize) {
this.downloadSize = downloadSize;
this.totalSize = totalSize;
}
public DownloadStatus(boolean isChunked, long downloadSize, long totalSize) {
this.downloadSize = downloadSize;
this.totalSize = totalSize;
}
protected DownloadStatus(Parcel in) {
this.totalSize = in.readLong();
this.downloadSize = in.readLong();
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public long getDownloadSize() {
return downloadSize;
}
public void setDownloadSize(long downloadSize) {
this.downloadSize = downloadSize;
}
/**
* 获得格式化的总Size
*
* @return example: 2KB , 10MB
*/
public String getFormatTotalSize() {
return formatSize(totalSize);
}
public String getFormatDownloadSize() {
return formatSize(downloadSize);
}
public static String formatSize(long size) {
String hrSize;
double b = size;
double k = size / 1024.0;
double m = ((size / 1024.0) / 1024.0);
double g = (((size / 1024.0) / 1024.0) / 1024.0);
double t = ((((size / 1024.0) / 1024.0) / 1024.0) / 1024.0);
DecimalFormat dec = new DecimalFormat("0.00");
if (t > 1) {
hrSize = dec.format(t).concat(" TB");
} else if (g > 1) {
hrSize = dec.format(g).concat(" GB");
} else if (m > 1) {
hrSize = dec.format(m).concat(" MB");
} else if (k > 1) {
hrSize = dec.format(k).concat(" KB");
} else {
hrSize = dec.format(b).concat(" B");
}
return hrSize;
}
/**
* 获得格式化的状态字符串
*
* @return example: 2MB/36MB
*/
public String getFormatStatusString() {
return getFormatDownloadSize() + "/" + getFormatTotalSize();
}
/**
* 获得下载的百分比, 保留两位小数
*
* @return example: 5.25%
*/
public String getPercent() {
String percent;
Double result;
if (totalSize == 0L) {
result = 0.0;
} else {
result = downloadSize * 1.0 / totalSize;
}
NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMinimumFractionDigits(2);//控制保留小数点后几位,2:表示保留2位小数点
percent = nf.format(result);
return percent;
}
/**
* 获得下载的百分比数值
*
* @return example: 5% will return 5, 10% will return 10.
*/
public long getPercentNumber() {
double result;
if (totalSize == 0L) {
result = 0.0;
} else {
result = downloadSize * 1.0 / totalSize;
}
return (long) (result * 100);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.totalSize);
dest.writeLong(this.downloadSize);
}
}
Step 4在Activity中进行文件下载测试
String url = "http://www.bz55.com/uploads/allimg/150701/140-150F1142638.jpg";
Disposable mSubscribe = DownLoadUtil3.download(url, Environment.getExternalStorageDirectory().getAbsolutePath(), "140-150F1142638.jpg")
.subscribe(new Consumer<DownloadStatus>() {
@Override
public void accept(@NonNull DownloadStatus downloadStatus) throws Exception {
long downloadSize = downloadStatus.getDownloadSize();
long totalSize = downloadStatus.getTotalSize();
Log.e("TAG", "onNext -->" + downloadSize + "------" + totalSize);
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e("TAG", "onError:--->" + throwable);
}
}, new Action() {
@Override
public void run() throws Exception {
Log.e("TAG", "下载完成");
}
});
//停止下载的方式为 mSubscribe.dispose();