Retrofit 最简洁易用的封装,摆脱RxJava
2018-11-16 本文已影响153人
xcheng_
前言
Retrofit相信很多android开发者都在使用!很多时候我们根据需要为其在封装一层实现。能够更好更简洁的实现我们的业务代码,我们先列一下retrofit使用过程中的一些痛点
1、取消请求不方便,必须持有发起请求时的Call对象
2、不能动态修改baseUrl
3、不能监听下载进度
4、回调函数 public void onResponse(Call<T> call, final Response<T> response) 我们还需要再次解析Response<T> ,更具是否成功在做业务处理,但是这时候很多代码都是重复判断代码,显得冗余。
5、没有独立的回调接口监听 请求发起 、请求结束、请求取消
我们关心的是请求的结果,比如登录 我们只想要拿到登录信息LoginInfo,或者加载列表的时候我们只需要拿到列表对象List<Item>,请求失败的时候我们很多时候只需要拿到错误信息,弹出给用户同事即可
针对以上问题 ,故做了二次封装。github EasyOkHttp 基于Retrofit的二次封装。解决以上问题
最新的依赖为 implementation 'com.xcheng:easyokhttp:2.5.0'
如何使用?
1、初始化 全局的Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://wanandroid.com/")
.callFactory(new OkHttpClient.Builder()
.addNetworkInterceptor(httpLoggingInterceptor)
.build())
.addCallAdapterFactory(ExecutorCallAdapterFactory.INSTANCE)
.addConverterFactory(GsonConverterFactory.create())
.build();
RetrofitManager.init(retrofit);
2、构建解析器GsonResponseBodyConverter
一般正常的http返回的合适应该是这样的格式
package com.simple.entity;
/**
* 普通的结果提示 ,code=0代表成功
* Created by chengxin on 2017/9/26.
*/
public class BaseResult<T> {
private int code;
private String msg;
private T data;
public T getData() {
return data;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public boolean isSuccess() {
return code == 0;
}
}
请求成功时: 我们仅需要的是data对象。
请求失败时 :我们需要的是msg,如果出错时有其他的业务需要处理可以携带对应的信息?如根据code码 处理未登录、 没权限,或者还需要原始的json信息做进一步处理等!基于此需求,我们构建了如下实体类,只要返回的结果不是我们预期的我们就将结果包装成此异常抛出
package com.xcheng.retrofit;
import android.support.annotation.Nullable;
/**
* 通用的错误信息,一般请求是失败只需要弹出一些错误信息即可,like{@link retrofit2.HttpException}
* Created by chengxin on 2017/6/22.
*/
public final class HttpError extends RuntimeException {
private static final long serialVersionUID = -134024482758434333L;
/**
* 展示在前端的错误描述信息
*/
public String msg;
/**
* <p>
* 请求失败保存失败信息,for example:
* <li>BusiModel: {code:xxx,msg:xxx} 业务错误信息</li>
* <li>original json: 原始的json</li>
* <li>{@link retrofit2.Response}:错误响应体->Response<?></li>
* <li>Throwable: 抛出的异常信息</li>
* </p>
*/
@Nullable
public final transient Object body;
public HttpError(String msg) {
this(msg, null);
}
public HttpError(String msg, @Nullable Object body) {
super(msg);
if (body instanceof Throwable) {
initCause((Throwable) body);
}
//FastPrintWriter#print(String str)
this.msg = msg != null ? msg : "null";
this.body = body;
}
/**
* 保证和msg一致
*/
@Override
public String getMessage() {
return msg;
}
@Override
public String toString() {
return "HttpError {msg="
+ msg
+ ", body="
+ body
+ '}';
}
}
json解析器的代码如下
package com.simple.converter;
import android.support.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.internal.$Gson$Types;
import com.simple.okhttp.Tip;
import com.xcheng.retrofit.HttpError;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.lang.reflect.Type;
import okhttp3.ResponseBody;
import retrofit2.Converter;
/**
* 创建时间:2018/4/3
* 编写人: chengxin
* 功能描述:json解析相关
*/
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final Type type;
GsonResponseBodyConverter(Gson gson, Type type) {
this.type = type;
this.gson = gson;
}
@SuppressWarnings("unchecked")
@Override
public T convert(@NonNull ResponseBody value) throws IOException {
String cacheStr = value.string();
try {
JSONObject jsonObject = new JSONObject(cacheStr);
final int code = jsonObject.getInt("errorCode");
final String msg = jsonObject.getString("errorMsg");
Tip tip = new Tip(code, msg);
if (code != 0) {
throw new HttpError(msg, tip);
}
Class<?> rawType = $Gson$Types.getRawType(type);
if (Tip.class == rawType) {
return (T) tip;
}
Object data = jsonObject.get("data");
if (data == JSONObject.NULL) {
//in case
throw new HttpError("暂无数据", tip);
}
//如果是String 直接返回
if (String.class == rawType) {
return (T) data.toString();
}
//data 为Boolean 如{"msg": "手机号格式错误","code": 0,"data": false}
if (Boolean.class == rawType && data instanceof Boolean) {
return (T) data;
}
//data 为Integer 如{"msg": "手机号格式错误","code": 0,"data": 12}
if (Integer.class == rawType && data instanceof Integer) {
return (T) data;
}
T t = gson.fromJson(data.toString(), type);
if (t != null) {
//防止线上接口修改导致反序列化失败奔溃
return t;
}
throw new HttpError("数据异常", tip);
} catch (JSONException e) {
throw new HttpError("解析异常", cacheStr);
}
}
}
3、回调函数
回调函数中我们,我们期望能监听到
请求开始: onStart(Call2<T> call2)
开启loading 动画
请求成功: onSuccess(Call2<T> call2, T response)
处理成功
请求失败: onError(Call2<T> call2, HttpError error);
处理失败
请求结束: onCompleted(Call2<T> call2)
关闭 loading动画
请求取消: onCancel(Call2<T> call2)
处理取消等
基类代码如下:
package com.xcheng.retrofit;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import retrofit2.Call;
import retrofit2.Response;
/**
* if {@link Call#cancel()}called {@link #onStart(Call2)}、 {@link #onSuccess(Call2, Object)}、
* {@link #onError(Call2, HttpError)}、 {@link #onCompleted(Call2)} will not be called
*
* @param <T> Successful response body type.
*/
@UiThread
public abstract class Callback2<T> {
@NonNull
public Result<T> parseResponse(Call2<T> call2, Response<T> response) {
T body = response.body();
if (response.isSuccessful()) {
if (body != null) {
return Result.success(body);
} else {
return Result.error(new HttpError("暂无数据", response));
}
}
final String msg;
switch (response.code()) {
case 400:
msg = "参数错误";
break;
case 401:
msg = "身份未授权";
break;
case 403:
msg = "禁止访问";
break;
case 404:
msg = "地址未找到";
break;
default:
msg = "服务异常";
}
return Result.error(new HttpError(msg, response));
}
/**
* 统一解析Throwable对象转换为HttpError对象。如果为HttpError,
* 则为{@link retrofit2.Converter#convert(Object)}内抛出的异常
*
* @param call2 call
* @param t Throwable
* @return HttpError result
*/
@NonNull
public HttpError parseThrowable(Call2<T> call2, Throwable t) {
if (t instanceof HttpError) {
//用于convert函数直接抛出异常接收
return (HttpError) t;
} else if (t instanceof UnknownHostException) {
return new HttpError("网络异常", t);
} else if (t instanceof ConnectException) {
return new HttpError("网络异常", t);
} else if (t instanceof SocketException) {
return new HttpError("服务异常", t);
} else if (t instanceof SocketTimeoutException) {
return new HttpError("响应超时", t);
} else {
return new HttpError("请求失败", t);
}
}
public void onStart(Call2<T> call2) {
}
public void onCancel(Call2<T> call2) {
}
public abstract void onError(Call2<T> call2, HttpError error);
public abstract void onSuccess(Call2<T> call2, T response);
/**
* 请求回调全部完成时执行
*
* @param call2 Call
*/
public void onCompleted(Call2<T> call2) {
}
}
重写callback2:
package com.simple.okhttp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.gson.JsonSyntaxException;
import com.xcheng.retrofit.Call2;
import com.xcheng.retrofit.Callback2;
import com.xcheng.retrofit.HttpError;
import com.xcheng.view.controller.ILoadingView;
/**
* Created by chengxin on 2017/9/24.
*/
public abstract class AnimCallback<T> extends Callback2<T> {
private ILoadingView mLoadingView;
public AnimCallback(@Nullable ILoadingView loadingView) {
this.mLoadingView = loadingView;
}
@Override
public void onStart(Call2<T> call2) {
super.onStart(call2);
if (mLoadingView != null)
mLoadingView.showLoading();
}
@Override
public void onCompleted(Call2<T> call2) {
super.onCompleted(call2);
if (mLoadingView != null)
mLoadingView.hideLoading();
}
@NonNull
@Override
public HttpError parseThrowable(Call2<T> call2, Throwable t) {
HttpError filterError;
if (t instanceof JsonSyntaxException) {
filterError = new HttpError("解析异常", t);
} else {
filterError = super.parseThrowable(call2, t);
}
return filterError;
}
}
5、发起请求
/**
* 创建时间:2018/4/8
* 编写人: chengxin
* 功能描述:测试接口
*/
public interface ApiService {
//登录
@FormUrlEncoded
@POST("user/login")
Call2<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
//获取微信公众号列表
@GET("wxarticle/chapters/json")
Call2<List<WXArticle>> getWXarticle();
//获取首页文章列表
@GET("article/list/0/json")
Call2<List<Article>> getArticle0();
//下载文件
@GET("http://shouji.360tpcdn.com/181115/4dc46bd86bef036da927bc59680f514f/com.ss.android.ugc.aweme_330.apk")
Call2<File> loadDouYinApk();
}
获取列表
RetrofitManager.create(ApiService.class)
.getWXarticle().enqueue(hashCode(), new AnimCallback<List<WXArticle>>(this) {
@Override
public void onError(Call2<List<WXArticle>> call2, HttpError error) {
Toast.makeText(MainActivity.this, error.msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onSuccess(Call2<List<WXArticle>> call2, List<WXArticle> response) {
Toast.makeText(MainActivity.this, "获取公众号列表成功", Toast.LENGTH_SHORT).show();
}
});
下载文件
String filePath = new File(getContext().getExternalCacheDir(), "test_douyin.apk").getPath();
//构建可以监听进度的client
OkHttpClient client = new OkHttpClient().newBuilder()
.addNetworkInterceptor(getProgressInterceptor()).build();
//构建可以下载文件的client
Retrofit retrofit = RetrofitManager.retrofit()
.newBuilder()
.callFactory(client)
.addConverterFactory(new FileConverterFactory(filePath))
.build();
retrofit.create(ApiService.class)
.loadDouYinApk().enqueue("loadApk", new Callback2<File>() {
@Override
public void onStart(Call2<File> call2) {
super.onStart(call2);
button.setText("取消下载");
}
@Override
public void onError(Call2<File> call2, HttpError error) {
progressView.setProgress(0);
Toast.makeText(MainActivity.this, error.msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onSuccess(Call2<File> call2, File response) {
}
@Override
public void onCancel(Call2<File> call2) {
super.onCancel(call2);
progressView.setProgress(0);
button.setText("下载抖音apk文件");
}
@Override
public void onCompleted(Call2<File> call2) {
super.onCompleted(call2);
button.setText("下载完成");
}
});
6、取消请求
tag为发起请求时传入的tag,这样就不用一直引用Call2对象用于取消请求了。
CallManager.getInstance().cancel(tag);
image.png
是不是很简单,能应对所有的需求,简洁易用
github地址:https://github.com/xchengDroid/EasyOkHttp
作者:xcheng_;转载请添加原文地址 https://www.jianshu.com/p/aeea4fe91102