【Android】用retrofit2和rxjava2搭建网络请求框架

一、准备工作

以前用Retrofit和RxJava搭建的网络请求框架问题比较多,使用起来也不方便,所以有时间以后,就想重新搭一套比较通用的框架,上传到JCenter,以后新开项目也可以直接用,所以诞生了这篇博客。首先,整理一下我能想到的需求:

1、需求:

(1)多baseurl

通常项目最少会有两套环境,一套线上环境,一套测试环境。不同环境使用的域名不同,目前我遇到的有两种情况,一种是打包时,根据不同的环境打的包使用的域名不同,另一种是调试时,在多个环境之间切换。

(2)可设置请求超时时间

需要可以设置超时时间和超时时间的单位,如果不设置超时时间和超时时间的单位,则有一个默认的超时时间和时间单位。

(3)添加拦截器

可以根据不同的需要在初始化时添加需要的拦截器。

(4)添加请求头

添加一个拦截器,在拦截器中实现添加请求头。

(5)实现可定制BaseResponse

给定一个默认的BaseResponse,同时在封装的Subscriber中进行类型判断,继承自BaseResponse的封装好状态判断等操作,不继承则需要自己进行更多的处理,甚至可以自己封装Subscriber。推荐封装自用的Subscriber,可以更好的符合自己的需求。

(6)BaseView应该有哪些东西?是否可以定制?

实现BaseView就得实现BaseView所有方法,所以需要通用性,从多个方面考虑后,发现只有加载具有通用性,因为个人觉得加载提示是请求接口时必不可少的东西。所以在BaseView中只封装了loading()方法。若有其他需求,则可定制自己的BaseView。

目前我能想到的需求只有这些,后边有时间可能会加入下载上传以及进度展示的封装,我的思路也可能存在问题,如果有问题,请各位大佬指正。

2、添加依赖

由于我是打算封装好以后上传到JCenter直接使用的,所以另开了一个项目,直接在项目的build.gradle中加入了以下依赖:

implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'com.squareup.retrofit2:retrofit:2.2.0'
implementation 'com.squareup.retrofit2:converter-gson:2.2.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
api 'com.orhanobut:logger:2.1.1'
api 'com.alibaba:fastjson:1.2.47'

二、实现

1、创建Retrofit帮助类

创建一个名为RetrofitHelper的类,主要用于实现OkHttpClient和Retrofit相关设置。通过一个建造者模式,来实现需求中的1-4条。以下代码为Builder类:

public static final class Builder {
    private OkHttpClient mOkHttpClient;
    private OkHttpClient.Builder mBuilder;
    private String mBaseUrl;

    private long mConnectTimeout;
    private TimeUnit mConnectTimeUnit;
    private long mReadTimeout;
    private TimeUnit mReadTimeUnit;
    private long mWriteTimeout;
    private TimeUnit mWriteTimeUnit;

    public Builder() {
        mBuilder = new OkHttpClient.Builder();
    }

    public Builder(OkHttpClient okHttpClient) {
        mOkHttpClient = okHttpClient;
    }

    public Builder(RetrofitHelper retrofitHelper) {
        mOkHttpClient = retrofitHelper.mOkHttpClient;
    }

    /**
     * 添加拦截器
     *
     * @param interceptor 拦截器
     * @return this
     */
    public Builder addInterceptor(Interceptor interceptor) {
        if (mOkHttpClient != null) {
            mBuilder = mOkHttpClient.newBuilder();
        }
        if (mBuilder != null) {
            mBuilder.addInterceptor(interceptor);
        }
        return this;
    }

    /**
     * 添加拦截器来实现动态添加多个请求头
     *
     * @param headers 需要添加的header
     * @return this
     */
    public Builder addHeaders(final HashMap headers) {
        return addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Request.Builder builder = request.newBuilder();
                Set keys = headers.keySet();
                if (keys.iterator().hasNext()) {
                    builder.addHeader(keys.iterator().next(), headers.get(keys.iterator().next()));
                }
                return chain.proceed(request);
            }
        });
    }

    /**
     * 添加单个请求头
     *
     * @param key   请求头key
     * @param value 请求头value
     * @return this
     */
    public Builder addHeader(String key, String value) {
        HashMap headers = new HashMap<>(1);
        headers.put(key, value);
        return addHeaders(headers);
    }

    /**
     * 设置baseUrl
     *
     * @param baseUrl baseUrl
     * @return this
     */
    public Builder baseUrl(String baseUrl) {
        mBaseUrl = baseUrl;
        return this;
    }

    /**
     * 设定Url是否可以变化
     *
     * @return this
     */
    public Builder urlCanTrans() {
        return addInterceptor(HttpUrlInterceptor.getInstance());
    }

    /**
     * 连接超时时间{@link #connectTimeout(long, TimeUnit)}
     *
     * @param connectTimeout 超时时间
     * @return this
     */
    public Builder connectTimeout(long connectTimeout) {
        connectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
        return this;
    }

    /**
     * 连接超时时间
     *
     * @param connectTimeout  超时时间
     * @param connectTimeUnit 超时时间单位
     * @return this
     */
    public Builder connectTimeout(long connectTimeout, TimeUnit connectTimeUnit) {
        mConnectTimeout = connectTimeout;
        mConnectTimeUnit = connectTimeUnit;
        return this;
    }

    /**
     * 读数据超时时间{@link #readTimeout(long, TimeUnit)}
     *
     * @param readTimeout 超时时间
     * @return this
     */
    public Builder readTimeout(long readTimeout) {
        readTimeout(readTimeout, TimeUnit.MILLISECONDS);
        return this;
    }

    /**
     * 读数据超时时间
     *
     * @param readTimeout  超时时间
     * @param readTimeUnit 超时时间单位
     * @return this
     */
    public Builder readTimeout(long readTimeout, TimeUnit readTimeUnit) {
        mReadTimeout = readTimeout;
        mReadTimeUnit = readTimeUnit;
        return this;
    }

    /**
     * 写数据超时时间{@link #writeTimeout(long, TimeUnit)}
     *
     * @param writeTimeout 超时时间
     * @return this
     */
    public Builder writeTimeout(long writeTimeout) {
        writeTimeout(writeTimeout, TimeUnit.MILLISECONDS);
        return this;
    }

    /**
     * 写数据超时时间
     *
     * @param writeTimeout  超时时间
     * @param writeTimeUnit 超时时间单位
     * @return this
     */
    public Builder writeTimeout(long writeTimeout, TimeUnit writeTimeUnit) {
        mWriteTimeout = writeTimeout;
        mWriteTimeUnit = writeTimeUnit;
        return this;
    }

    /**
     * 构造OkHttpClient
     */
    private void buildOkHttpClient() {
        if (mConnectTimeout != 0 && mConnectTimeUnit != null) {
            mBuilder.connectTimeout(mConnectTimeout, mConnectTimeUnit);
        } else {
            mBuilder.connectTimeout(TIME_OUT, TimeUnit.MILLISECONDS);
        }

        if (mReadTimeout != 0 && mReadTimeUnit != null) {
            mBuilder.readTimeout(mReadTimeout, mReadTimeUnit);
        } else {
            mBuilder.readTimeout(TIME_OUT, TimeUnit.MILLISECONDS);
        }

        if (mWriteTimeout != 0 && mWriteTimeUnit != null) {
            mBuilder.writeTimeout(mWriteTimeout, mWriteTimeUnit);
        } else {
            mBuilder.writeTimeout(TIME_OUT, TimeUnit.MILLISECONDS);
        }

        if (BuildConfig.DEBUG) {
            addInterceptor(new LoggingInterceptor());
        }
        mOkHttpClient = mBuilder.build();
    }

    /**
     * 创建Retrofit对象
     *
     * @return 创建好的Retrofit对象
     */
    public Retrofit build() {
        if (mBaseUrl == null) {
            throw new IllegalArgumentException("Illegal Url, You must have baseurl");
        }

        if (mOkHttpClient == null) {
            buildOkHttpClient();
        }
        return RetrofitHelper.getInstance(mBaseUrl, mOkHttpClient).createRetrofit();
    }
}

以下为各个方法的解析:
addInterceptor:不用多说,用于给OkHttpClient添加拦截器;
addHeaders和addHeader:使用addInterceptor方法来给请求分别添加多个和单个Header;
baseUrl:是用于给Retrofit的baseUrl赋值。
urlCanTrans:添加一个HttpUrlInterceptor,HttpUrlInterceptor是使用拦截器的方式修改baseurl。用于实现需求中的第一条。
timeout方法:用于给OkHttpClient设置超时时间。
buildOkHttpClient:设置OkHttpClient的各种值,在调试时,加入LoggingInterceptor,用于打印请求相关信息。
build:生成Retrofit对象,如果没有设定baseUrl,则会抛出异常。

最终创建一个Retrofit对象:

private Retrofit createRetrofit() {
    Retrofit.Builder builder = new Retrofit.Builder();
    builder.client(mOkHttpClient)
            .baseUrl(mBaseUrl)
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()));
    return builder.build();
}

这里的ConverterFactory采用了GsonConverterFactory,因为现在基本都是用Json格式交互,所以直接默认使用了GsonConverterFactory。到这里,Retrofit帮助类就告一段落了,下一步使用工厂模式进一步封装,这一步封装主要是为了给HttpUrlInterceptor使用所封装的,具体细节在HttpUrlInterceptor时描述。

2、RetrofitFactory

这个类本可以不用封装,但是在使用拦截器转换baseUrl时,会将API类中的全路径接口的域名给替换掉,与Retrofit本身相悖,因为Retrofit中的API的全路径是不受baseUrl影响的,所以封装了这个类。以下抽出该类中的两个方法来简述一下:

private static Class mClass;

public  T createApi(String baseUrl, Class clazz) {
    return createApi(baseUrl, clazz, false);
}

public  T createApi(String baseUrl, Class clazz, boolean canTransUrl) {
    mClass = clazz;

    RetrofitHelper.Builder builder = new RetrofitHelper.Builder().baseUrl(baseUrl);

    if (canTransUrl) {
        builder.urlCanTrans();
    }

    Retrofit retrofit = builder.build();
    return retrofit.create(clazz);
}

其中这个mClass就是关键,在这个类中有个静态方法getClazz()返回mClass。上述的crateApi方法就是用RetrofitHelper来创建与clazz相对应的API对象。

3、HttpUrlInterceptor

这个类是用来切换baseUrl的关键,该类继承自Interceptor,是通过拦截请求,修改请求的域名来切换baseUrl,这里就有个问题,如何不修改API中的全路径接口,就需要用到RetrofitFactory中的mCalss了。

 private static List getMethods(Class clazz) {
    if (clazz == null) {
        return null;
    }

    List resultMethods = new ArrayList<>();
    Method[] methods = clazz.getMethods();
    for (int i = 0; i < methods.length; i++) {
        Method method = methods[i];
        Annotation[] annotations = method.getDeclaredAnnotations();
        for (int j = 0; j < annotations.length; j++) {
            Annotation annotation = annotations[j];
            if (annotation instanceof GET) {
                String value = ((GET) annotation).value();
                addUrl(resultMethods, value);
            } else if (annotation instanceof POST) {
                String value = ((POST) annotation).value();
                addUrl(resultMethods, value);
            }
        }
    }

    return resultMethods;
}

这个方法是通过反射,来获取API类中的全路径加入List中,用来判断是否需要替换域名。其中的value,就是GET和POST注解中的路径。

4、IBaseView接口

View接口主要是用于定义与UI交互的方法,IBaseView是只封装了loading这一方法,网络请求时有加载提示应该算是比较通用的,没有想到其他通用的方法,所以就只有一个loading方法,若有其他需要,只需要继承自IBaseView,自己封装方法即可。

public interface IBaseView {
    void loading();
}

5、IBasePresenter接口

public interface IBasePresenter {
    /**
     * 与View绑定
     * @param t 需要绑定的View
     */
    void attachView(T t);

    /**
     * 在Activity销毁时,与View解除绑定
     */
    void detachView();
}

IBaseView接口定义了两个方法,一个attachView,一个detachView,分别是用来与View绑定和解除绑定的。这两个方法可以在Activity基类中使用,这样就可以避免每次在Activity中都要使用这两个方法。

6、BasePresenter

这个类是Presenter基类,主要用于做一些公共的操作。代码如下:

public class BasePresenter implements IBasePresenter {
    protected T mView;
    private CompositeDisposable mCompositeDisposable;
    protected V mApi;

    public BasePresenter() {
        mApi = getInstanceV();
    }

    @SuppressWarnings("unchecked")
    private V getInstanceV() {
        ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
        Class type = (Class) superClass.getActualTypeArguments()[1];
        try {
            return type.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected void addSubscriber(DisposableSubscriber subscriber) {
        if (mCompositeDisposable == null) {
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(subscriber);
    }

    private void unSubscribe() {
        if (mCompositeDisposable != null) {
            mCompositeDisposable.clear();
        }
    }

    @Override
    public void attachView(T view) {
        mView = view;
    }

    @Override
    public void detachView() {
        mView = null;
        unSubscribe();
    }

}

上述代码中的泛型V,是MVP中的M层,用于处理数据和进行数据库操作等。以代码为例:

public Flowable> findAllUser() {
        return api.findAllUser();
    }

上述代码为M层的类,命名为DataManage类中定义的一个方法,用于获取全部的用户。这个DataManager类,就是BasePresenter中的泛型V,我们在构造函数中,直接实例化了该泛型,所以在子类Presenter中,可以直接使用mApi来调用DataManager中类,而不需要再去子类的构造函数中创建DataManager的实例。下边的代码为子类Presenter的调用请求全部用户的代码:

@Override
public void findAllUser() {
    addSubscriber(mApi
            .findAllUser()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(new CommonSubscriber>(mView) {
                @Override
                public void success(List allUser) {
                    super.success(allUser);
                    mView.findAllUserSuccess(allUser);
                }

                @Override
                protected void failure(int exceptionCode, String errorMsg) {
                    super.failure(exceptionCode, errorMsg);
                    mView.findAllUserFail(errorMsg);
                }

                @Override
                protected void dealData(List allUser) {
                    super.dealData(allUser);
                    mView.findAllUserSuccess(allUser);
                }
            }));
}

以上就是部分关键点代码。

三、总结

描述时总感觉很吃力,想要表达的意思描述不出来,所以贴了大段的代码,但是感觉也没有突出重点,可能是因为我的思路比较乱,没有一个总体的框架,后边我会写一例子来更好的理一下思路。另:该库已经上传到JCenter,使用时直接:implementation 'com.wanggle.network:network:1.1.6'即可。感觉基础还是比较薄弱,下一步计划就是分析一些源码,以此来提高自己。

你可能感兴趣的:(【Android】用retrofit2和rxjava2搭建网络请求框架)