去年的时候学习了 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 做以下几个方面的配置:
1. 设置日志拦截器, 拦截服务器返回的 JSON 数据. Retrofit 将请求到 JSON 数据直接转换成了实体类, 但有时候我们需要查看 JSON 数据, Retrofit 并没有提供直接获取 JSON 数据的功能. 因此我们需要自定义一个日志拦截器拦截 JSON 数据, 并输入到控制台.
2. 设置 Http 请求头. 给 OkHttp 添加请求头拦截器, 配置请求头信息. 还可以为接口统一添加请求头数据. 例如, 把用户名, 密码 (或者 token) 统一添加到请求头. 后续每个接口的请求头中都会携带用户名, 密码 (或者 token) 数据, 避免了为每个接口单独添加.
3. 为 OkHttp 配置缓存. 同样可以同过拦截器实现缓存处理. 包括控制缓存的最大生命值, 控制缓存的过期时间.
4. 如果采用 https, 我们还可以在此处理证书校验以及服务器校验.
5. 为 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 的处理. 在投稿被推送后收到了很多小伙伴的建议, 因此笔者也参考了大家的意见并做了优化, 在此感谢大家. 最后如果有疑问欢迎在文章留言评论.
来源: http://www.jianshu.com/p/5fac21859bf9