最近折腾了一下文件上传的方法,网上虽然有不少封装好的,但是基于RxJava2+Retrofit 带上传进度的还没有找到好的解决方法,所以自己就去踩了一下坑。(大部分和RxJava1的方法是一样的)。
主要思路:继承okHttp3的RequestBody方法,在"写入"的方法监听数据传递的字节长度,当上传的上传的字节长度和文件总大小一致,则上传完成。(没有耐心看实现过程的直接到github看封装,链式调用,爽到不行)。
- 附:如果开启了OkHttp日志拦截,会导致onPreogress被调用两次,导致ProgressDialog进度条会刷新两次,被这个坑了好久!!
文末附github地址
先上服务端的代码(Java servlet技术):
web.xml文件
文件接收处理方法类
public class UploadHandleServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(savePath);
//判断上传文件的保存目录是否存在
if (!file.exists() && !file.isDirectory()) {
System.out.println(savePath+"目录不存在,需要创建");
//创建目录
file.mkdir();
}
//消息提示
String message = "";
try{
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(request)){
//按照传统方式获取数据
return;
}
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封装的是普通输入项的数据
if(item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{//如果fileitem中封装的是上传文件
//得到上传的文件名称,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//获取item中的上传文件的输入流
InputStream in = item.getInputStream();
//创建一个文件输出流
FileOutputStream out = new FileOutputStream(savePath + "\\" + filename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流
in.close();
//关闭输出流
out.close();
//删除处理文件上传时生成的临时文件
item.delete();
message = "文件上传成功!";
}
}
}catch (Exception e) {
message= "文件上传失败!";
e.printStackTrace();
}
response.setContentType("text/plain;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
StringBuffer sb = new StringBuffer();
sb.append(message);
PrintWriter out = null;
try {
out = response.getWriter();
out.write(sb.toString());
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
服务器收到的问价保存路径(在设置的工作空间的目录中)
客户端(Android)代码
gradle库依赖
//RxAndroid
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
//RxJava2
compile 'io.reactivex.rxjava2:rxjava:2.0.7'
//Retrofit2的RxJava适配
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
//Retrofit2
compile 'com.squareup.retrofit2:retrofit:2.2.0'
//RxJava2Gson适配
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
//网络日志拦截
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
需要权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
文件上传的回调
public abstract class FileUploadObserver<T> extends DefaultObserver<T> {
@Override
public void onNext(T t) {
onUpLoadSuccess(t);
}
@Override
public void onError(Throwable e) {
onUpLoadFail(e);
}
@Override
public void onComplete() {
}
//监听进度的改变
public void onProgressChange(long bytesWritten, long contentLength) {
onProgress((int) (bytesWritten*100 / contentLength));
}
//上传成功的回调
public abstract void onUpLoadSuccess(T t);
//上传失败回调
public abstract void onUpLoadFail(Throwable e);
//上传进度回调
public abstract void onProgress(int progress);
}
实现上传进度的主要方法, 扩展OkHttp的请求体,实现上传时的进度提示
public class UploadFileRequestBody extends RequestBody {
private RequestBody mRequestBody;
private FileUploadObserver<ResponseBody> fileUploadObserver;
public UploadFileRequestBody(File file, FileUploadObserver<ResponseBody> fileUploadObserver) {
this.mRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
this.fileUploadObserver = fileUploadObserver;
}
@Override
public MediaType contentType() {
return mRequestBody.contentType();
}
@Override
public long contentLength() throws IOException {
return mRequestBody.contentLength();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
CountingSink countingSink = new CountingSink(sink);
BufferedSink bufferedSink = Okio.buffer(countingSink);
//写入
mRequestBody.writeTo(bufferedSink);
//必须调用flush,否则最后一部分数据可能不会被写入
bufferedSink.flush();
}
protected final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
if (fileUploadObserver != null) {
fileUploadObserver.onProgressChange(bytesWritten, contentLength());
}
}
}
}
</br>
使用
这么复杂!!??这也好意思叫封装?完全不能忍好吗
继续封装后的使用:
是不是瞬间觉得爽了,链式调用,传入需要上传的文件,完整的接口地址,对回调进行监听就行了。
Github地址: https://github.com/Cicinnus0407/RetrofitUpLoadFileDemo