前言:封装只是加深本身的理解,网上已经有很优秀的封装,我也是借鉴了okgo和鸿洋的okhttputils。本项目是基于mvc模式下,但这篇只讲如何对okhttp进行封装(这里我按最基础步骤来,须要额外功能,看源码和本文理解,确定能够实现)java
咱们封装要有的功能有:android
get请求 | post请求 | 上传文件 |
---|---|---|
![]() |
![]() |
![]() |
下载文件 | ||
![]() |
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
Request.Builder mBuilder = new Request.Builder();
mBuilder.url("url?parm1=x&parm2=y");
mBuilder.header("head","headValue");
Request okHttpRequest = mBuilder.build();
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
复制代码
get请求参数是拼在url后面的,并且上面是异步请求enqueue,这个方法是在子线程里的。回调onFailure和onResponse也都在子线程,咱们能够把解析等耗时操做放在这里,可是这里不能直接更改UI,要把他切换到主线程里。git
//tag取消网络请求
public void cancleOkhttpTag(String tag) {
Dispatcher dispatcher = okHttpClient.dispatcher();
synchronized (dispatcher) {
//请求列表里的,取消网络请求
for (Call call : dispatcher.queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
//正在请求网络的,取消网络请求
for (Call call : dispatcher.runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
}
复制代码
能够看到,取消代码请求要用的okHttpClient,因此咱们要保持okHttpClient的惟一性,EasyOk这里就要用到单例了github
public class EasyOk {
private static EasyOk okHttpUtils;
private OkHttpClient okHttpClient;
//这个handler的做用是把子线程切换主线程。在后面接口中的具体实现,就不须要用handler去回调了
private Handler mDelivery;
private EasyOk() {
mDelivery = new Handler(Looper.getMainLooper());
okHttpClient = new OkHttpClient.Builder()
.hostnameVerifier(new HostnameVerifier() {//证书信任
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
public static EasyOk getInstance() {
if (okHttpUtils == null) {
okHttpUtils = new EasyOk();
}
return okHttpUtils;
}
public OkHttpClient getOkHttpClient() {
return okHttpClient;
}
public Handler getmDelivery() {
return mDelivery;
}
//tag取消网络请求
public void cancleOkhttpTag(String tag) {
Dispatcher dispatcher = okHttpClient.dispatcher();
synchronized (dispatcher) {
//请求列表里的,取消网络请求
for (Call call : dispatcher.queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
//正在请求网络的,取消网络请求
for (Call call : dispatcher.runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
}
}
复制代码
mDelivery是将子线程切换到主线程的handler,这里借鉴了鸿洋大神的思路。由于咱们最好把重复性的工做所有都放在封装里。封装最重要的优势不就是为了方便吗。json
根据最原始的okhttp进行的get请求,咱们还缺Request,还有一个网络请求的回调,那么请看下面。api
由于每次请求,Request 请求体都是须要new的,因此可想而知这里不多是单例,并且每次调用请求都是new出来的Request。根据最原始的get请求,咱们知道OkGetBuilder里须要一、url,二、参数,三、header,四、tag,五、还有本身的网络回调。缓存
因此咱们得用有个网络回调接口,这里我用的是抽象类ResultMyCall,这里用抽象类的好处是,咱们能够把统一重复操做放在父类里,只要不重写方法,都会按父类方法去实现,因此这里有时候你只须要重写一个onSuccess方法便可,不像接口同样要把方法所有实现。要注意的是,若是要基于mvc,最好用接口ResulCall,这块到时候介绍mvc的时候回介绍。如今咱们都按抽象类ResultMyCall走。抽象类以下ResultMyCall服务器
public abstract class ResultMyCall<T> {
//请求网络以前,通常展现loading
public void onBefore() {
}
//请求网络结束,消失loading
public void onAfter() {
}
//监听上传图片的进度(目前支持图片上传,其余重写这个方法无效)
public void inProgress(float progress) {
}
//错误信息
public void onError(String errorMessage) {
ToastUtils.showToast(errorMessage);
}
public void onSuccess(Object response) {
}
//若是带了泛型T,这里个方法会获取泛型的type,用于解析,若是不带泛型,默认返回的是String
public Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
return null;
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
public Type getType() {
return getSuperclassTypeParameter(getClass());
}
}
复制代码
相信这个类也很容易理解,这里的OnError我写了一个Toast。因此每次请求的回调onError能够不重写,会自动弹Toast,若是你须要有其余操做,好比说不弹Toast,是须要打开另一个页面则能够重写这个方法如:cookie
@Override
public void onError(String errorMessage) {
super.onError(errorMessage);
//注释super.onError(errorMessage);那么不会走父类方法,
}
复制代码
public class OkGetBuilder {
private String url;
private String tag;
private Map<String, String> headers;
private Map<String, String> params;
private OkHttpClient okHttpClient;
private Context context;
private Handler mDelivery;
private Request okHttpRequest;
public OkGetBuilder() {
this.okHttpClient = EasyOk.getInstance().getOkHttpClient();
this.context = MyApplication.getContext();
this.mDelivery = EasyOk.getInstance().getmDelivery();
}
public OkGetBuilder build() {
Request.Builder mBuilder = new Request.Builder();
if (params != null) {
mBuilder.url(appendParams(url, params));
} else {
mBuilder.url(url);
}
if (!TextUtils.isEmpty(tag)) {
mBuilder.tag(tag);
}
if (headers != null) {
mBuilder.headers(appendHeaders(headers));
}
okHttpRequest = mBuilder.build();
return this;
}
public void enqueue(final ResultMyCall resultMyCall) {
if (resultMyCall != null) {
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onBefore();
}
});
}
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
if (resultMyCall != null) {
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
String errorMsg;
if (e instanceof SocketException) {
} else {
if (e instanceof ConnectException) {
errorMsg = context.getString(R.string.network_unknow);
} else if (e instanceof SocketTimeoutException) {
errorMsg = context.getString(R.string.network_overtime);
} else {
errorMsg = context.getString(R.string.server_error);
}
resultMyCall.onError(errorMsg);
}
}
});
}
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
//网络请求成功
if (response.isSuccessful()) {
if (resultMyCall != null) {
String result = response.body().string();
Object successObject = null;
try {
if (resultMyCall.getType() == null) {
successObject = result;
} else {
successObject = GsonUtil.deser(result, resultMyCall.getType());
}
} catch (Throwable e) {
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
resultMyCall.onError("数据解析出错了");
}
});
return;
}
if (successObject == null) {
successObject = result;
}
final Object finalSuccessObject = successObject;
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
resultMyCall.onSuccess(finalSuccessObject);
}
});
}
} else {
//接口请求确实成功了,code 不是 200
if (resultMyCall != null) {
final String errorMsg = response.body().string();
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
resultMyCall.onError(errorMsg);
}
});
}
}
}
});
}
private Headers appendHeaders(Map<String, String> headers) {
Headers.Builder headerBuilder = new Headers.Builder();
if (headers == null || headers.isEmpty()) return null;
for (String key : headers.keySet()) {
headerBuilder.add(key, headers.get(key));
}
return headerBuilder.build();
}
//get 参数拼在url后面
private String appendParams(String url, Map<String, String> params) {
StringBuilder sb = new StringBuilder();
if (url.indexOf("?") == -1) {
sb.append(url + "?");
} else {
sb.append(url + "&");
}
if (params != null && !params.isEmpty()) {
for (String key : params.keySet()) {
sb.append(key).append("=").append(params.get(key)).append("&");
}
}
sb = sb.deleteCharAt(sb.length() - 1);
LogUtils.i("网络请求", "请求接口 ==>> " + sb.toString());
return sb.toString();
}
}
复制代码
首先咱们new这个类的时候把惟一的okHttpClient拿到用来进行请求,把mDelivery拿到,用于子线程切换主线程,那么在后面的回调方法里能够直接进行UI操做。OkGetBuilder里要作的是把heads用map传入,那么要进行一个循环add到head上去如:appendHeaders方法,参数也用map传入,拼接参数如:appendParams方法。网络
那么接下来就是请求这块
public static OkGetBuilder get() {
return new OkGetBuilder();
}
复制代码
//这些是所有方法,没有用到的不使用
//paramsBuilder 是我封装用的一个传递参数的类。有要用的参数一致点下去就行了...
EasyOk.get().url("http://gank.io/api/xiandu/category/wow")
.tag("cancleTag")
//内部已经作了null处理,请求头部
//.headers(paramsBuilder.getHeads())
//内部已经作了null处理,请求参数
//.params(paramsBuilder.getParams())
.build().enqueue(new ResultMyCall<T>() {
@Override
public void onBefore() {
super.onBefore();
}
@Override
public void onAfter() {
super.onAfter();
}
@Override
public void onError(String errorMessage) {
super.onError(errorMessage);
}
@Override
public void onSuccess(Object response) {
super.onSuccess(response);
//若是你再new ResultMyCall的时候带了泛型,那么这里只须要
//T bean = (T)response ;
//若是没有带泛型,那么默认返回的string类型,
//Sring bean = (String)response;
}
});
复制代码
若是onBefore和onAfter还有onError,都把统一操做封装好了而且不须要重写没有特殊操做的你能够这样:
EasyOk.get().url("http://gank.io/api/xiandu/category/wow")
.tag("cancleTag")
.build().enqueue(new ResultMyCall<T>() {
@Override
public void onSuccess(Object response) {
super.onSuccess(response);
//若是你再new ResultMyCall的时候带了泛型,那么这里只须要
//T bean = (T)response ;
//若是没有带泛型,那么默认返回的string类型,
//Sring bean = (String)response;
}
});
复制代码
上面介绍我我把缓存和重连等其余功能没有说,由于加进去太复杂也不清晰,等后面单独拿出来说,经过本文的理解,你会知道怎么去封装,加到什么地方去。
通过上述介绍,我们直接看OkDownloadBuilder里的内容(我把多余部分去掉了,便于理解):
public class OkDownloadBuilder {
//断点续传的长度
private long currentLength;
private String url;
private String tag;
//文件路径(不包括文件名)
private String path;
//文件名
private String fileName;
//是否开启断点续传
private boolean resume;
private OkHttpClient okHttpClient;
private Handler mDelivery;
private Request.Builder mBuilder;
public OkDownloadBuilder() {
this.okHttpClient = EasyOk.getInstance().getOkHttpClient();
this.mDelivery = EasyOk.getInstance().getmDelivery();
}
public OkDownloadBuilder build() {
mBuilder = new Request.Builder();
mBuilder.url(url);
if (!TextUtils.isEmpty(tag)) {
mBuilder.tag(tag);
}
//这里只要断点上传,总会走缓存。。因此强制网络下载
mBuilder.cacheControl(CacheControl.FORCE_NETWORK);
return this;
}
public void enqueue(final OnDownloadListener listener) {
if (resume) {
File exFile = new File(path, fileName);
if (exFile.exists()) {
currentLength = exFile.length();
mBuilder.header("RANGE", "bytes=" + currentLength + "-");
}
}
Request okHttpRequest = mBuilder.build();
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
//下载失败监听回调
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadFailed(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[1024];
int len = 0;
FileOutputStream fos = null;
//储存下载文件的目录
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
final File file = new File(dir, fileName);
try {
is = response.body().byteStream();
//总长度
final long total;
//若是当前长度就等于要下载的长度,那么此文件就是下载好的文件
//前提是这里是默认下载的赞成文件,要判断是否能够断点续传,最好在开启网络的时候判断是不是赞成版本号
if (currentLength == response.body().contentLength()) {
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadSuccess(file);
}
});
return;
}
if (resume) {
total = response.body().contentLength() + currentLength;
} else {
total = response.body().contentLength();
}
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownLoadTotal(total);
}
});
if (resume) {
//这个方法是文件开始拼接
fos = new FileOutputStream(file, true);
} else {
//这个是不拼接,从头开始
fos = new FileOutputStream(file);
}
long sum;
if (resume) {
sum = currentLength;
} else {
sum = 0;
}
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
final int progress = (int) (sum * 1.0f / total * 100);
//下载中更新进度条
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloading(progress);
}
});
}
fos.flush();
//下载完成
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadSuccess(file);
}
});
} catch (final Exception e) {
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadFailed(e);
}
});
} finally {
try {
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
}
}
}
});
}
}
复制代码
若是你不看断点续传这块,在onResponse里其实就是输入流和文件流,把流写进文件里的操做;断点的续传的关键点是哪些?要知道断点续传其实就是接着上次未下载的文件继续下载(固然这里要确保下载的是同一文件,以下载更新要保证是同一版本号,这里在得到版本更新内容的时候判断是不是同一版本)。关键有2点:
mBuilder.header("RANGE", "bytes=" + currentLength + "-");
复制代码
fos = new FileOutputStream(file, true);//没错就是这个true
复制代码
场景以下:假如首页广告,get请求下来的数据,并且这个可能1个星期才会换一次数据,这个时候若是没有这个功能,每次进首页都会去请求网络,若是有这功能,那么若是缓存内容在有效期就会跳过网络请求,直接取缓存。这样节约流量之余还能减轻服务器压力。固然要实现缓存,要设置缓存文件,在初始化okHttpClient时候设置缓存文件
//设置缓存文件路径,和文件大小
okHttpClent.cache(new Cache(new File(Environment.getExternalStorageDirectory() +"/okhttp_cache/"), 50 * 1024 * 1024))
复制代码
查阅大量的资料,大量博客都很坑。坑的你懵逼这里给你们看下正解:okhttp缓存正解,文字地址
看到这篇的时候,你就知道其余地方均可以不用管,用拦截器能够实现,可是要清楚什么是在线缓存,什么是离线缓存。这是2个概念。在线缓存须要加网络拦截器
okHttpClient.addNetworkInterceptor(NetCacheInterceptor.getInstance())
复制代码
这里用的拦截器,我使用了单例,这样便于经过改变参数,能够达到是否使用缓存;NetCacheIntertor代码以下:
/** * Created by leo * on 2019/7/25. * 在有网络的状况下 * 若是还在网络有效期呢则取缓存,不然请求网络 * 重点 : 通常okhttp只缓存不大改变的数据适合get。(我的理解 : 例如你设置了个人方案列表接口的缓存后,你删除了一条方案,刷新下。 * 他取的是缓存,结果那条删除的数据会出来。这个时候这个接口,不适合用缓存了) * (这里注意,若是一个接口设置了缓存30秒,下次请求这个接口的30秒内都会去取缓存,即便你设置0也不起效。由于缓存文件里的标识里已经有30秒的有效期) */
public class NetCacheInterceptor implements Interceptor {
private static NetCacheInterceptor cacheInterceptor;
//30在线的时候的缓存过时时间,若是想要不缓存,直接时间设置为0
private int onlineCacheTime;
public static NetCacheInterceptor getInstance() {
if (cacheInterceptor == null) {
cacheInterceptor = new NetCacheInterceptor();
}
return cacheInterceptor;
}
private NetCacheInterceptor() {
}
public void setOnlineTime(int time) {
this.onlineCacheTime = time;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder1 = request.newBuilder();
//这里咱们登录,在head里获取令牌token存起来,网络请求的时候把令牌加入head,用于身份区分
String token = (String) PreferenceUtil.get("USER_TOKEN", "");
if (!TextUtils.isEmpty(token)) {
builder1.addHeader("Token", token)
.build();
}
request = builder1.build();
Response response = chain.proceed(request);
List<String> list = response.headers().values("Token");
if (list.size() > 0) {
PreferenceUtil.put("USER_TOKEN", list.get(0));
}
//这里是设置缓存的操做
if (onlineCacheTime != 0) {
//若是有时间就设置缓存
int temp = onlineCacheTime;
Response response1 = response.newBuilder()
.header("Cache-Control", "public, max-age=" + temp)
.removeHeader("Pragma")
.build();
onlineCacheTime = 0;
return response1;
} else {
//若是没有时间就不缓存
Response response1 = response.newBuilder()
.header("Cache-Control", "no-cache")
.removeHeader("Pragma")
.build();
return response1;
}
// return response;
}
}
复制代码
max-age是在线缓存的有效时间,若是我设置了max-age = 3600,那么意思是在首次请求网络缓存下来的数据后,在1小时以内都将会直接取缓存,跳过网络请求。这里我获取token是经过拦截器获取的,response.headers()能够得到,服务器返回的全部head信息,包括set-cookie信息。并且okhttp提供了.cookjar()。能够经过cookie持久化等自定义
这些设置完怎么验证呢?回到你原始网络请求onResponse回调里;经过:
if (response.networkResponse()!=null){
LogUtils.i("内容来源","来自网络请求");
}
if (response.cacheResponse()!=null){
LogUtils.i("内容来源","来自缓存");
}
复制代码
固然这里只是验证,在onResponse不须要改变,okhttp内部已经作好了全部的工做。
例如腾讯新闻等,在你手机开启飞行模式的时候,在进app的时候,仍是会依旧显示以前加载的数据。这个是就是离线缓存,离线缓存和在线缓存最大的区别,在线缓存即便有条件请求网络也能够跳过网络取缓存。一样经过拦截器添加,在无网络的时候,是不会走addNetworkInterceptor方法的。可是经过addInterceptor,有没有网都会走,并且addInterceptor会先于addNetworkInterceptor运行
okHttpClient.addInterceptor(OfflineCacheInterceptor.getInstance());
复制代码
一样用了单例,具体以下:
/** * Created by leo * on 2019/7/25. * 这个会比网络拦截器先 运行 * 在没有网络链接的时候,会取的缓存 * 重点 : 通常okhttp只缓存不大改变的数据适合get。(我的理解,无网络的时候能够将无网络有效期改长点) * 这里和前面的不一样,当即设置,当即生效。例,你一个接口设置1个小时的离线缓存有效期,当即设置0.下次进入后,则无效 */
public class OfflineCacheInterceptor implements Interceptor {
private static OfflineCacheInterceptor offlineCacheInterceptor;
//离线的时候的缓存的过时时间
private int offlineCacheTime;
private OfflineCacheInterceptor() {
}
public static OfflineCacheInterceptor getInstance() {
if (offlineCacheInterceptor == null) {
offlineCacheInterceptor = new OfflineCacheInterceptor();
}
return offlineCacheInterceptor;
}
public void setOfflineCacheTime(int time) {
this.offlineCacheTime = time;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkUtils.isNetworkConnected(MyApplication.getContext())) {
if (offlineCacheTime != 0) {
int temp = offlineCacheTime;
request = request.newBuilder()
// .cacheControl(new CacheControl
// .Builder()
// .maxStale(60,TimeUnit.SECONDS)
// .onlyIfCached()
// .build()
// ) 两种方式结果是同样的,写法不一样
.header("Cache-Control", "public, only-if-cached, max-stale=" + temp)
.build();
offlineCacheTime = 0;
} else {
request = request.newBuilder()
.header("Cache-Control", "no-cache")
.build();
}
}
return chain.proceed(request);
}
}
复制代码
NetWorkUtils是一个判断有没有网络的工具类。你能够看到这里是max-stale,这里我理解就是要设置的离线缓存有效期,若是设置为max-stale=3600就是离线缓存1个小时。若是你去深山老林,在长达1小时01分的时候,离线缓存失效,那么你在此进app,页面将空白。固然你也能够设置离线缓存一直有效,Integer.MAX_VALUE。
这里固然你能够利用tag来作,若是当前正在请求的池里的call.request.tag()或等待请求队列里的tag,包含你当前请求网络tag时,则不请求网络,只显示loading。可是这样的话必须每次都要加上tag。因此我直接在EasyOk里加上了一个
//防止网络重复请求的tagList;
private ArrayList<String> onesTag;
复制代码
若是没有tag的时候,这里我能够用请求url。在网络请求成功和结束的时候我从这个集合里remove掉这个元素。固然你会说,当咱们取消网络请求的时候呢,其实取消网络请求的时候会走onFailure(Call call,IOException e)。这个时候错误类型是SocketException。因此你取消网络的时候是会走onFailure,一样会remove掉、代码大体以下(只放相关代码):
public void enqueue(final ResultCall resultMyCall) {
if (resultMyCall != null) {
//这里是子线程切换到主线程的操做,只要请求网络,咱们都调onBefore,这里就是展现loading
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onBefore();
}
});
}
//这里是否带了onlyOneNet参数,默认是不开启的,return后将不继续往下走,就不会开启网络了
if (onlyOneNet) {
if (!TextUtils.isEmpty(tag)) {
if (EasyOk.getInstance().getOnesTag().contains(tag)) {
return;
}
EasyOk.getInstance().getOnesTag().add(tag);
} else {
if (EasyOk.getInstance().getOnesTag().contains(url)) {
return;
}
EasyOk.getInstance().getOnesTag().add(url);
}
}
...
}
复制代码
这里也查阅了大量博客,都说okhttp有设置重连,设置okHttpClient.retryOnConnectionFailure(true)既可用重连,可是我大量测试发现,然并软(有明白的小伙伴,求告知)。这里我用了本身的方式,只要走onFailure,那么咱们看看有没有设置重连和重连次数,代码以下,此代码都在builder下,每次网络请求都会new一个builder,以OkGetBuilder为例:
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
//这里是取消网络请求,那么不用重连了
if (e instanceof SocketException) {
} else {
//tryAgainCount是重连,不设置,默认是0.不开启重连功能
//currentAgainCount,是OkGetBuilder里的属性,每次开启网络都会new一个Builder
//固然currentAgainCount初始是0
if (currentAgainCount < tryAgainCount && tryAgainCount > 0) {
currentAgainCount++;
//这里就是重连操做,call.request会获取你最开始的request
//this这里就是你当前new Callback,因此网络回调还会走这里
okHttpClient.newCall(call.request()).enqueue(this);
return;
}
}
}
复制代码
我定义了NetWorListener(get,post,上传文件网络回调),OnDownloadListener(文件下载网络回调),PermissionListener(权限申请结果回调),若是你想请求网络,只要实现这个接口,而后经过ModelSuperImpl去调用网络请求,就能在任何你实现这个接口的页面拿到网络请求回调,请求只须要这样:
//在外面调用只须要传入参数,把url和解析类什么的都放在ModelSuperImpl里
ModelSuperImpl.netWork().gankGet(ParamsBuilder.build().params(PARAMS.gank("android")) .command(GANK_COMMAND), this);
复制代码
ModelSuperImpl大体以下
public class ModelSuperImpl extends ModelBase {
private static final ModelSuperImpl ourInstance = new ModelSuperImpl();
public static ModelSuperImpl netWork() {
return ourInstance;
}
public static ModelPermissionImpl permission() {
return new ModelPermissionImpl();
}
private ModelSuperImpl() {
}
public void gankGet(ParamsBuilder paramsBuilder, NetWorkListener netWorkListener) {
paramsBuilder.url(SystemConst.GANK_GET)
.type(new TypeToken<ResponModel<User>>() {
}.getType())
;
sendOkHttpGet(paramsBuilder, netWorkListener);
}
}
复制代码
在Activity/Fragment里或是只要实现接口(NetWorListener)的地方拿到回调就是这样,command是为了区分一个页面可能请求多个网络请求:
@Override
public void onNetCallBack(int command, Object object) {
switch (command) {
case GANK_COMMAND:
Response<User> userModel = (Response<User>)object;
break;
}
}
复制代码
这里的ParamsBuilder有多个参数具体以下(去掉部分代码,偏于理解):
public class ParamsBuilder {
//请求网络的url(必填)
private String url;
//网络回调的int值(必填)
private int command;
//网络返回的type类型(选填)不填,则会返回string类型
private Type type;
//网络请求须要带的头部信息(选填,不填为null)
private HashMap<String, String> heads;
//网络请求须要带的参数(选填,不填为null)
private HashMap<String, String> params;
//网络loading须要带的文字信息(选填,不填为null)
private String loadMessage;
//是否显示网络loading(默认为显示loading)
private boolean isShowDialog = true;
//网络请求的tag,可根据tag取消网络请求(选填,不填:默认当前宿主类名,退出后自动取消)
private String tag;
//是否重写网络问题仍是超时问题对回调进行一个重写
//若是是true,则在回调的时候可对那部分额外操做,除了弹提示还能够作别的操做
//(选填,不填:重写不了且只弹提示)
private boolean overrideError;
//json上传要带的参数
private String json;
//网络接口code=200, 但没有成功,此用户已关注
//须要重写带true,重写能够写逻辑包括弹提示
//不须要重写只弹提示
private boolean successErrorOverrid;
//离线缓存时间 单位秒
private int cacheOfflineTime;
//有网络请求时缓存最大时间
private int cacheOnlineTime;
//屡次点击按钮,只进行一次联网请求
//场景:网络还在loading,又点了一次请求,那么不发送新请求,只显示loading
private boolean onlyOneNet = true;
//联网失败,重试次数
private int tryAgainCount;
//若是是在网络请求接口回调不是activity,也不是fragment,用于传context
//用于showdialog,当请求网络的页面不是Activity或是Fragment时必传
private Context context;
/** * 下载文件才用的到 */
private String path;
private String fileName;
//是否开启断点续传,要注意的是开启断点续传,要保证下载的是同一文件
//默认是不开启断点续传,除非判断要下载文件和当前未下载文件属于同一文件
//若是不是那么从新下载,会清掉以前的文件。
private boolean resume;
}
复制代码
看到上面,具体设计的参数都写上了。可是我没有封装的很完美,固然必传的字段,没传时,你能够throw new NullPonintException("参数没传");把异常抛出去,一旦不传,程序就崩溃了。
一样在下载文件只须要实现OnDownliadListener,便可。调用只须要这样:
ModelSuperImpl.netWork().downApk(ParamsBuilder.build().path(path)
.fileName(fileName).tag("downApk"), this);
复制代码
这里我把请求权限全部逻辑封装在了 ModelPerissionImpl里,只要实现PerimissionListener便可拿到网络请求回调,调用只需这样:
/RESUME_COMMAND一个页面可能要请求多个权限,用于区分,this便是PerimissionListener实现类,后面权限参数是可变的若是有多个权限能够一直逗号加下去
ModelSuperImpl.permission().requestPermission(RESUME_COMMAND, this, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE);
复制代码
回调只需这样:
//回调只须要这样;command是区分一个页面多个权限申请,若是一个页面只有1个申请那么能够不传commad
@Override
public void permissionSuccess(int command) {
switch (command) {
case NORMAL_COMMAND:
ModelSuperImpl.netWork().downApk(ParamsBuilder.build().path(path)
.fileName(fileName).tag("downApk"), this);
break;
}
}
复制代码
大体思路:我把具体的联网操做和具体的解析封装在了抽象类ModelBase,把具体的权限申请逻辑封装在了ModelPerissionImpl,ModelSuperImpl只负责调用网络请求方法,和权限申请方法,这样代码完美分隔了代码。视图层View收到了用户的操做或点击请求,响应controller,controller通知model处理逻辑业务,拿到结果经过接口告诉controller去更新Ui。加入你多个页面须要请求同一个url,你只须要经过ModelSuperImpl去调用方法就Ok了。
这里确定不少人对ParamBuilder里的overrideError和successErrorOverrid不是很理解。其实这里默认不是重写方法,例如网络请求失败,我在ModelBase默认是弹出toast,若是是code=200,可是有可能接口走的错误方法,如关注失败,我也是默认不重写弹出toast。有可能实际操做须要咱们作别的,如网络请求错误,须要咱们跳另一个页面,那么这个时候就须要你带.overrideError(true)和.successErrorOverrid(true)代码以下:
@Override
public void onNetCallBack(int command, Object object) {
switch (command) {
case GANK_COMMAND:
/** * 请求接口失败(网络错误,或超时等形成) * 若是须要重写则请求接口的时候加上.overrideError(true) * 那么在下面代码写上逻辑,若是不须要重写,已经封住了会自动弹出错误提示,并且重 写会无效 * */
// if (obj instanceof NetFail) {
// NetFail netFailBean = (NetFail) obj;
// 处理逻辑
// return;
// }
/** * 这是接口请求成功 * 若是请求接口,写了.successErrorOverrid(true) * 说明重写了虽然code=200,返回的result 不是1;可是须要作其余逻辑不仅是Toast才须要true * 若是只是须要Toast错误信息,那么能够不写,下面的就不用重写了。封装已经默认弹提示 * */
// if (obj instanceof ErrorBean) {
// ErrorBean errorBean = (ErrorBean) obj;
// 处理逻辑
// return;
// }
ResponModel<User> detailModel = (ResponModel<User>) obj;
//更新UI
break;
}
}
复制代码
记得把builder里的EventBus所有删除,我以前没考虑太多,因此在展现效果的时候用EventBus传递了p.p 固然这里用的泛型类ResponModel,ErrorBean,NetFail都是我根据个人项目来定义的,记得若有数据结构不一样请修改为须要的