×

RxJava2+Retrofit2单文件上传监听进度封装(服务端代码+客户端代码)

96
r09er
2017.03.21 18:00* 字数 398

最近折腾了一下文件上传的方法,网上虽然有不少封装好的,但是基于RxJava2+Retrofit 带上传进度的还没有找到好的解决方法,所以自己就去踩了一下坑。(大部分和RxJava1的方法是一样的)。
主要思路:继承okHttp3的RequestBody方法,在"写入"的方法监听数据传递的字节长度,当上传的上传的字节长度和文件总大小一致,则上传完成。(没有耐心看实现过程的直接到github看封装,链式调用,爽到不行)。

  • 附:如果开启了OkHttp日志拦截,会导致onPreogress被调用两次,导致ProgressDialog进度条会刷新两次,被这个坑了好久!!
    文末附github地址

先上服务端的代码(Java servlet技术):

web.xml文件

Paste_Image.png

文件接收处理方法类

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);
}
}

服务器收到的问价保存路径(在设置的工作空间的目录中)

图1-1 服务器保存的文件路径

客户端(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>

使用

图1-2 没有封装之前的文件调用

这么复杂!!??这也好意思叫封装?完全不能忍好吗

继续封装后的使用:

图1-3 封装后的单文件上传调用

是不是瞬间觉得爽了,链式调用,传入需要上传的文件,完整的接口地址,对回调进行监听就行了。

Github地址: https://github.com/Cicinnus0407/RetrofitUpLoadFileDemo

日记本
Web note ad 1