天天看点

基于OkHttp3,Retrofit2和RxJava2高度封装的网络请求库

项目地址

该网络库基于OkHttp3,Retrofit2和RxJava2进行了高度封装,使网络请求变得异常方便,支持单文件,多文件上传及进度监听,支持文件下载及进度监听

目前对以下需求进行了封装

  • 一般的请求(类似表单)
  • 传复杂的Json对象
  • 单个文件上传监听进度
  • 单个文件上传监听进度+其他表单参数
  • 多文件上传监听进度+其他表单参数
  • 多文件上传监听进度+其他表单参数
  • 文件下载监听进度

项目展示

基于OkHttp3,Retrofit2和RxJava2高度封装的网络请求库

用法

在Application的onCreate中初始化配置

//初始化配置
NetConfig.init(this)
                .withApiHost(BuildConfig.DEBUG ? Constant.API_URL.ENV_DEV : Constant.API_URL.ENV_PROD) //调试环境Url Or 生产环境Url
                .isNetLogDebug(BuildConfig.DEBUG)  //生产环境不打印网络日志
                .configure();
           

其次,根据服务端的接口协议,申明接口

INetService

public interface INetService {
    @GET("activity/query")
    Observable<HttpResult<List<ActivityArticle>>> getActivites(@Query("pageNumber") int pagenumber, @Query("pageSize") int pagesize);

    @POST("version/android/patch")
    Observable<HttpResult<AppVersion>> getLatestVersion(@Query("code") int code);

    //post复杂的Json对象
    @POST("activity/add")
    Observable<HttpResult<ActivityArticle>> addActivityTheme(@Body Object jsonObject);

    //上传单个文件
    @Multipart
    @POST("userprofile/uploadavatar")
    Observable<ResponseBody> uploadAvatar(@Part MultipartBody.Part file);

    //上传文件+其他表单参数
    @Multipart
    @POST("userprofile/uploadavatar2")
    Observable<ResponseBody> uploadAvatar2(@PartMap Map<String, RequestBody> params);

    //上传多个文件+其他参数
    @Multipart
    @POST("bike/fault/add")
    Observable<ResponseBody> uploadIssueReport(@PartMap Map<String, RequestBody> params);
}
           

ApiService

public class ApiService {
    private final INetService mApiService;

    private ApiService() {
        mApiService = RequestCreator.getRetrofitClient().create(INetService.class);
    }

    //静态内部类创建单例
    public static class InstanceHolder {
        private static final ApiService INSTANCE = new ApiService();
    }

    public static ApiService getInstance() {
        return InstanceHolder.INSTANCE;
    }

    //订阅的时候在IO线程,数据返回的时候在主线程
    public static <T> ObservableTransformer<T, T> io_main() {
        return tObservable -> tObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * 获取最新版本号和补丁文件
     */
    public Observable<AppVersion> getLatestVersion() {
        return mApiService.getLatestVersion(AppUtils.getAppVersionCode())
                .map(new HttpResultFunc<>())
                .compose(io_main());
    }

    //获取活动列表
    public Observable<List<ActivityArticle>> getActivites(int pagenumber, int pagesize) {
        return mApiService.getActivites(pagenumber, pagesize)
                .map(new HttpResultFunc<>())
                .compose(io_main());
    }

    //Post复杂的Json对象
    public Observable<Boolean> addActivity(ActivityArticle activity) {
        return mApiService.addActivityTheme(activity)
                .map(new BooleanResultFunc<>())
                .compose(io_main());
    }

}
           

统一的异常返回处理,需要根据自己服务端返回的格式做适配

public class ErrorAction implements Consumer<Throwable> {
    @Override
    public void accept(Throwable throwable) throws Exception {
        Logger.e("异常日志", throwable);
        if (throwable instanceof ConnectException || throwable instanceof UnknownHostException) {
            ToastUtils.showShortSafe("网络错误");
        } else if (throwable instanceof SocketTimeoutException) {
            ToastUtils.showShortSafe("连接超时,请重试");
        } else if (throwable instanceof HttpException) {
            ToastUtils.showShortSafe("服务器错误(" + ((HttpException) throwable).code());
        } else if (throwable instanceof ApiException) {
            onApiError((ApiException) throwable);
        } else {
            //未知错误,最好将其上报给服务端,供异常排查
            if (!TextUtils.isEmpty(throwable.getMessage())) {
                ToastUtils.showShortSafe(throwable.getMessage());
            }
        }
    }
    public void onApiError(ApiException throwable) {
        //有errorMsg优先吐msg,没有吐errcode,两者区别:msg一般是比较友好的中文说明
        if (throwable.getMessage() != null)
            ToastUtils.showShortSafe(throwable.getMessage());
        else if (throwable.getErrorCode() != null) {
            ToastUtils.showShortSafe(throwable.getErrorCode());
        }
    }
}
           

拦截器,内置了用户身份认证拦截器

@Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request.Builder builder = chain.request().newBuilder().addHeader("User-Agent", APP_TAG); // 标明发送本次请求的客户端
        Logger.d(APP_TAG);
        //如果用户已经登陆,每次请求头带上token
        if (StringUtils.isNotBlank(AccountManager.getInstance().getToken())) {
            builder.addHeader("token", AccountManager.getInstance().getToken());
            Logger.i("token:%s", AccountManager.getInstance().getToken());
        }
        Request request = builder.build();
        Response response = null;
        try {
            response = chain.proceed(request);
            String responseBody = getResponse(response);
            //处理登录过期情况
            handleTokenExpired(responseBody);
        } catch (IOException e) {
            Logger.e(e.getMessage());
        }
        return response;
    }
           

如果需要拓展拦截器,可以

NetConfig.init(this)
                .withApiHost(BuildConfig.DEBUG ? Constant.API_URL.ENV_DEV : Constant.API_URL.ENV_PROD) //调试环境Url Or 生产环境Url
                .isNetLogDebug(BuildConfig.DEBUG)  //生产环境不打印网络日志
                .withInterceptors(Arrays.asList(new CookieInterceptors()))
                .configure();
           

一般的请求

ApiService.getInstance().getActivites(1, 20)
                .subscribe(activityArticles -> ToastUtils.showShort("请求成功"), new ErrorAction());
           

Post复杂的Json对象

ActivityArticle article = new ActivityArticle();
        article.title = "ArchSummit全球架构师峰";
        article.articleurl = "https://www.oschina.net/event/2288817";
        article.image1url = "https://static.oschina.net/uploads/space/2018/1102/181140_cfy7_3843409.jpg";
        article.image2url = "https://static.oschina.net/uploads/space/2018/1102/181157_MYXH_3843409.jpg";
        ApiService.getInstance().addActivity(article)
                .subscribe(aBoolean -> ToastUtils.showShort("请求成功"), new ErrorAction());
           

单个文件上传监听进度+其他表单参数

FileUploadService.getInstance().uploadAvatar2(uploadFile, "20180607090729115PHHBSWE", new FileUploadObserver<ResponseBody>() {
            @Override
            public void onUpLoadSuccess(ResponseBody responseBody) {
                ToastUtils.showShort("文件上传成功");
            }

            @Override
            public void onUpLoadFail(Throwable e) {
                ToastUtils.showShort("文件上传失败");
            }

            @Override
            public void onProgress(int progress) {
                Logger.d("下载进度为:%d", progress);
            }
        });
           

多文件上传监听进度+其他表单参数

FileUploadService.getInstance().uploadIssueReport(files, "00000001", "开不了了", "真的开不了了",
                new FileUploadObserver<ResponseBody>() {
                    @Override
                    public void onUpLoadSuccess(ResponseBody responseBody) {
                        ToastUtils.showShort("文件上传成功");
                    }

                    @Override
                    public void onUpLoadFail(Throwable e) {
                        ToastUtils.showShort("文件上传失败");
                    }

                    @Override
                    public void onProgress(int progress) {
                        Logger.d("下载进度为:%d", progress);
                    }
                });
           

文件下载监听进度

public void download() {
        String url = "http://192.168.1.8:8080/rent/assets/img/bikefault/20181125222314579QDUGWYOO1.png";
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            //sd卡已挂载
            String fileDirPath = new StringBuilder().append(Environment.getExternalStorageDirectory().getAbsolutePath()) + "/AndFix/";
            File fileDir = new File(fileDirPath);
            try {
                if(null == fileDir || !fileDir.exists()) {
                    fileDir.mkdir();
                }
                Logger.d("存储位置:%s", fileDirPath.concat(System.currentTimeMillis() + ".").concat(FileUtils.getFileExtension(url)));
                FileDownloadService.getInstance(new IDownloadListener() {
                    @Override
                    public void onDownloadSuccess() {
                        ToastUtils.showShortSafe("文件下载成功");
                    }

                    @Override
                    public void onDownloadFail(Exception exception) {
                        ToastUtils.showShortSafe("文件下载失败");
                    }

                    @Override
                    public void onProgress(int progress) {
                        Logger.d("文件下载%d", progress);
                    }
                }).download(url, fileDirPath.concat(System.currentTimeMillis() + ".").concat(FileUtils.getFileExtension(url)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            ToastUtils.showShortSafe("SD卡未挂载");
        }
    }