用Retrofit+RxJava2封装优雅的网络请求框架

最近难得赋闲在家,网上看到几篇讲Retrofit2的文章,发现自己以前Android项目中使用的封装方式反而更加简单易用,所以决定花点时间整理分享一下,让做Android开发的小伙伴们可以更优雅的处理网络请求。

系列文章:
最简单易懂的Retrofit2源码详解
秒懂Retrofit中的GsonConverterFactory

  • 概述
  • 封装背景
  • 封装方法
    • 构建返回结果映射类型
    • 定义Retrofit请求接口
    • 获取Retrofit实例并封装请求接口
    • 异常类封装
    • 用于处理回调的Subscriber类
  • 如何使用
  • 总结:

概述

首先,这是一篇讲解如何封装RetrofitRxJava2的文章,所以需要阅读者对RetrofitRxJava2有一定的了解,不然不太容易看明白。现在OkHttp + Retrofit 基本成为Android开发中网络请求部分的标配,所以每个做Android开发的小伙伴都应该花点时间去学习一下。

官方网站:

Retrofit官网
A type-safe HTTP client for Android and Java.

OkHttp官网
An HTTP & HTTP/2 client for Android and Java applications.

RxJava2 Github 托管地址
RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.

封装背景

如果只是单独使用retrofit 不做任何封装的话会存在以下几个问题。

  • 项目中会出现许多重复的网络处理代码。
  • 网络请求参数耦合严重,一旦修改网络请求的方式或者参数时,需要修改大量的代码,容易引入错误
  • 框架耦合

封装方法

首先我们网络请求的方式以及后台返回的数据格式都是预先设计好的,属于接口协议,是稳定的。

例如:
请求:使用post 方法,以 request body形式向后台发起http请求。
返回结果:为如下格式的 json

{
    "body":
         {
            "name":"ben",
            "age":"28",
            "gender":"man"
         },
        "code":"0",
        "status":"0"
}

status 表示处理状态 “0”:正确 “1”:错误;code 表示错误码,当status 为“1”时,会产生相应的错误码,当status为“0”时 code为“0”;body 表示返回的结果数据

构建返回结果映射类型

根据我们定义的返回数据格式,设计映射类型

public class ResponseResult
{
    private JsonElement body;
    private String code;
    private String status;

    public JsonElement getBody()
    {
        return body;
    }

    public void setBody(JsonElement body)
    {
        this.body = body;
    }

    public String getCode()
    {
        return code;
    }

    public void setCode(String code)
    {
        this.code = code;
    }

    public String getStatus()
    {
        return status;
    }

    public void setStatus(String status)
    {
        this.status = status;
    }

    @Override
    public String toString()
    {
        return "ResponseResult{" +
                "body='" + body + '\'' +
                ", code='" + code + '\'' +
                ", status='" + status + '\'' +
                '}';
    }
}

定义Retrofit请求接口

根据定义的请求方式,构建请求接口函数

public interface CommonQueueService
{
    @POST("")
    Observable postRxBody( @Url String interfaceName, @Body Map reqParamMap);
}

如果是以表单的形式发起请求可以参考如下代码

    @FormUrlEncoded
    @POST("queue")
    Observable postRxString(@FieldMap Map reqParamMap);

获取Retrofit实例并封装请求接口

此帮助类以单例的形式向外暴露接口,共有两个网络请求接口函数

1、用于处理返回数据较为简单,不需要大量加工的情形

public void startServerRequest(Observer subscriber, String interfaceName, Map reqParamMap, boolean isObserveMainThread){... }
参数1:外部调用传入,在里面获得回调结果,结果类型为String
参数2:接口相对url
参数3:request body
参数4:回调结果是否在UI线程

2、用于返回数据类型较为复杂,需要大量加工的情形
public void startServerRequest(Observer subscriber,Function mapper, String interfaceName, Map reqParamMap){...}
参数1:外部调用传入,在里面获得回调结果,结果类型为泛型
参数2:外部调用传入,可以在里面对后台返回的数据做加工,加工成自己想要的样子,此工作默认在子线程中执行
参数3:接口相对url
参数4:request body

下面是完整代码

public class HttpMethods
{
    private static final String TAG = HttpMethods.class.getSimpleName();
    private static final int DEFAULT_TIMEOUT = 20;
    private Retrofit retrofit;
    private Retrofit specialRetrofit;
    private OkHttpClient okHttpClient;

    private HttpMethods()//构造方法私有
    {
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        builder.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        builder.addInterceptor(logging);
        okHttpClient = builder.build();
        retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(new Gson()))  
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(Api.SERVER_SITE)
                .build();
    }
    private static class SingletonHolder
    {
        private static final HttpMethods INSTANCE = new HttpMethods();
    }
    public static HttpMethods getInstance()    //获取单例
    {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 处理网络请求结果,返回的是后台接口的body里面的字符串
     * @param subscriber
     * @param interfaceName
     * @param reqParamMap
     * @param isObserveMainThread
     */
    public void startServerRequest(Observer subscriber, String interfaceName, Map reqParamMap, boolean isObserveMainThread)
    {
        CommonQueueService service = retrofit.create(CommonQueueService.class);
        //Logger.t(TAG).d(String.format("接口请求数据:%s  %s ",interfaceName, new Gson().toJson(reqParamMap)));
        Observable observable = service.postRxBody(interfaceName,reqParamMap)
                .map(new ResponseResultMapper());
        toSubscribe(observable, subscriber, isObserveMainThread);
    }

    /**
     * 处理网络请求结果,将结果转换成的类型交给使用者处理
     * 此方法的优秀之处在于将数据处理完全放在了工作线程,转换成用户的目标类型后才切换到UI线程
     * @param subscriber
     * @param mapper
     * @param interfaceName
     * @param reqParamMap
     * @param 
     */
    public void startServerRequest(Observer subscriber,Function mapper, String interfaceName, Map reqParamMap)
    {
        CommonQueueService service = retrofit.create(CommonQueueService.class);
        //Logger.t(TAG).d(String.format("接口请求数据:%s  %s ",interfaceName, new Gson().toJson(reqParamMap)));
        Observable observable = service.postRxBody(interfaceName,reqParamMap)
                .map(new ResponseResultMapper()).map(mapper);
        toSubscribe(observable, subscriber, true);
    }

    //观察者启动器
    private  void toSubscribe(Observable o, Observer s, boolean isMainThread)
    {
        Scheduler observeScheduler = Schedulers.io();
        if (isMainThread)
            observeScheduler = AndroidSchedulers.mainThread();
        o.subscribeOn(Schedulers.io()) //绑定在io
                .observeOn(observeScheduler) //返回 内容 在Android 主线程
                .subscribe(s);  //放入观察者
    }

    /**
     * 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber
     */
    private class ResponseResultMapper implements Function<ResponseResult, String>
    {
        @Override
        public String apply(@NonNull ResponseResult httpResult) throws Exception
        {
            if (httpResult == null)
            {
                throw new NullPointerException("|返回结果为null|");
            }
            //Logger.t(TAG).d("服务器返回结果" + AppController.getInstance().getGsonInstance().toJson(httpResult,ResponseResult.class));
            if ("1".equals(httpResult.getStatus()))
            {
                String bodyStr = httpResult.getBody();
                String codeStr = httpResult.getCode();
                throw new ApiException(codeStr == null ? "" : codeStr, bodyStr == null ? "" : bodyStr);
            }
            return TextUtils.isEmpty(httpResult.getBody().toString()) ? "{}" : httpResult.getBody().toString();
        }
    }
}

简单对上面的代码做个解释

首先在网络帮助类的私有构造函数中,构建了OkHttp的实例,我们也为OkHttp设置了Log拦截器,这样就能打印log了。
然后构建了Retrofit的实例,将OkHttp作为其客户端,我们通过.addConverterFactory(GsonConverterFactory.create(new Gson()))设置了json转换器,以便于retrofit将json字符串转换为我们的目标 java object。通过.addCallAdapterFactory(RxJava2CallAdapterFactory.create())集成了RxJava2,如果不添加这个adapterretrofit 只能返回Call类型,通过添加这个adapter,我们就可以返回RxJava2的类型了,进而使用RxJava2了。

至此,我们成功构建了符合我们需求的Retrofit实例

异常类封装

此类用于封装异常

public class ApiException extends RuntimeException
{
    private String errorCode;
    private String errorBody;

    public ApiException(String detailMessage)
    {
        this(detailMessage,"{}");
    }

    public ApiException(String detailMessage, String errorBody)
    {
        super(detailMessage);
        this.errorBody = errorBody;
        this.errorCode=detailMessage;
    }

    public String getErrorCode()
    {
        return errorCode;
    }

    public String getErrBody()
    {
        return errorBody;
    }
}

用于处理回调的Subscriber类

public class SilenceSubscriber implements Observer
{
    private final static String TAG = SilenceSubscriber.class.getSimpleName();

    @Override
    public void onComplete()
    {

    }

    @Override
    public void onSubscribe(Disposable d)
    {

    }

    @Override
    public void onError(Throwable e)
    {
        try
        {
            if (e instanceof SocketTimeoutException)
            {
                Logger.t(TAG).d("SocketTimeoutException 网络中断,请检查您的网络状态>" + e.getMessage());
                onHandledNetError(e);
                e.printStackTrace();
            }
            else if (e instanceof SocketException)
            {
                Logger.t(TAG).d("SocketException 网络中断,请检查您的网络状态>" + e.getMessage());
                onHandledNetError(e);
                e.printStackTrace();
            }
            else if (e instanceof ApiException)
            {
                String errCode=((ApiException)e).getErrorCode();
                Logger.t(TAG).d("错误码为》"+errCode);
                if (ErrorCodeTable.handleSpecificError(errCode))
                    return;
                onHandledError((ApiException) e);
            }
            else
            {
                e.printStackTrace();
                onHandledNetError(e);
                Logger.t(TAG).d("网络请求发生了没有处理异常 网络中断,请检查您的网络状态>" + e.getMessage());
            }
        } catch (Exception e1)
        {
            e1.printStackTrace();
        }
    }

    //要处理特殊的错误码,重写这个函数,需要展示toast的调用super,不需要就不调用--wb
    //1:如果要特殊处理“GAME_OVER”,而且不希望弹出toast,那么就重写此函数,且不调用super。
    //2:如果不需要特殊处理“GAME_OVER”,只是想弹出“游戏结束”的toast,那么把“GAME_OVER”放入码表里面解析成对应的文案就好了。不要重写此函数!
    //3: 如果要将后台返回的提示直接显示成toast,不做任何处理,不要重写此函数。
    public void onHandledError(ApiException apiE)
    {
        Logger.t(TAG).d("父类onHandledError调用》"+apiE.getErrorCode()+" "+apiE.getErrBody());
        String errMsg= ErrorCodeTable.parseErrorCode(apiE.getErrorCode());//码表里只存放不需要特殊处理只需要显示toast的错误码。
        if (!TextUtils.isEmpty(errMsg))
            ToastUtils.showShort(errMsg);
    }

    public void  onHandledNetError(Throwable throwable)
    {
        Logger.t(TAG).d("onHandledNetError》"+ (throwable==null?"null":throwable.getMessage()));
    }

    @Override
    public void onNext(T response)
    {
        Logger.t(TAG).d("onNext》"+response);
    }
}

如何使用

第一种返回简单数据的情形:

例如我们要调用登录这个接口,首先构建请求的参数map,然后传入接口名称,选择是否要将结果切换到UI线程。

    public void login(final String username, String password)
    {
        Map reqMap = RequestHeader.getCommonPartOfParam();
        reqMap.put(RequestHeader.U_NAME, username);
        reqMap.put(RequestHeader.U_PASSWORD, password);
        HttpMethods.getInstance().startServerRequest(new SilenceSubscriber()
        {
            @Override
            public void onNext(String response)
            {
                super.onNext(response);
                //此处获得了后台返回的body里面的数据
            }
        }, Api.SYSTEMC_FEEDBACK, reqMap, true);
    }

第二种:返回复杂数据,需要对数据加工的情形:

例如我们调用获取用户详情这个接口,构建请求map,传入接口参数,传入一个对返回数据做加工的Function,然后在subscriber里面的onNext中获得最终需要的数据。

    public void getUser(final String tUserId)
    {
        Map reqMap = RequestHeader.getCommonPartOfParam();
        reqMap.put(RequestHeader.USER_ID, tUserId);
        HttpMethods.getInstance().startServerRequest(new SilenceSubscriber()
        {
            @Override
            public void onNext(UserBean user)
            {
                super.onNext(user);
                //在UI线程中获取到了最终处理过的数据
            }
        }, new Function()
        {
            @Override
            public UserBean apply(String response) throws Exception
            {
                return new Gson().fromJson(response, UserBean.class);
            }
        },Api.USERC_QUERYUSERINFO, reqMap);
    }

上面处理了请求成功的情形,请求失败也只是使用了默认处理,如果用户需要特殊处理某些失败情形,可以重写SilenceSubscriber中相应的方法。

总结:

本文介绍的方法在我们的项目中运行的很好,希望可以给苦逼的从事Android开发的程序员一些启发,那样我就很欣慰了。

你可能感兴趣的:(Java,Android)