一、准备工作
以前用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'即可。感觉基础还是比较薄弱,下一步计划就是分析一些源码,以此来提高自己。