項目中使用了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();