本身封装一个okhttp,一个看的懂的okhttp封装

前言:封装只是加深本身的理解,网上已经有很优秀的封装,我也是借鉴了okgo鸿洋的okhttputils。本项目是基于mvc模式下,但这篇只讲如何对okhttp进行封装(这里我按最基础步骤来,须要额外功能,看源码和本文理解,确定能够实现)java

咱们封装要有的功能有:android

  • 支持get请求
  • 支持post请求
  • 支持上传文件
  • 支持下载文件和断点续传
  • 有网络时,支持缓存(链接网络时的有效期)
  • 断开网络,支持离线缓存(离线缓存有效期)
  • 屡次请求同一url,在网络还在请求时,是否只请求一次
  • 支持请求失败,自动重连

先看看我封装的效果(建议打开权限)

get请求 post请求 上传文件
下载文件

首先okhttp进行简单get请求代码是这样的:

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

一、封装EasyOk

1.1 取消网络请求

//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

1.2 简单的EasyOk封装

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

二、封装OkGetBuilder(这里我将每种请求封装成了不一样的Builder,虽然重复了好多操做,但更加清晰,偏于理解)

由于每次请求,Request 请求体都是须要new的,因此可想而知这里不多是单例,并且每次调用请求都是new出来的Request。根据最原始的get请求,咱们知道OkGetBuilder里须要一、url,二、参数,三、header,四、tag,五、还有本身的网络回调。缓存

2.1 自定义请求回调抽象类(基于mvc二次封装建议用接口类)

因此咱们得用有个网络回调接口,这里我用的是抽象类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);那么不会走父类方法,
 
            }
复制代码

2.2 简单OkGetBuilder封装(去掉了部分代码,偏于理解清晰)

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方法。网络

那么接下来就是请求这块

  • 在调用自定义方法enqueue(我这里和okhttp重名了偏于理解),首先咱们这里就要回调onBefore
  • 在原始回调失败onFailure里,固然是回调咱们的onAfter和onError。
  • 在原始回调onResponse里,这里会比较麻烦,即便返回code=200;这里还有2种状况,一种是正常的按你传入的泛型解析,一种是好比:点击关注,网络请求也成功了,但接口问题返回关注失败。咱们公司用的是status为0表明失败,这里要特别注意
  • OkGetBuilder封装好了,把它放进EasyOk里以下:
public static OkGetBuilder get() {
        return new OkGetBuilder();
    }
复制代码

三、最后封装后的进行GET请求用法以下:(其实post和上传文件都是这个思路,不一样的是post有多种RequestBody,上传文件也是post的一种,这里具体能够看鸿洋和okgo的封装)

//这些是所有方法,没有用到的不使用
//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点:

  • 在开启文件下载的时候,要在header里加上当前文件长度
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。

七、屡次请求同一url,在网络请求未结束,是否只请求一次

这里固然你能够利用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;
                    }
                }
        
        }
复制代码

至此,大体介绍完了。以上介绍的到底怎么用。能够去个人github上的介绍,看看以上的用法

九、那么基于mvc封装怎么用,下面咱们具体来讲本项目封装的用法

我定义了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;
        }
    }
复制代码

结束语:我我的思路封装。若有不对欢迎指正,且若是有更好的思路欢迎留言。技术界的小学生,喜欢学习。看到这里,若是有帮助到你,帮博主star下吧

github传送门

记得把builder里的EventBus所有删除,我以前没考虑太多,因此在展现效果的时候用EventBus传递了p.p 固然这里用的泛型类ResponModel,ErrorBean,NetFail都是我根据个人项目来定义的,记得若有数据结构不一样请修改为须要的

相关文章
相关标签/搜索