网络请求:retrofit2 + rxjava2 的基本上使用以及简单封装

retrofit2是一款封装okhttp的优秀的网络请求框架,搭配rxjava时有出人意料的效果,每当出去面试的时候,被问及的频率也是极高的,于是新项目里也就顺理成章的接入了retrofit2+rxjava,并做了简易的封装,趁着今天周六,写一篇文章来记录一下。

1.retrofit 网络请求

github:https://github.com/square/retrofit

我们先来使用retrofit进行一个简单的网络请求,获取必应搜索的每日一图,这是一个简单的get请求,返回数据为一个json对象,每日一图的地址需要从对象里来获取。

api:https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN

1.1先定义一个接口:


public interface NetService {

    @GET("HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN")
    Call getImage();
}

baseUrl截取为 https://cn.bing.com/  作为一个常量,放在常量类中。FirstImageBean为返回的json对应的实体类,可使用gsonformat直接生成,不做赘述。

1.2.在activity中进行请求

  public void getImage() {

//      1.获取retrofit对象
        Retrofit build = new Retrofit.Builder()
                .baseUrl(UrlConstant.URL_BING1)
                .addConverterFactory(GsonConverterFactory.create())//指定json解析
                .build();
//      获取接口类对象
        NetService netService = build.create(NetService.class);
        netService.getImage().enqueue(new Callback() {
            @Override
            public void onResponse(Call call, retrofit2.Response response) {
                String url = response.body().getImages().get(0).getUrl();
            }
            @Override
            public void onFailure(Call call, Throwable t) {
                LogUtil.i("请求失败:" + t.getMessage());
            }
        });
    }

2加入rxjava再来一次网络请求

2.1重新定义接口,这次我们返回的是个Obervable对象

  @GET("HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN")
    Observable getBingDayImage();

2.2.在Activty中进行请求

 /**
     * 获取必应搜索每日图片
     */
    private void getBingImage() {
        OkHttpClient build = new OkHttpClient.Builder().build();
        Retrofit retrofit = new Retrofit.Builder()
                .client(build)
                .baseUrl(UrlConstant.URL_BING1)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
        NetService netService = retrofit.create(NetService.class);
        //获取每日一图
        netService.getBingDayImage()
                .subscribeOn(Schedulers.io())//
                .observeOn(AndroidSchedulers.mainThread())//指定观察者执行在主线程
                .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(Disposable d) { }

                    @Override
                    public void onNext(FirstImageBean firstImageBean) {
                        image_url = UrlConstant.URL_BING + firstImageBean.getImages().get(0).getUrl();
                        LogUtil.i("retrofit:" + image_url);
                        mHandler.sendEmptyMessage(0);
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }

可以看到,在构建retrofit对象的时候,我我们加了.addCallAdapterFactory(RxJava2CallAdapterFactory.create()),这句话为添加rxjava时所必须的。

3.网络请求封装

经过前2步,我们可以看到使用retrofit有很多代码都是重复的,所以我们可以写个管理类出来,统一提供出一个retrofit对象,甚至可以统一提供出一个接口对象,这样就避免了很多重复的代码。

public class Retrofit2Util {
    private static Retrofit2Util retrofit2Util;
    private static Retrofit retrofitClient;
    private static OkHttpClient client;

    private Retrofit2Util() { }
    
    public static Retrofit2Util getInstance() {
        if (retrofit2Util == null) {
            synchronized (Retrofit2Util.class) {
                if (retrofit2Util == null) {
                    retrofit2Util = new Retrofit2Util();
                    client = getClient();
                    retrofitClient = getRetrofit();
                }
            }
        }
        return retrofit2Util;
    }

    public static Retrofit getRetrofit() {
        if (retrofitClient == null) {
            synchronized (Retrofit2Util.class) {
                if (retrofitClient == null)
                    retrofitClient = new Retrofit.Builder()
                            .client(client)
                            .baseUrl(UrlConStant.URL_BASE)
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                            .build();
            }
        }

        return retrofitClient;
    }

    public static OkHttpClient getClient() {
        OkHttpClient build = new OkHttpClient.Builder()
                .addInterceptor(getLoggerInterceptor())//打印日志拦截器
                .addInterceptor(getInterceptor())//添加公共参数拦截器
                .build();
        return build;
    }

    public NetService getNetService(){
        NetService netService = retrofitClient.create(NetService.class);
        return netService;
    }

    /**
     * 基础公共参数适配器
     *
     * @return
     */
    private static CommonParamsInterceptor getInterceptor() {
        return new CommonParamsInterceptor();
    }

    /**
     * 拦截器 - 打印日志
     * @return
     */
    private static Interceptor getLoggerInterceptor() {

        Interceptor interceptor = new okhttp3.logging.HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY);

        return interceptor;
    }


}

可以看到,我们在这个工具类里采用单例模式初始化了一个retrofit对象,并提供出一个静态方法来供外部调用。

在代码中进行网络请求时,我们可以这样来写:

 Retrofit2Util.getInstance()
                .getNetService()..

这么一看,是不是节省了很多的重复代码?

同时在实际开发中,我们的返回实体类一般包含一个状态码,一个描述信息以及一个实体类,这样我们可以做个简单的基类:

public class BaseResponse {
    private int status;
    private String msg;
    private T data;

    public int getStatus() {
        return status;
    }

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

    public String getMsg() {
        return msg;
    }

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

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

现在再来看一下我们对返回结果的处理上,但用retrofit时,回调方法只有2个,即一个成功,一个失败,清楚明了,但是我们加上rxjava之后,需要重写四个回调方法,极度影响代码阅读,毕竟我们只希望看到2个结果。

这就需要我们对Observable再做一个简单的封装了。即自定义一个BaseObservable,重写它的四个方法,并提供两个抽象方法:成功和失败,在相应的方法中执行,这样在我们使用的时候,就可以只重写2个方法就OK了。具体操作如下:

public abstract class BaseObserver implements Observer {

    public BaseObserver() { }

    @Override
    public void onSubscribe(Disposable d) { }

    @Override
    public void onComplete() {
        onFinish();
    }

    @Override
    public void onError(Throwable e) {
        onFailure(-1,e.getMessage());
        onFinish();
    }

    protected void onFinish(){}


    @Override
    public void onNext(T baseResponse) {

        if (baseResponse.getStatus()==0){
            onSuccess(baseResponse);
        }else {
            onFailure(baseResponse.getStatus(),baseResponse.getMsg());
        }
    }

    public abstract void onSuccess(T baseResponse);

    public abstract void onFailure(int errcode, String errMsg);

}

这样,在实际开发中,我们的返回结果处理可以这样写:

 ..subscribe(new BaseObserver>>() {
                    @Override
                    public void onSuccess(BaseResponse> baseResponse{
                       
                    }

                    @Override
                    public void onFailure(int errcode, String errMsg) {
                    }
                });

从代码量上来说,我们的代码进一步减少了。

现在再来看一下我们在activity中调用网络请求:

 Retrofit2Util.getInstance()
                .getNetService()
                .getEarHome()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseObserver>() {
                    @Override
                    public void onSuccess(BaseResponse baseResponse{
                       
                        }
                    }

                    @Override
                    public void onFailure(int errcode, String errMsg) {
                    }
                });

是不是简单了很多呢?

4.添加公共参数以及打印网络请求

现实开发中我们常常会携带几个公共参数去后台进行请求,比如当前的手机平台,是ios还是Android,app的版本号,以及用户userID,token等等,在每个网络接口里都加上四个参数的方法不是不能用,单就是显得逼格不高,那有没有什么方法可以一劳永逸呢?当然是有的,可以通过okhttp的拦截器来做。

回头看看我们的RetrofitUtils类,我有添加了两个拦截器:


    public static OkHttpClient getClient() {
        OkHttpClient build = new OkHttpClient.Builder()
                .addInterceptor(getLoggerInterceptor())//打印日志拦截器
                .addInterceptor(getInterceptor())//添加公共参数拦截器
                .build();
        return build;
    }
 /**
     * 基础公共参数拦截器
     *
     * @return
     */
    private static CommonParamsInterceptor getInterceptor() {
        return new CommonParamsInterceptor();
    }
    /**
     * 拦截器 - 打印日志
     * @return
     */
    private static Interceptor getLoggerInterceptor() {
        Interceptor interceptor = new okhttp3.logging.HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY);
        return interceptor;
    }

拦截器是okhttp一个很优秀的机制,retrofit是基于okhtpp的,所以这一点也被继承了过来,那先来看一看公共参数到底是怎么添加的吧:

/**
 * 网络 拦截器 添加公共参数
 * Created by zhangyanpeng on 2019/10/9
 */
public class CommonParamsInterceptor implements Interceptor {

    private static final String TYPE_GET = "GET";
    private static final String TYPE_POST = "POST";
    private static final String DEVICE_TYPE = "android";
    private static final String TOKEN = LocalDataSourceImpl.getInstance().getToken();
    private static final String DEVICE_ID = Build.SERIAL;
    private static final String APP_VERSION = AppUtils.getAppVersionName();

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (request.method().equals(TYPE_GET)) {
            request = addParamsGet(request);
        } else if (request.method().equals(TYPE_POST)) {
            request = addParamsPost(request);
        }
        return chain.proceed(request);
    }

    /**
     * POST请求添加公共参数
     */
    private Request addParamsPost(Request request) {
        if (request.body() instanceof FormBody) {
            FormBody body = (FormBody) request.body();
            FormBody.Builder builder = new FormBody.Builder();
//          复制原来的参数
            for (int i = 0; i < body.size(); i++) {
                builder.addEncoded(body.encodedName(i), body.encodedValue(i));
            }
//          添加公共参数
            builder.add("token", TOKEN);
            builder.add("deviceID", DEVICE_ID);
            builder.add("deviceType", DEVICE_TYPE);
            builder.add("appVersion", APP_VERSION);
            Request build = request.newBuilder().post(builder.build()).build();//构建新的请求
            return build;
        } else {
//          无formbady 时 调用此方法,添加公共参数
            FormBody build = new FormBody.Builder()
                    .add("token", TOKEN)
                    .add("deviceID", DEVICE_ID)
                    .add("deviceType", DEVICE_TYPE)
                    .add("appVersion", APP_VERSION)
                    .build();
            Request reque = request.newBuilder().post(build).build();
            return reque;
        }
    }

    /**
     * get请求 添加公共参数
     */
    private Request addParamsGet(Request request) {
        HttpUrl url = request.url().newBuilder()
                .addQueryParameter("token", TOKEN)
                .addQueryParameter("deviceID", DEVICE_ID)
                .addQueryParameter("deviceType", DEVICE_TYPE)
                .addQueryParameter("appVersion", APP_VERSION)
                .build();
        Request build = request.newBuilder().url(url).build();
        return build;
    }
}

可以看到原理很简单,重写拦截器,可以获取到网络请求的request对象,其中包含有我们的请求体,我们拿到请求体之后重新添加公共参数即可。

日志打印拦截器也是类似的原理:

public class HttpLog implements HttpLoggingInterceptor.Logger {
    @Override
    public void log(String message) {
        LogUtil.i(message);
    }
}

好了,到这一步,我们的网络请求封装的基础版已经封装完毕,可以总结一下我们都做了那些工作:

1.自定义retrofit工具类,提供retrofit单例对象以及网络请求的接口对象。

2.自定义BaseObservable和基础的返回对象,方便对返回结果做处理。

3.自定义拦截器,方便添加公共参数,以及打印出网络请求和返回的参数。

经过这三步之后,我们在Activity中就可以采用链式代码进行请求。

当然这都是最基础的一些封装,以后有空可以做进一步的封装。

 

你可能感兴趣的:(Android开发技巧)