Retrofit2 & RxJava2实现单文件和多文件上传

Retrofit2 是目前Android开发主流的网络库,RxJava2也是目前开发者使用的比较多用来更优雅实现异步的库,因为最近业务需求有用到这两个库,就简单分享下它的一个实际使用场景—上传文件

[TOC]

集成RxJava2和Retrofit2

    // Rx
    compile rootProject.ext.dependencies["rxjava"]
    compile rootProject.ext.dependencies["rxandroid"]
    compile rootProject.ext.dependencies["rxpermissions"]

    // network
    compile rootProject.ext.dependencies["retrofit"]
    compile rootProject.ext.dependencies["retrofit-converter-gson"]
    compile rootProject.ext.dependencies["retrofit-adapter-rxjava2"]
    compile rootProject.ext.dependencies["logging-interceptor"]

上面我将依赖统一抽取出来了,也建议大家这样做。

具体配置文件在根目录下的config.gradle

ext {
    android = [
            compileSdkVersion: 25,
            buildToolsVersion: '25.0.3',
            applicationId    : "com.tencent.bugly",
            minSdkVersion    : 16,
            targetSdkVersion : 25,
            javaVersion      : JavaVersion.VERSION_1_7,
            versionCode      : 1,
            versionName      : "1.0.0"
    ]

    def dependVersion = [
            rxJava             : "2.0.7",
            rxandroid          : "2.0.1",
            rxpermissions      : "0.9.3@aar",
            retrofit           : "2.2.0",
            okhttp3            : "3.4.1",
    ]

    dependencies = [
            // rx
            "rxjava"                            : "io.reactivex.rxjava2:rxjava:${dependVersion.rxJava}",
            "rxandroid"                         : "io.reactivex.rxjava2:rxandroid:${dependVersion.rxandroid}",
            "rxpermissions"                     : "com.tbruyelle.rxpermissions2:rxpermissions:${dependVersion.rxpermissions}",

            // network
            "retrofit"                          : "com.squareup.retrofit2:retrofit:${dependVersion.retrofit}",
            "retrofit-converter-gson"           : "com.squareup.retrofit2:converter-gson:${dependVersion.retrofit}",
            "retrofit-adapter-rxjava2"          : "com.squareup.retrofit2:adapter-rxjava2:${dependVersion.retrofit}",
            // 网络日志拦截
            "logging-interceptor"               : "com.squareup.okhttp3:logging-interceptor:${dependVersion.okhttp3}",
    ]
}

这是依赖的部分,集成之后会从maven仓库中将我们需要的库下载到本地,这样我就可以使用了 ,不用说,这些大家都懂。

封装OkHttpManager类

/**
 * OkHttp管理类.
 *
 * @author devilwwj
 * @since 2017/7/12
 */
public class OkHttpManager {

    private static OkHttpClient okHttpClient;

    /**
     * 获取OkHttp单例,线程安全.
     *
     * @return 返回OkHttpClient单例
     */
    public static OkHttpClient getInstance() {
        if (okHttpClient == null) {
            synchronized (OkHttpManager.class) {
                if (okHttpClient == null) {
                    OkHttpClient.Builder builder = new OkHttpClient.Builder();

                    if (BuildConfig.DEBUG) {
                        // 拦截okHttp的日志,如果开启了会导致上传回调被调用两次
                        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
                        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                        builder.addInterceptor(interceptor);
                    }

                    // 超时时间
                    builder.connectTimeout(15, TimeUnit.SECONDS);// 15S连接超时
                    builder.readTimeout(20, TimeUnit.SECONDS);// 20s读取超时
                    builder.writeTimeout(20, TimeUnit.SECONDS);// 20s写入超时
                    // 错误重连
                    builder.retryOnConnectionFailure(true);
                    okHttpClient = builder.build();
                }
            }
        }
        return okHttpClient;
    }
}

这个类主要是获取OkHttpClient示例,设置它的一些参数,比如超时时间,拦截器等等.

封装RetrofitClient类

/**
 * RetrofitClient.
 *
 * @author devilwwj
 * @since 2017/7/12
 */
public class RetrofitClient {
    private static RetrofitClient mInstance;
    private static Retrofit retrofit;

    private RetrofitClient() {
        retrofit = RetrofitBuilder.buildRetrofit();
    }

    /**
     * 获取RetrofitClient实例.
     *
     * @return 返回RetrofitClient单例
     */
    public static synchronized RetrofitClient getInstance() {
        if (mInstance == null) {
            mInstance = new RetrofitClient();
        }
        return mInstance;
    }

    private <T> T create(Class<T> clz) {
        return retrofit.create(clz);
    }

    /**
     * 单上传文件的封装.
     *
     * @param url 完整的接口地址
     * @param file 需要上传的文件
     * @param fileUploadObserver 上传回调
     */
    public void upLoadFile(String url, File file,
                    FileUploadObserver<ResponseBody> fileUploadObserver) {

        UploadFileRequestBody uploadFileRequestBody =
                        new UploadFileRequestBody(file, fileUploadObserver);

        create(UploadFileApi.class)
                        .uploadFile(url, MultipartBuilder.fileToMultipartBody(file,
                                        uploadFileRequestBody))
                        .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                        .subscribe(fileUploadObserver);

    }

    /**
     * 多文件上传.
     *
     * @param url 上传接口地址
     * @param files 文件列表
     * @param fileUploadObserver 文件上传回调
     */
    public void upLoadFiles(String url, List<File> files,
                    FileUploadObserver<ResponseBody> fileUploadObserver) {

        create(UploadFileApi.class)
                        .uploadFile(url, MultipartBuilder.filesToMultipartBody(files,
                                        fileUploadObserver))
                        .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                        .subscribe(fileUploadObserver);

    }

}

这个是Retrofit客户端类,获取它的单例然后去调用它的上传文件的方法,可以看到我这里封装了两个方法,uploadFile是上传单个文件,uploadFiles方法上传多个文件.

因为我们需要构造一个Retrofit对象,所以这里有一个RetrofitBuilder类:

/**
 * Retrofit构造器.
 *
 * @author devilwwj
 * @since 2017/7/13
 */
public class RetrofitBuilder {
    private static Retrofit retrofit;

    public static synchronized Retrofit buildRetrofit() {
        if (retrofit == null) {
            Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
            GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);
            retrofit = new Retrofit.Builder().client(OkHttpManager.getInstance())
                    .baseUrl(AppConfig.HTTP_SERVER)
                    .addConverterFactory(gsonConverterFactory)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

可以看到,想构造Retrofit对象是需要获取OkhttpClient实例的。

定义上传文件接口

/**
 * 上传API.
 *
 * @author devilwwj
 * @since 2017/7/12
 */
public interface UploadFileApi {
    String UPLOAD_FILE_URL = AppConfig.HTTP_SERVER + "file/upload";

    @POST
    Observable<ResponseBody> uploadFile(@Url String url, @Body MultipartBody body);
}

这里就是Retrofit定义接口的形式,通过注解来表示各个参数,@POST表示发起post请求,@Url表示这是个请求地址,@Body表示这是请求体,关于Retrofit的各种注解的使用这里不多说,大家可以自行了解。

构造MultipartBody

上一步定义好了上传的接口,我们最终是要去构造MultipartBody,这一块就需要跟后台同学进行沟通了,根据接口定义来实现,这里是我们的实现:

/**
 * MultipartBuilder.
 *
 * @author devilwwj
 * @since 2017/7/13
 */
public class MultipartBuilder {

    /**
     * 单文件上传构造.
     *
     * @param file 文件
     * @param requestBody 请求体
     * @return MultipartBody
     */
    public static MultipartBody fileToMultipartBody(File file, RequestBody requestBody) {
        MultipartBody.Builder builder = new MultipartBody.Builder();

        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("fileName", file.getName());
        jsonObject.addProperty("fileSha", Utils.getFileSha1(file));
        jsonObject.addProperty("appId", "test0002");

        builder.addFormDataPart("file", file.getName(), requestBody);

        builder.addFormDataPart("params", jsonObject.toString());
        builder.setType(MultipartBody.FORM);
        return builder.build();
    }

    /**
     * 多文件上传构造.
     * 
     * @param files 文件列表
     * @param fileUploadObserver 文件上传回调
     * @return MultipartBody
     */
    public static MultipartBody filesToMultipartBody(List<File> files,
                    FileUploadObserver<ResponseBody> fileUploadObserver) {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        JsonArray jsonArray = new JsonArray();

        Gson gson = new Gson();
        for (File file : files) {
            UploadFileRequestBody uploadFileRequestBody =
                            new UploadFileRequestBody(file, fileUploadObserver);
            JsonObject jsonObject = new JsonObject();

            jsonObject.addProperty("fileName", file.getName());
            jsonObject.addProperty("fileSha", Utils.getFileSha1(file));
            jsonObject.addProperty("appId", "test0002");

            jsonArray.add(jsonObject);
            LogUtil.d(jsonObject.toString());
            builder.addFormDataPart("file", file.getName(), uploadFileRequestBody);
        }

        builder.addFormDataPart("params", gson.toJson(jsonArray));

        LogUtil.d(gson.toJson(jsonArray));
        builder.setType(MultipartBody.FORM);
        return builder.build();
    }

}

自定义RequestBody

构造MultipartBody是需要去创建每个文件对应的ReqeustBody,但我们这边需要监听到文件上传成功、失败和进度的状态,所以需要去自定义:

/**
 * 上传文件请求body.
 *
 * @author devilwwj
 * @since 2017/7/12
 */
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();

    }

    /**
     * CountingSink.
     */
    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());
            }

        }

    }
}

这里有个RxJava2的Observer的抽象类,主要是用来收到Rxjava2的事件:

/**
 * 上传文件的RxJava2回调.
 *
 * @author devilwwj
 * @since 2017/7/12
 *
 * @param <T> 模板类
 */
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 abstract void onUploadSuccess(T t);

    // 上传失败回调
    public abstract void onUploadFail(Throwable e);

    // 上传进度回调
    public abstract void onProgress(int progress);

    // 监听进度的改变
    public void onProgressChange(long bytesWritten, long contentLength) {
        onProgress((int) (bytesWritten * 100 / contentLength));
    }
}

ok,到现在完整的代码实现已经说完。

具体使用方法

RetrofitClient.getInstance().upLoadFiles(UploadFileApi.UPLOAD_FILE_URL, files,
                new FileUploadObserver<ResponseBody>() {
                    @Override
                    public void onUploadSuccess(ResponseBody responseBody) {

                        if (responseBody == null) {
                            LogUtil.e("responseBody null");
                            return;
                        }

                        try {
                            JSONObject jsonObject = new JSONObject(responseBody.string());

                            ArrayList<String> fileIds = new ArrayList<String>();
                            fileIds.add(jsonObject.getString("fileId"));

                        } catch (IOException e) {
                            e.printStackTrace();
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }

                    }

                    @Override
                    public void onUploadFail(Throwable e) {
                    }

                    @Override
                    public void onProgress(int progress) {
                        LogUtil.d(String.valueOf(progress));
                    }
                });

笔者这里是上传到文件服务器,成功会返回对应的fileId。

总结

通篇代码实现很多,但可以看到使用Retrofit2和RxJava2的结合起来使用还是挺方便的,再也不用自己去控制线程的切换了,也不用去关注http的具体实现,少写了不少代码,实现起来也优雅不少,希望这篇文章能帮助到大家。

原文发布于微信公众号 - 小巫技术博客(wwjblog)

原文发表时间:2017-09-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

ExecuteReader在执行有输出参数的存储过程时拿不到输出参数

异常处理汇总-后端系列 http://www.cnblogs.com/dunitian/p/4523006.html 后期会在博客首发更新:http://dnt...

3537
来自专栏向治洪

将图库的图片剪切并保存

最近有些用户反映保存图片之后在系统图库找不到保存的图片,遂决定彻底查看并解决下。 Adnroid中保存图片的方法可能有如下两种: 第一种是自己写方法,如下代...

23610
来自专栏狂码一生

用MFC写一个聊天室程序 - 学习笔记

下面的服务器端与客户端的程序与步骤是我在学习MFC网络编程写一个聊天室程序所写的程序,在这里作一个笔记,也希望能帮到一部分刚刚学习的朋友,一起共勉,一起努力历进...

98815
来自专栏颇忒脱的技术博客

Servlet 3.1 Async IO分析

Servlet Async Processing提供了一种异步请求处理的手段(见我的另一篇文章Servlet 3.0 异步处理详解),能够让你将Http thr...

1443
来自专栏Lambda

编程规范

领域层–编码规范 2018年4月4日14:10:38 Controller层编写规范 controller层只是负责从service层获得数据,对外暴露API接...

3626
来自专栏Java成神之路

Java微信公众平台开发_03_消息管理之被动回复消息

上一节,我们启用服务器配置的时候,填写了一个服务器地址(url),如下图,这个url就是回调url,是开发者用来接收微信消息和事件的接口URL 。也就是说,用户...

1.2K5
来自专栏酷玩时刻

Android极速开发之发送短信

实现SMS主要用到SmsManager类,该类继承自java.lang.Object类,下面我们介绍一下该类的主要成员。

1142
来自专栏开发之途

Gradle 常用配置总结

当项目逐渐演进的过程中,主工程依赖的 Module 可能会越来越多,此时就需要统一配置各个 Module 的编译参数了

1223
来自专栏非著名程序员

Android WebView 上传文件支持全解析

声明:原文地址:http://blog.isming.me/2015/12/21/android-webview-upload-file/,转载请注明出处。 默...

9007
来自专栏Android 研究

APK安装流程详解14——PMS中的新安装流程上(拷贝)补充

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACK...

2551

扫码关注云+社区

领取腾讯云代金券