共计 12285 个字符,预计需要花费 31 分钟才能阅读完成。
学如逆水行舟,逆水行舟。
去年的时候学习了 Rxjava 和 Retrofit 的根本用法,但始终没有在理论我的项目中使用。往年开做新我的项目,果决在新我的项目中引入了 RxJava 和 Retrofit。本篇文章将介绍笔者在我的项目中对 Retrofit 的封装。
先来看一下封装过后的 Retrofit 如何应用。
RetrofitHelper.getApiService()
.getMezi()
.compose(this.<List<MeiZi>>bindToLifecycle())
.compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<MeiZi>>() {
@Override
public void onSuccess(List<MeiZi> response) {showToast("申请胜利,妹子个数为" + response.size());
}
});
没错,就是这么简洁的一个链式调用,能够显示加载动画,还退出了 Retrofit 生命周期的治理。
开始之前须要先在 module 我的项目里的 Gradle 文件中增加用到的依赖库
compile "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version"
compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit2Version"
compile "com.squareup.retrofit2:converter-scalars:$rootProject.ext.retrofit2Version"
compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofit2Version"
compile "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofit2Version"
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile "com.trello.rxlifecycle2:rxlifecycle:$rootProject.ext.rxlifecycle"
//compile "com.trello.rxlifecycle2:rxlifecycle-android:$rootProject.ext.rxlifecycle"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.ext.rxlifecycle"
为了不便依赖库版本的批改咱们采纳”io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version”这中形式增加依赖,因而须要在 project 的 build.gradle 文件的加上以下内容:
ext {
supportLibVersion = '25.1.0'
butterknifeVersion = '8.5.1'
rxjava2Version = '2.0.8'
retrofit2Version = '2.2.0'
rxlifecycle='2.1.0'
gsonVersion = '2.8.0'
}
上面将通过几个大节对本次封装作具体的解析:
- 服务器响应数据的基类 BasicResponse
- 构建初始化 Retrofit 的工具类 IdeaApi
- 通过 GsonConverterFactory 获取实在响应数据
- 封装 DefaultObserver 解决服务器响应
- 解决加载 Loading
- 治理 Retrofit 生命周期
- 如何应用封装
- 小结
一. 服务器响应数据的基类 BasicResponse。
假设服务器返回的 Json 数据格式如下:
{
"code": 200,
"message": "胜利",
"content": {...}
}
依据 Json 数据格式构建咱们的 BasicResponse(BasicResponse 中的字段内容须要依据本人服务器返回的数据确定)。代码如下:
public class BasicResponse<T> {
private int code;
private String message;
private T content;
... 此处省去 get、set 办法。
二. 构建初始化 Retrofit 的工具类 IdeaApi。
该类通过 RetrofitUtils 来获取 ApiService 的实例。代码如下:
public class IdeaApi {public static <T> T getApiService(Class<T> cls,String baseUrl) {Retrofit retrofit = RetrofitUtils .getRetrofitBuilder(baseUrl).build();
return retrofit.create(cls);
}
}
RetrofitUtils 用来构建 Retrofit.Builder,并对 OkHttp 做以下几个方面的配置:
- 设置日志拦截器,拦挡服务器返回的 json 数据。Retrofit 将申请到 json 数据间接转换成了实体类,但有时候咱们须要查看 json 数据,Retrofit 并没有提供间接获取 json 数据的性能。因而咱们须要自定义一个日志拦截器拦挡 json 数据,并输出到控制台。
- 设置 Http 申请头。给 OkHttp 增加申请头拦截器,配置申请头信息。还能够为接口对立增加申请头数据。例如,把用户名、明码(或者 token)对立增加到申请头。后续每个接口的申请头中都会携带用户名、明码(或者 token)数据,防止了为每个接口独自增加。
- 为 OkHttp 配置缓存。同样能够同过拦截器实现缓存解决。包含管制缓存的最大生命值,管制缓存的过期工夫。
- 如果采纳 https,咱们还能够在此解决证书校验以及服务器校验。
- 为 Retrofit 增加 GsonConverterFactory。此处是一个比拟重要的环节,将在后边具体解说。
RetrofitUtils 代码如下:
public class RetrofitUtils {public static OkHttpClient.Builder getOkHttpClientBuilder() {HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
try {LogUtils.e("OKHttp-----", URLDecoder.decode(message, "utf-8"));
} catch (UnsupportedEncodingException e) {e.printStackTrace();
LogUtils.e("OKHttp-----", message);
}
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
return new OkHttpClient.Builder()
.readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(new HttpHeaderInterceptor())
.addNetworkInterceptor(new HttpCacheInterceptor())
// .sslSocketFactory(SslContextFactory.getSSLSocketFactoryForTwoWay()) // https 认证 如果要应用 https 且为自定义证书 能够去掉这两行正文,并自行配制证书。// .hostnameVerifier(new SafeHostnameVerifier())
.cache(cache);
}
public static Retrofit.Builder getRetrofitBuilder(String baseUrl) {Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
OkHttpClient okHttpClient = RetrofitUtils.getOkHttpClientBuilder().build();
return new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(baseUrl);
}
}
三. 通过 GsonConverterFactory 获取实在响应数据
在第一节中咱们构建了服务器响应数据 BasicResponse,BasicResponse 由 code、message、和 content 三个字段。其中 code 为服务器返回的错误码。咱们会当时和服务器约定胜利时的 code 值,比方 200 示意申请胜利。但通常在申请服务器数据过程中免不了会呈现各种谬误。例如用户登录时明码谬误、申请参数谬误的状况。此时服务器会依据谬误状况返回对应的错误码。一般来说,咱们只关怀胜利时即 code 为 200 时的 content 数据。而对于 code 不为 200 时咱们只须要给出对应的 Toast 提醒即可。事实上咱们对咱们有用的仅仅时 code 为 200 时的 content 数据。因而咱们能够思考过滤掉 code 和 message,在申请胜利的回调中只返回 content 的内容。
在此种状况下就须要咱们通过自定义 GsonConverterFactory 来实现了。咱们能够间接从 Retrofit 的源码中 copy 出 GsonConverterFactory 的三个相干类来做批改。
其中最终要的一部分是批改 GsonResponseBodyConverter 中的 convert 办法。在该办法中拿到服务器响应数据并判断 code 是否为 200。如果是,则获取到 content 并返回,如果不是,则在此处能够抛出对应的自定义的异样。而后再 Observer 中对立解决异常情况。GsonResponseBodyConverter 代码如下:
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(TypeAdapter<T> adapter) {this.adapter = adapter;}
@Override
public Object convert(ResponseBody value) throws IOException {
try {BasicResponse response = (BasicResponse) adapter.fromJson(value.charStream());
if (response.getCode()==200) {return response.getResults();
} else {
// 特定 API 的谬误,在相应的 DefaultObserver 的 onError 的办法中进行解决
throw new ServerResponseException(response.getCode(), response.getMessage());
}
} finally {value.close();
}
return null;
}
}
四. 构建 DefaultObserver 解决服务器响应。
上一节中咱们讲到了在申请服务器时可能呈现的一些例如明码谬误、参数谬误的状况,服务器给咱们返回了对应的错误码,咱们依据错误码抛出了对应自定义异样。除此之外在咱们发动网络申请时还可能产生一些异常情况。例如没有网络、申请超时或者服务器返回了数据但在解析时呈现了数据解析异样等。对于这样的状况咱们也要进行对立解决的。那么咱们就须要自定义一个 DefaultObserver 类继承 Observer,并重写相应的办法。
该类中最重要的两个办法时 onNext 和 onError。
1. 在服务器返回数据胜利的状况下会回调到 onNext 办法。因而咱们能够在 DefaultObserver 中定义一个形象办法 onSuccess(T response),在调用网络时重写 onSuccess 办法即可。
2. 如果在申请服务器过程中呈现任何异样,都会回调到 onError 办法中。包含上节中咱们本人抛出的异样都会回调到 onError。因而咱们的重头戏就是解决 onError。在 onError 中咱们依据异样信息给出对应的 Toast 提醒即可。
DefaultObserver 类的代码如下:
public abstract class DefaultObserver<T> implements Observer<T> {
@Override
public void onSubscribe(Disposable d) { }
@Override
public void onNext(T response) {onSuccess(response);
onFinish();}
@Override
public void onError(Throwable e) {LogUtils.e("Retrofit", e.getMessage());
if (e instanceof HttpException) { // HTTP 谬误
onException(ExceptionReason.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) { // 连贯谬误
onException(ExceptionReason.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) { // 连贯超时
onException(ExceptionReason.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) { // 解析谬误
onException(ExceptionReason.PARSE_ERROR);
}else if(e instanceof ServerResponseException){onFail(e.getMessage());
} else {onException(ExceptionReason.UNKNOWN_ERROR);
}
onFinish();}
@Override
public void onComplete() {}
/**
* 申请胜利
*
* @param response 服务器返回的数据
*/
abstract public void onSuccess(T response);
/**
* 服务器返回数据,但响应码不为 200
*
*/
public void onFail(String message) {ToastUtils.show(message);
}
public void onFinish(){}
/**
* 申请异样
*
* @param reason
*/
public void onException(ExceptionReason reason) {switch (reason) {
case CONNECT_ERROR:
ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT);
break;
case CONNECT_TIMEOUT:
ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT);
break;
case BAD_NETWORK:
ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT);
break;
case PARSE_ERROR:
ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT);
break;
case UNKNOWN_ERROR:
default:
ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT);
break;
}
}
/**
* 申请网络失败起因
*/
public enum ExceptionReason {
/**
* 解析数据失败
*/
PARSE_ERROR,
/**
* 网络问题
*/
BAD_NETWORK,
/**
* 连贯谬误
*/
CONNECT_ERROR,
/**
* 连贯超时
*/
CONNECT_TIMEOUT,
/**
* 未知谬误
*/
UNKNOWN_ERROR,
}
}
五. 解决加载 Loading
对于 Loading 咱们能够通过 RxJava 的 compose 操作符来做一个十分优雅的解决。首先定义一个 ProgressUtils 工具类,而后通过 RxJava 的 ObservableTransformer 做一个变换来解决 Loading。想要显示 Loading,只须要加上 .compose(ProgressUtils.< T >applyProgressBar(this))
即可。
ProgressUtils 代码如下:
public class ProgressUtils {
public static <T> ObservableTransformer<T, T> applyProgressBar(@NonNull final Activity activity, String msg) {final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
final DialogUtils dialogUtils = new DialogUtils();
dialogUtils.showProgress(activityWeakReference.get());
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(Observable<T> upstream) {return upstream.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {}}).doOnTerminate(new Action() {
@Override
public void run() throws Exception {
Activity context;
if ((context = activityWeakReference.get()) != null
&& !context.isFinishing()) {dialogUtils.dismissProgress();
}
}
}).doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
/*Activity context;
if ((context = activityWeakReference.get()) != null
&& !context.isFinishing()) {dialogUtils.dismissProgress();
}*/
}
});
}
};
}
public static <T> ObservableTransformer<T, T> applyProgressBar(@NonNull final Activity activity) {return applyProgressBar(activity, "");
}
}
至此对于 RxJava 和 Retrofit 的二次封装曾经根本实现。然而咱们不能疏忽了很重要的一点,就是网络申请的生命周期。咱们将在下一节中具体解说。
六、治理 Retrofit 生命周期
当 activity 被销毁时,网络申请也应该随之终止的。要不然就可能造成内存透露。会重大影到响 App 的性能!因而 Retrofit 生命周期的治理也是比拟重要的一点内容。在这里咱们应用 RxLifecycle 来对 Retrofit 进行生命周期治理。其应用流程如下:
1. 在 gradel 中增加依赖如下:
compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
2. 让咱们的 BaseActivity 继承 RxAppCompatActivity。
具体代码如下:
public abstract class BaseActivity extends RxAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(getLayoutId());
init(savedInstanceState);
}
protected void showToast(String msg) {ToastUtils.show(msg);
}
protected abstract @LayoutRes int getLayoutId();
protected abstract void init(Bundle savedInstanceState);
}
同样咱们我的项目的 BaseFragment 继承 RxFragment(留神应用继承 V4 包下的 RxFragment),如下:
public abstract class BaseFragment extends RxFragment {
public View rootView;
public LayoutInflater inflater;
@Nullable
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {super.onCreateView(inflater, container, savedInstanceState);
this.inflater = inflater;
if (rootView == null) {rootView = inflater.inflate(this.getLayoutId(), container, false);
init(savedInstanceState);
}
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {parent.removeView(rootView);
}
return rootView;
}
protected abstract int getLayoutId();
protected abstract void init(Bundle savedInstanceState);
protected void showToast(String msg) {ToastUtils.show(msg);
}
@Override
public void onResume() {super.onResume();
}
@Override
public void onPause() {super.onPause();
}
@Override
public void onDestroyView() {super.onDestroyView();
}
}
3. 应用 compose 操作符治理 Retrofit 生命周期了:
myObservable
.compose(bindToLifecycle())
.subscribe();
或者
myObservable
.compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
.subscribe();
对于 RxLifecycle 的具体应用办法能够参考 RxLifecycle 官网
七. 如何应用封装
后面几节内容解说了如何 RxJava 进行二次封装,封装局部的代码能够放在咱们我的项目的 Library 模块中。那么封装好之后咱们应该如何在 app 模块中应用呢?
1. 定义一个接口来寄存咱们我的项目的 API
public interface IdeaApiService {
/**
* 此接口服务器响应数据 BasicResponse 的泛型 T 应该是 List<MeiZi>
* 即 BasicResponse<List<MeiZi>>
* @return BasicResponse<List<MeiZi>>
*/
@Headers("Cache-Control: public, max-age=10")// 设置缓存 缓存工夫为 100s
@GET("福利 /10/1")
Observable<List<MeiZi>> getMezi();
/**
* 登录 接口为假接口 并不能返回数据
* @return
*/
@POST("login.do")
Observable<LoginResponse> login(@Body LoginRequest request);
/**
* 刷新 token 接口为假接口 并不能返回数据
* @return
*/
@POST("refresh_token.do")
Observable<RefreshTokenResponseBean> refreshToken(@Body RefreshTokenRequest request);
@Multipart
@POST("upload/uploadFile.do")
Observable<BasicResponse> uploadFiles(@Part List<MultipartBody.Part> partList);
}
2. 定义一个 RetrofitHelper 类,通过 IdeaApi 来获取 IdeaApiService 的实例。
public class RetrofitHelper {
private static IdeaApiService mIdeaApiService;
public static IdeaApiService getApiService(){return mIdeaApiService;}
static {mIdeaApiService= IdeaApi.getApiService(IdeaApiService.class, Constants.API_SERVER_URL);
}
}
3. 在 Activity 或者 Fragment 中发动网络申请
/**
* Get 申请
* @param view
*/
public void getData(View view) {RetrofitHelper.getApiService()
.getMezi()
.compose(this.<List<MeiZi>>bindToLifecycle())
.compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<MeiZi>>() {
@Override
public void onSuccess(List<MeiZi> response) {showToast("申请胜利,妹子个数为" + response.size());
}
});
}
八. 小结
本篇文章次要解说了 Rxjava 和 Retrofit 的二次封装。以上内容也是笔者参考多方面的材料通过长时间的改变优化而来。但鉴于自己能力无限,其中也防止不了呈现不当之处。还请大家多多包涵。另外,在投稿郭神公众号时文章可能还存在很多解决不优雅的中央,比方对响应数据的解决以及对 Loading 的解决。在投稿被推送后收到了很多小伙伴的倡议,因而笔者也参考了大家的意见并做了优化,在此感激大家。最初如果有疑难欢送在文章留言评论。