本文是RxJava<第三十二篇>:RxJava+Retrofit+OkHttp+MVP的扩展,着重分析单文件上传
、单文件下载
。
本章重点想展示一下上传和下载的进度是如何实现的,后续会推出一个完整封装:RxJava+RxBinding+Retrofit+OkHttp+MVP+Dagger2
来实现get请求、post请求、单文件上传、单文件下载、多文件上传、多文件下载
(1)Retrofit+OkHttp实现文件上传
第一步
自定义带进度条文件体
/**
* 带进度条的RequestBody封装
*/
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mMediaType;//文件类型
private UploadResultListener mListener;//上传结果回调监听
private int bufferSize = 1024;//缓存大小
public ProgressRequestBody(final File file, String mediaType, final UploadResultListener listener) {
mFile = file;
mMediaType = mediaType;
mListener = listener;
}
public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, final UploadResultListener listener) {
mFile = file;
mMediaType = mediaType;
bufferSize = eachBufferSize;
mListener = listener;
}
@Override
public MediaType contentType() {
return MediaType.parse(mMediaType);
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[bufferSize];
FileInputStream in = new FileInputStream(mFile);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
handler.post(new ProgressUpdate(uploaded, fileLength));
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
private class ProgressUpdate implements Runnable {
private long mUploaded;
private long mTotal;
public ProgressUpdate(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override public void run() {
mListener.onProgressChange((int) (100 * mUploaded / mTotal));
}
}
}
第二步
上传文件结果回调监听
/**
* 上传文件监听接口
*/
public interface UploadResultListener {
void onProgressChange(int percent);//进度变化回调
void onFinish();//上传完成回调
void onInterrupted();//上传中断回调
}
第三步
retrofit接口定义
public interface FileAPIService {
//文件上传
@Multipart
@POST("FileUpload/FileUploadServlet")
Call<ResponseBody> uploadFlie(@Part MultipartBody.Part file);
}
第四步
实现
UploadResultListener uploadResultListener = new UploadResultListener() {
@Override
public void onProgressChange(int percent) {
//进度条更新
Log.d("aaa", "percent:"+percent + "%");
((TextView)findViewById(R.id.result_text1)).setText("当前上传进度:" + percent + "%");
}
@Override
public void onFinish() {
//上传完成
((TextView)findViewById(R.id.result_text1)).setText("上传完成");
}
@Override
public void onInterrupted() {
//上传中断
((TextView)findViewById(R.id.result_text1)).setText("上传失败");
}
};
findViewById(R.id.bt_1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//单文件上传
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.0.105:8080")
.build();
FileAPIService fileAPIService = retrofit.create(FileAPIService.class);
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "myapk.apk");
// 创建 RequestBody
ProgressRequestBody progressRequestBody = new ProgressRequestBody(file, "application/otcet-stream", uploadResultListener);
//新建Part
MultipartBody.Part body = MultipartBody.Part.createFormData("myapk", file.getName(), progressRequestBody);
Call<ResponseBody> call = fileAPIService.uploadFlie(body);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response != null && response.isSuccessful()){
uploadResultListener.onFinish();
}else{
uploadResultListener.onInterrupted();
}
}
@Override
public void onFailure(Call call, Throwable t) {
uploadResultListener.onInterrupted();
}
});
}
});
总体四步就可以实现文件的上传,其中第一步是带进度上传文件的核心。
效果如下:
(2)Retrofit+OkHttp实现文件下载
第一步
下载结果回调监听
public interface DownloadResultListener {
void onProgressChange(int percent);//进度变化回调
void onFinish();//下载完成回调
void onInterrupted();//下载中断回调
}
第二步
retrofit接口定义
public interface FileAPIService {
@Streaming
@GET("/1.apk")
Call<ResponseBody> downloadFile();
}
第三步
实现
DownloadResultListener downloadResultListener = new DownloadResultListener() {
@Override
public void onProgressChange(int percent) {
//进度条更新
((TextView)findViewById(R.id.result_text2)).setText("当前下载进度:" + percent+ "%");
}
@Override
public void onFinish() {
//上传完成
((TextView)findViewById(R.id.result_text2)).setText("下载完成");
}
@Override
public void onInterrupted() {
//上传中断
((TextView)findViewById(R.id.result_text2)).setText("下载失败");
}
};
findViewById(R.id.bt_2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//文件下载
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.0.105:8080").build();
FileAPIService searchFileAPIService = retrofit.create(FileAPIService.class);
Call<ResponseBody> call = searchFileAPIService.downloadFile();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response != null && response.isSuccessful()){
new Thread(new Runnable() {
@Override
public void run() {
boolean writtenToDisk = writeResponseBodyToDisk(response.body(), downloadResultListener);
downloadResultListener.onFinish();
}
}).start();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
downloadResultListener.onInterrupted();
}
});
}
});
//写入到磁盘根目录
private boolean writeResponseBodyToDisk(ResponseBody body, DownloadResultListener downloadResultListener) {
try {
File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "mydownloadapk.apk");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
int fileSize = (int) body.contentLength();
int 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;
final int finalFileSizeDownloaded = fileSizeDownloaded;
runOnUiThread(new Runnable() {
@Override
public void run() {
downloadResultListener.onProgressChange(100 * finalFileSizeDownloaded / 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;
}
}
效果如下:
补充:
上传文件时,如果还需要传参,写法如下:
//文件上传
//@Multipart
@POST("/{uploadUrl}")
Call<ResponseBody> uploadFlie(@Path(value = "uploadUrl") String uploadUrl, @Body RequestBody body);
注意,如果仅仅上传文件,不上传参数,那么直接使用@Multipart
注解即可,那么如果一定要在上传文件的同时再上传参数,那么需要传递一个RequestBody
,@Body
不能和@Multipart
一起使用,所以要去除@Multipart
。
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "pic2.jpg");
ProgressRequestBody progressRequestBody = new ProgressRequestBody(file, "multipart/form-data", uploadResultListener);
RequestBody body=new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file",file.getName(),progressRequestBody)
.addFormDataPart("xxx","shareimage")
.addFormDataPart("ooo","weqwewq")
.addFormDataPart("qqq",String.valueOf(System.currentTimeMillis()))
.addFormDataPart("eee",String.valueOf(0))
.addFormDataPart("rrr",String.valueOf(1))
.build();
Call<ResponseBody> call = fileAPIService.uploadFlie("upload.cgi", body);
call.enqueue(new Callback<ResponseBody>() {
以上代码尤其要注意的是,需要和服务器确认所支持的mediaType
有两个地方:
-
确认请求体本身的mediaType
.setType(MultipartBody.FORM)//就是"multipart/form-data"
确认上传文件的mediaType(上传文件一般使用二进制流编码方式:multipart/form-data)
new ProgressRequestBody(file, "multipart/form-data", uploadResultListener);
以上两处mediaType都是需要和服务器相关人员确认的,如果服务器相关人员也不确认,那么只能我们自己去一一尝试了,再尝试之前,必须了解常用的mediaType有哪些?
下面说一说常用的mediaType:
-
流的形式:上传和下载
//multipart/form-data:所有的数据以二进制流发送,此种方式多用于文件上传 //application/otcet-stream:所有的数据以八进制流发送,目测下载文件有效
默认情况
一般默认mediaType如下
//application/x-www-form-urlencoded:默认值,不写就是这个,retrofit 的表单默认也是这个,不能用来传文件
但如果是上传文件的话,默认mediaType可能是
multipart/mixed
-
其它情况
//text/plain:纯文本格式 //application/json:body是一个json
[本章完...]