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

最近折腾了一下文件上传的方法,网上虽然有不少封装好的,但是基于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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,534评论 25 707
  • 随摘 受自由和自主思想培育的人民,認為任何統治形式都是可怕的,是違背自然的。習慣於君主制的人民也一樣。不管命運為他...
    功法阅读 159评论 0 0
  • 潜能解密,最受父母欢迎的公众号~ 一旦孩子出现问题, 我们经常是指责学校、社会,而不是反思自己。 人民日报:教育改...
    齐小上阅读 383评论 0 1
  • “轰!” 粗壮的银色雷蛇从厚重的云层落下,随之而来的是密集的雨声。 暴雨,冲刷着城市的每一个角落,将城市中那些污垢...
    萧萧不帅阅读 278评论 0 0
  • 文/肖政球 立在原野上,独自徘徊 立在十字路口,不知走向哪里 寻了一个圈,却找不到前进的目标 于是,你仿佛离开了光...
    cb25f42212e3阅读 246评论 0 0