(一)最流行的网络请求框架Rxjava2+Retrofit完美封装
* 本文同步发表在简书,转载请注明出处。
* 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
要说2016年最火的Android技术是什么,毫无疑问肯定是RxJava+Retrofit+Mvp。现如今2017年也已经过了快一半了。相信做android开发的小伙伴对RxJava和Retrofit也不再陌生。即使没有刻意的去学习过,也应该对RxJava和Retrofit有个一知半解。去年的时候学习了Rxjava和Retrofit的基本用法,但一直没有在实际项目中运用。今年开做新项目,果断在新项目中引入了RxJava和Retrofit。本篇文章将介绍笔者在项目中对Retrofit的封装。
先来看一下封装过后的Retrofit如何使用。
IdeaApi.getApiService()
.getMezi()
.compose(this.<BasicResponse<List<MeiZi>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<BasicResponse<List<MeiZi>>>(this) {
@Override
public void onSuccess(BasicResponse<List<MeiZi>> response) {
List<MeiZi> results = response.getResults();
showToast("请求成功,妹子个数为"+results.size());
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
没错,就是这么简洁的一个链式调用,可以显示加载动画,还加入了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"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
为了方便依赖库版本的修改我们采用”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'
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
下面将通过几个小节对本次封装作详细的解析:
- 构建初始化Retrofit的工具类IdeaApi
- 服务器响应数据的基类BasicResponse
- 封装DefaultObserver处理服务器响应
- 处理加载动画ProgressDialog
- Rxjava生命周期处理
一.构建初始化Retrofit的工具类IdeaApi。在该类中主要完成三个功能,即:
- 设置日志拦截器拦截服务器返回的json数据。Retrofit将请求到json数据直接转换成了实体类,但有时候我们需要查看json数据,Retrofit并没有提供直接获取json数据的功能。因此我们需要自定义一个日志拦截器拦截json数据,并输入到控制台。
- 设置 Http 拦截器,处理缓存问题。通过拦截器拦截Http请求头,为请求头配置缓存信息,包括控制缓存的最大生命值,控制缓存的过期时间。
-
获取Retrofit实例。通过单利模式获取Retrofit实例。
实现代码如下:
public class IdeaApi {
private IdeaApiService service;
private IdeaApi() {
// HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
// 日志拦截器
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor((message)-> {
try {
String text = URLDecoder.decode(message, "utf-8");
LogUtils.e("OKHttp-----", text);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
LogUtils.e("OKHttp-----", message);
}
});
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(IdeaApiService.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(IdeaApiService.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(interceptor)
.addNetworkInterceptor(new HttpCacheInterceptor())
.cache(cache)
.build();
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(IdeaApiService.API_SERVER_URL)
.build();
service = retrofit.create(IdeaApiService.class);
}
// 创建单例
private static class SingletonHolder {
private static final IdeaApi INSTANCE = new IdeaApi();
}
public static IdeaApiService getApiService() {
return SingletonHolder.INSTANCE.service;
}
class HttpCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetworkUtils.isConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
LogUtils.d("Okhttp", "no network");
}
Response originalResponse = chain.proceed(request);
if (NetworkUtils.isConnected()) {
//有网的时候读接口上的@Headers里的配置,可以在这里进行统一的设置
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
} else {
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=2419200")
.removeHeader("Pragma")
.build();
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
二.构建服务器响应数据的基类BasicResponse。假定服务器返回的Json数据格式如下:
{
"code": 200,
"message": "成功",
"content": {
...
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
根据Json数据格式构建我们的BasicResponse(BasicResponse中的字段内容需要根据自己服务器返回的数据确定)。代码如下:
public class BasicResponse<T> {
private int code;
private String message;
private T content;
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
三.构建DefaultObserver处理服务器响应数据。定义DefaultObserver类继承Observer,并重写相应的方法。
在请求数据过程中免不了会出现各种错误或者异常,可以分为两种情况:
1.像登录时密码错误、请求参数错误的情况,即服务器返回了错误的数据,我们可以称之为错误。只要服务器返回数据,Observer中的onNext()方法就会被执行。但有时服务器返回错误数据并不是我们想要的,因此我们需要对错误数据进行处理。我们可以和服务端事先约定正常情况的请求码,如上面Json中的code等于200时视为数据正常,code不为200时视为数据错误。因此我们可以定义出一个请求数据正常的抽象方法onSuccess(),在code为200时调用,并在请求数据的页面重写该方法。另外定义一个请求失败的方法onFail(),在code不为200时调用,并Toast出错误原因。请求网络页面不必重写该方法。代码如下:
@Override
public void onNext(T response) {
mBaseImpl.dismissProgress();
if (response.getCode() == 200) {
onSuccess(response);
} else {
onFail(response);
}
}
/**
* 请求数据成功 且响应码为200
* @param response 服务器返回的数据
*/
abstract public void onSuccess(T response);
/**
* 服务器返回数据,但响应码不为200
* @param response 服务器返回的数据
*/
public void onFail(T response) {
String message = response.getMessage();
if (TextUtils.isEmpty(message)) {
ToastUtils.show(R.string.response_return_error);
} else {
ToastUtils.show(message);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
2.请求网络时出现异常情况,如网络连接失败、网络连接超时、数据解析异常等情况。我们可以称之为异常,即这种情况下Observer的onError()方法被调用。因此我们可以定义一个onException()的方法,并根据不同的异常在onException()方法中给出对应的Toast提示。代码如下:
@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(CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) { // 连接超时
onException(CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) { // 解析错误
onException(PARSE_ERROR);
} else {
onException(UNKNOWN_ERROR);
}
}
/**
* 请求异常
* @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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
四.处理加载动画ProgressDialog
我们可以自定义ProgressBar,然后封装成DialogUtils进行控制ProgressDialog,
DialogUtils代码如下:
public class CommonDialogUtils {
// 加载进度的dialog
private CustomProgressDialog mProgressDialog;
/**
* 显示ProgressDialog
*/
public void showProgress(Context context, String msg) {
/* if (context == null || context.isFinishing()) {
return;
}*/
if(mProgressDialog==null){
mProgressDialog= new CustomProgressDialog.Builder(context)
.setTheme(R.style.ProgressDialogStyle)
.setMessage(msg)
.build();
}
if(mProgressDialog!=null&&!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
/**
* 显示ProgressDialog
*/
public void showProgress(Context context) {
/*if (activity == null || activity.isFinishing()) {
return;
}*/
if(mProgressDialog==null){
mProgressDialog= new CustomProgressDialog.Builder(context)
.setTheme(R.style.ProgressDialogStyle)
.build();
}
if(mProgressDialog!=null&&!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
/**
* 取消ProgressDialog
*/
public void dismissProgress() {
if (mProgressDialog != null&&mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
加入ProgressBar后完整的DefaultObserver
public abstract class DefaultObserver<T extends BasicResponse> implements Observer<T> {
private Activity activity;
// Activity 是否在执行onStop()时取消订阅
private boolean isAddInStop = false;
private CommonDialogUtils dialogUtils;
public DefaultObserver(Activity activity) {
this.activity = activity;
dialogUtils=new CommonDialogUtils();
dialogUtils.showProgress(activity);
}
public DefaultObserver(Activity activity, boolean isShowLoading) {
this.activity = activity;
dialogUtils=new CommonDialogUtils();
if (isShowLoading) {
dialogUtils.showProgress(activity,"Loading...");
}
}
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(T response) {
dismissProgress();
if (!response.isError()) {
onSuccess(response);
} else {
onFail(response);
}
/*if (response.getCode() == 200) {
onSuccess(response);
} else {
onFail(response);
}*/
}
private void dismissProgress(){
if(dialogUtils!=null){
dialogUtils.dismissProgress();
}
}
@Override
public void onError(Throwable e) {
LogUtils.e("Retrofit", e.getMessage());
dismissProgress();
if (e instanceof HttpException) { // HTTP错误
onException(ExceptionReason.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) { // 连接错误
onException(CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) { // 连接超时
onException(CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) { // 解析错误
onException(PARSE_ERROR);
} else {
onException(UNKNOWN_ERROR);
}
}
@Override
public void onComplete() {
}
/**
* 请求成功
*
* @param response 服务器返回的数据
*/
abstract public void onSuccess(T response);
/**
* 服务器返回数据,但响应码不为200
*
* @param response 服务器返回的数据
*/
public void onFail(T response) {
String message = response.getMessage();
if (TextUtils.isEmpty(message)) {
ToastUtils.show(R.string.response_return_error);
} else {
ToastUtils.show(message);
}
}
/**
* 请求异常
*
* @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,
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
五、Rxjava生命周期处理
在Activity或者Fragment中使用RxJava时我们有必要对RxJava的生命周期进行管理,否则可能引起内存泄漏问题。在这里我们使用 RxLifecycle来对RxJava进行生命周期管理。
1.在gradel中添加依赖如下:
compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
- 1
- 2
- 3
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
我们项目的BaseFragment继承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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
接下来我们在使用Rxjava时就可以通过以下代码去管理生命周期了:
myObservable
.compose(bindToLifecycle())
.subscribe();
或者
myObservable
.compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
.subscribe();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
关于RxLifecycle的详细使用方法可以参考 RxLifecycle官网
最后构建存放网络请求的接口IdeaApiService,项目中所有的请求接口都可以放到这里,具体代码如下:
public interface IdeaApiService {
/**
* 网络请求超时时间毫秒
*/
int DEFAULT_TIMEOUT = 15000;
String HOST = "http://gank.io/";
String API_SERVER_URL = HOST + "api/data/";
@Headers("Cache-Control: public, max-age=86400") // 设置缓存
@GET("福利/10/1")
Observable<BasicResponse<List<MeiZi>>> getMezi();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13