App网络请求实战一:Rxjava+Retrofit的初步封装

App网络请求Rxjava+Retrofit的初步封装

​ 现在的App在网络请求方面都是用Rxjava和Retrofit这一套东西。来,下面我们按照下面的思路来想。为什么要封装–>决定要封装了,该从哪方面入手,或者说应该怎么封装。注意:本文默认你会使用Rxjava和Retrofit,重在探讨封装思路,皮得我们就不谈了。

老规矩上图

这里写图片描述

为什么要封装

想一想一个App里面,起码得有几十个Api请求吧? 如果不封装,相当于每一个请求都要进行判断,比如对返回码code进行判断。这样不仅很烦,而且重复代码很多,所以需要进行封装。又因为每个请求返回的格式都是一样的,返回的状态码也就那些,所以我们可以分别针对请求成功和请求失败进行封装,通俗点就是统一处理。

如何封装

1.首先我们要想,我们要的是数据对吧,那么首先我们对网络请求返回的数据进行分类。如下图所示:

这里写图片描述

说白了,我们只需要把握住两个关口。一个是成功,一个是失败。失败又包含:服务端返回码不等于1和其他的原因导致,如连接超时,解析错误等。

2.嘿嘿嘿,似乎有点思路了哦,那我们该怎么对所有的返回数据进行统一处理呢? 毕竟每个请求返回的数据又不是一样的。那我们能不能用一个实体类来表示所有的返回数据呢,如果能的话,那么就肯定能统一处理了。是的,java里真的有这个东西叫泛型。那就创建一个BaseResponse类,如下所示:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/19
 *     描述   : 这个需要注意的是,Code Message Data必须和后台定义的属性名称一致
 *     版本   : 1.0
 * </pre>
 */
public class BaseResponse<T>
{
    private int Code;
    private String Message;
    private T Data;

    public int getCode()
    {
        return Code;
    }

    public void setCode(int code)
    {
        Code = code;
    }

    public String getMessage()
    {
        return Message;
    }

    public void setMessage(String message)
    {
        Message = message;
    }

    public T getData()
    {
        return Data;
    }

    public void setData(T data)
    {
        Data = data;
    }
}

需要注意的是,这个需要后台配合的。就是说每个请求返回的数据格式必须得和上面一样,否则会有问题。如果后台不配合的话,那就干死他,嘿嘿嘿。上面是表示的是,我们最终需要的那个对象T中的数据。比如说我们有一个返回的实体类是这个样子的:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/19
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public class ResEntity1
{


    /**
     * Data : {"res":"返回成功 "}
     * Code : 1
     * Message : ok
     */

    private DataBean Data;
    private int Code;
    private String Message;

    public DataBean getData()
    {
        return Data;
    }

    public void setData(DataBean Data)
    {
        this.Data = Data;
    }

    public int getCode()
    {
        return Code;
    }

    public void setCode(int Code)
    {
        this.Code = Code;
    }

    public String getMessage()
    {
        return Message;
    }

    public void setMessage(String Message)
    {
        this.Message = Message;
    }

    public static class DataBean
    {
        /**
         * res : 返回成功
         */

        private String res;

        public String getRes()
        {
            return res;
        }

        public void setRes(String res)
        {
            this.res = res;
        }
    }

    @Override
    public String toString()
    {
        return "ResEntity1{" +
                "Data=" + Data +
                ", Code=" + Code +
                ", Message='" + Message + '\'' +
                '}';
    }
}

那么我们在ApiService中可以这么写了哦:

@GET("tools/mockapi/440/yx0419")
Observable<BaseResponse<ResEntity1.DataBean>> getHttpData1();

然后在项目中可以这么写:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(ApiService.baseUrl)
        //这里的client当然可以自己配置
        .client(new OkHttpClient())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build();

apiService = retrofit.create(ApiService.class);
Observable<BaseResponse<ResEntity1.DataBean>> httpData1 = apiService.getHttpData1();

3.好像是那个意思,但是,等等!上面那句代码的意思是期望网络请求返回一个泛型是ResEntity1.Data的BaseResponse对象。但是我们最终希望返回的是ResEntity.Data数据,那有没有一种方法是这样的呢,即输入BaseResponse输出ResEntity1.Data。如果你知道Rxjava,你肯定知道是有的。Rxjava1中叫Func1,Rxjava2中叫Function,利用map操作符进行转换。

/**
 * A functional interface that takes a value and returns another value, possibly with a
 * different type and allows throwing a checked exception.
 *
 * @param <T> the input value type
 * @param <R> the output value type
 */
public interface Function<T, R> {
    /**
     * Apply some calculation to the input value and return some other value.
     * @param t the input value
     * @return the output value
     * @throws Exception on error
     */
    R apply(T t) throws Exception;
}

所以我们可以写一个处理BaseResponse的类,如下所示:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/19
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public class HttpResultFunc<T> implements Function<BaseResponse<T>, T>
{
    @Override
    public T apply(BaseResponse<T> response) throws Exception
    {
        //只有当返回的code==success时才成功,其余情况全部抛出错误
        if (response.getCode() == Constants.HTTP_SUCCESS)
        {
            return response.getData();
        } else
        {
            //抛出异常,让rxjava捕获,便于统一处理
            throw new ApiException.ServerException(response.getCode(), response.getMessage());
        }
    }
}

只有当响应码等于代表成功时,才返回;否则都抛出异常,而这个异常会被发送到onError中。注意哦,这个自定义的异常属于服务端返回的响应码不等于1的异常。我们还有一种异常是非服务端异常。所以我们必须写一个错误处理类来进行统一处理,如下所示:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/19
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public class ApiException extends Exception
{
    private int errorCode;
    private String msg;

    private ApiException(Throwable throwable, int errorCode)
    {
        super(throwable);
        this.errorCode = errorCode;
        this.msg = throwable.getMessage();
    }

    public static ApiException handlerException(Throwable throwable)
    {
        ApiException exception = null;
        if (throwable instanceof HttpException)
        {
            HttpException httpException = (HttpException) throwable;
            exception = new ApiException(httpException, httpException.code());
            try
            {
                exception.setMsg(httpException.response().errorBody().string());
            } catch (IOException e)
            {
                e.printStackTrace();
                exception.setMsg(e.getMessage());
            }
        } else if (throwable instanceof SocketTimeoutException || throwable instanceof ConnectException ||
                throwable instanceof ConnectTimeoutException || throwable instanceof UnknownHostException)
        {
            exception = new ApiException(throwable, TIMEOUT_ERROR);
            exception.setMsg("网络连接超时,请检查您的网络状态,稍后重试!");
        } else if (throwable instanceof NullPointerException)
        {
            exception = new ApiException(throwable, NULL_POINTER_EXCEPTION);
            exception.setMsg("空指针异常");
        } else if (throwable instanceof SSLHandshakeException)
        {
            exception = new ApiException(throwable, SSL_ERROR);
            exception.setMsg("证书验证失败");
        } else if (throwable instanceof ClassCastException)
        {
            exception = new ApiException(throwable, CAST_ERROR);
            exception.setMsg("类型转换错误");
        } else if (throwable instanceof IllegalStateException)
        {
            exception = new ApiException(throwable, ILLEGAL_STATE_ERROR);
            exception.setMsg(throwable.getMessage());
        } else if (throwable instanceof JsonParseException || throwable instanceof JSONException
                || throwable instanceof JsonSyntaxException || throwable instanceof JsonSerializer
                || throwable instanceof NotSerializableException || throwable instanceof ParseException)
        {
            exception = new ApiException(throwable, PARSE_ERROR);
            exception.setMsg("解析错误");
        } else if (throwable instanceof ServerException)
        {
            int errorCode = ((ServerException) throwable).getErrorCode();
            String msg = ((ServerException) throwable).getErrorMsg();
            exception = new ApiException(throwable, errorCode);
            exception.setMsg(msg);
        } else
        {
            exception = new ApiException(throwable, UNKNOWN);
            exception.setMsg("未知错误");
        }
        return exception;
    }

    private static String getErrorMsgByErrorCode(int errorCode)
    {
        String msg = "";
        switch (errorCode)
        {
            case Constants.HTTP_NO_LOGIN:
                msg = "未登录";
                return msg;
            default:
                return "未知错误";
        }
    }

    public int getErrorCode()
    {
        return errorCode;
    }

    public void setErrorCode(int errorCode)
    {
        this.errorCode = errorCode;
    }

    public String getMsg()
    {
        return msg;
    }

    public void setMsg(String msg)
    {
        this.msg = msg;
    }

    @Override
    public String toString()
    {
        return "ApiException{" +
                "errorCode=" + errorCode +
                ", msg='" + msg + '\'' +
                '}';
    }

    /**
     * 约定异常
     */
    public static class ERROR
    {
        /**
         * 未知错误
         */
        public static final int UNKNOWN = 1000;
        /**
         * 连接超时
         */
        public static final int TIMEOUT_ERROR = 1001;
        /**
         * 空指针错误
         */
        public static final int NULL_POINTER_EXCEPTION = 1002;

        /**
         * 证书出错
         */
        public static final int SSL_ERROR = 1003;

        /**
         * 类转换错误
         */
        public static final int CAST_ERROR = 1004;

        /**
         * 解析错误
         */
        public static final int PARSE_ERROR = 1005;

        /**
         * 非法数据异常
         */
        public static final int ILLEGAL_STATE_ERROR = 1006;


    }


    public static class ServerException extends RuntimeException
    {
        private int errorCode;
        private String errorMsg;

        public ServerException(int errorCode, String errorMsg)
        {
            this.errorCode = errorCode;
            this.errorMsg = errorMsg;
        }

        public int getErrorCode()
        {
            return this.errorCode;
        }

        public String getErrorMsg()
        {
            return this.errorMsg;
        }

    }
}

能懂不,胸低们? 这个时候我们就可以在项目中这么写了:

Observable<ResEntity1.DataBean> compose = apiService.getHttpData1()
        .map(new HttpResultFunc<ResEntity1.DataBean>())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

线程切换占了两行,尼玛不爽。注意到Observable提供了一个compose方法:

/**
 * Transform an ObservableSource by applying a particular Transformer function to it.
 */
@SchedulerSupport(SchedulerSupport.NONE)
public final <R> Observable<R> compose(ObservableTransformer<T, R> composer) {
    return wrap(composer.apply(this));
}

大意就是通过我们指定的转换操作来对流进行转换。参数是一个实现了ObservableTransformer接口的类,来看下ObservableTransformer是神魔恋:

/**
 * Interface to compose Observables.
 *
 * @param <Upstream> the upstream value type
 * @param <Downstream> the downstream value type
 */
public interface ObservableTransformer<Upstream, Downstream> {
    /**
     * Applies a function to the upstream Observable and returns an ObservableSource with
     * optionally different element type.
     * @param upstream the upstream Observable instance
     * @return the transformed ObservableSource instance
     */
    ObservableSource<Downstream> apply(Observable<Upstream> upstream);
}

擦,又是输入输出。注意下我们这里不想改变流了,皮得我们就不谈了,所以我们可以这么写:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/19
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public class SchedulerTransformer<T> implements ObservableTransformer<T, T>
{
    @Override
    public ObservableSource<T> apply(Observable<T> upstream)
    {
        return upstream
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }
}

输入和输出都是T。所以一番改进后,项目里的切换线程可以这么写了:

Observable<ResEntity1.DataBean> compose = apiService.getHttpData1()
        .map(new HttpResultFunc<ResEntity1.DataBean>())
        .compose(RxSchedulers.<ResEntity1.DataBean>io_main());

上游事件到此结束咯,接下来搞下游事件。

4.接下来看一看,下游事件是神魔恋:

首先我们看看最原始的写法:

compose.subscribe(new Observer<ResEntity1.DataBean>()
{
    @Override
    public void onSubscribe(Disposable d)
    {

    }

    @Override
    public void onNext(ResEntity1.DataBean value)
    {

    }

    @Override
    public void onError(Throwable e)
    {

    }

    @Override
    public void onComplete()
    {

    }
});

我滴个龟龟,这要是在项目里这么写,那代码还不得几千行啊。所以需要去除那些重复的和我们不怎么关心的方法,这里需要关注下onSubscribe(Disposable d),我们可以利用这个来取消网络。所以我们可以写一个抽象类来实现这个Observer接口,如下所示:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/19
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public abstract class BaseObserver<T> implements Observer<T>
{
    protected RxManager rxManager;

    public BaseObserver(RxManager rxManager)
    {
        this.rxManager = rxManager;
    }

    @Override
    public void onSubscribe(Disposable d)
    {
        rxManager.add(d);
    }

    @Override
    public void onComplete()
    {

    }

    @Override
    public void onError(Throwable e)
    {
        //统一处理错误
        String msg = ApiException.handlerException(e).getMsg();
        int errorCode = ApiException.handlerException(e).getErrorCode();
        if (msg.length() > 64)
        {
            msg = msg.substring(0, 64);
        }
        if (errorCode == Constants.HTTP_NO_LOGIN)
        {
            //跳转至登录页面
            Intent intent = new Intent(App.getAppContext(), LoginActivity.class);
            intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
            App.getAppContext().startActivity(intent);
        }
        onErrorMsg("错误码:" + errorCode + "\n" + msg);
    }

    /**
     * 返回错误字符串
     *
     * @param msg
     */
    protected abstract void onErrorMsg(String msg);

    @Override
    public abstract void onNext(T t);

}

注意我们在onError里面统一进行错误处理,包含服务端的和非服务端的,还有一个我们需要单独处理。就是当errorCode等于未登录时,我们需要跳转登录页,并clear掉当前任务栈。当然这里还有一些其他的处理方式,并不一定非要用app来跳转。当判断errorMsg的长度大于64时,我就截取前64个字符串,免得错误字符串太长。还有一个地方就是用Rxmanager来管理网络请求,这个可以防止当Activity销毁时,网络还在请求的,导致出现空指针异常。综上项目中我们可以这么写:

Observable<ResEntity1.DataBean> compose = apiService.getHttpData1()
        .map(new HttpResultFunc<ResEntity1.DataBean>())
        .compose(RxSchedulers.<ResEntity1.DataBean>io_main());

compose.subscribe(new BaseObserver<ResEntity1.DataBean>(rxManager)
{
    @Override
    protected void onErrorMsg(String msg)
    {
    }

    @Override
    public void onNext(ResEntity1.DataBean dataBean)
    {
        mTextView.setText(dataBean.getRes());
        Toast.makeText(MainActivity.this, dataBean.getRes(), Toast.LENGTH_SHORT).show();
    }
});

5.到这里,基本上一个简单的封装就已经完成了。当然啦,如果全部的请求都在Activity或者Fragment写,会造成臃肿以及难以维护的现象。后面我会写,token的刷新,以及activity太过于臃肿而引入P层等。

github地址:https://github.com/xiaokun19931126/HttpExceptionDemo

以上。

上篇博客:动手写一个状态布局
下篇博客:App网络请求实战二:继续封装以及Interceptor拦截器的使用场景分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值