美文网首页移动开发狂热者(299402133)Android 文章
Retrofit2 & RxJava2实现单文件和多文件

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

作者: IT_xiao小巫 | 来源:发表于2017-09-04 18:59 被阅读145次

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

    集成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的具体实现,少写了不少代码,实现起来也优雅不少,希望这篇文章能帮助到大家。

    相关文章

      网友评论

        本文标题:Retrofit2 & RxJava2实现单文件和多文件

        本文链接:https://www.haomeiwen.com/subject/kqaejxtx.html