OkHttp的用法和高效配置

来源:互联网 发布:手机神奇软件 编辑:程序博客网 时间:2024/06/07 03:56

转载请声明来自http://blog.csdn.net/super_kingking/article/details/70992012

现在OkHttp越来越火,并伴随着RxJava,Retrofit的组合,功能非常的强大,使其越受开发者的关注,作为一个Android程序员的我,今天也来记录一下学习的过程。

来看一下官网的介绍:
An HTTP & HTTP/2 client for Android and Java applications

OverviewHTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.OkHttp is an HTTP client that’s efficient by default:HTTP/2 support allows all requests to the same host to share a socket.Connection pooling reduces request latency (if HTTP/2 isn’t available).Transparent GZIP shrinks download sizes.Response caching avoids the network completely for repeat requests.OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7

我就不用我那蹩脚的英文翻译了,直接上google翻译:

概述
HTTP是现代应用网络的方式。这是我们如何交换数据和媒体。有效地进行HTTP使您的东西加载更快,并节省带宽。

OkHttp是默认情况下高效的HTTP客户端
优点:

  • HTTP和HTTP/2支持允许同一主机的所有请求共享套接字。
  • 连接池复用,减少请求延迟(如果HTTP / 2不可用)。
  • 透明GZIP缩小下载大小。
  • 响应缓存可以避免重复请求的网络。

网络异常处理:

当网络麻烦时,OkHttp坚持不懈:它将从常见的连接问题中静默地恢复。如果您的服务有多个IP地址,如果第一个连接失败,OkHttp将尝试替代地址。这对于IPv4 + IPv6以及在冗余数据中心中托管的服务是必需的。OkHttp启动与现代TLS功能(SNI,ALPN)的新连接,如果握手失败,则返回TLS 1.0。

功能和支持版本:

  • 使用OkHttp很容易 它的请求/响应API设计有流畅的构建器和不变性。它支持同步阻塞调用和具有回调的异步调用。
  • OkHttp支持Android 2.3及以上版本。对于Java,最低要求是1.7。

就像上面描述的一样,okHttp是一款优秀而又强大的网络框架。

使用教程
Androidstudio用户可以直接饮用最新的依赖

compile 'com.squareup.okhttp3:okhttp:3.7.0'

最新的jar报地址是: Download the latest JAR

(1)get请求

    //创建okHttpClient对象        OkHttpClient okHttpClient = new OkHttpClient();        //创建Request对象        Request request = new Request.Builder().url("http://www.android-studio.org/").build();        //创建call        Call call = okHttpClient.newCall(request);        //加入调度        call.enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                //异步的get请求获取response在子线程中,不能在此更新UI                 String responseStr = response.body().toString();            }        });

上面是一个异步的get网络请求,首先创建一个okHttpClient 对象,然后构造request对象,除了最基本的url参数,request还可以添加其他参数比如:

  • header:添加header参数
  • cacheControl:需要缓冲设置CacheControl.FORCE_CACHE,要网络请求CacheControl.FORCE_NETWORK
  • method:GET,POST,DELETE,PUT等请求方式
  • tag : 当call.cancel()可以立即停止掉一个正在执行的call,如果一个线程正在写请求或者读响应,将会引发IOException
  • ….

同步网络请求调用call.execute();
取消网络调用call.cancel()
如果是异步请求调用call.enqueue()加入调度对列中获取响应response,response.body().toString()获取返回的字符串,异步可以通过handler机制将response传递到主线程.

(2)post请求

  • post提交键值对
OkHttpClient okHttpClient = new OkHttpClient();        RequestBody requestBody =  new FormBody.Builder().add("key1","value2")        .add("key2","value2")...build();        Request request = new Request.Builder().post(requestBody).url("...").build();        Call call = okHttpClient.newCall(request);        call.enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                String responseStr = response.body().toString();            }        });

我们可以通过FormBody(FormBody是RequestBody的子类),添加key—value参数,构造requestBody 调用post(requestBody)。

  • post提交Json
public static final MediaType JSON= MediaType.parse("application/json; charset=utf-8");String json = "";OkHttpClient client = new OkHttpClient();  RequestBody body = RequestBody.create(JSON, json);  Request request = new Request.Builder()      .url("")      .post(body)      .build();  call.enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                String responseStr = response.body().toString();            }        });

设置MediaType type,RequestBody.create(JSON, json)方式获取requestBody,传入到request中

  • Post方式提交String
MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String postBody = ""; Request request = new Request.Builder()        .url("...")        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))        .build(); ......}

其他用法可以参考OkHttp使用教程

okhttp用了大量的Builder设计模式,参数的扩展十分的方便。OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个。有很多大神已经封装好的okhttp工具大家可以拿来使用okhttp-OkGo,okhttputils….

封装的核心思想:

  1. 避免重复代码调用
  2. 将请求结果回调改为UI线程

前面已经有很多大神封装好了这优秀的代码,再次就不在闭门到轮子了。

下面谈一下如何配置一个高效地okhttp:

  1. 把OkHttpClient对象做成单例模式
  2. 设置网络连接超时时间
  3. 添加拦截器
  4. 请求缓存设置

OkHttpClient对象单例模式

  • 很多开发者喜欢使用DCL(double check lock)双重判断形式的单例模式:
 public static OkHttpClientUtil getInstance()    {        if (mInstance == null)        {            synchronized (OkHttpClientUtil.class)            {                if (mInstance == null)                {                    mInstance = new OkHttpClientUtil();                }            }        }        return mInstance;    }

DCL形式虽然在一定程度上是解决了资源消耗,多余的同步和线程安全问题,但是在高并发环境下也是有一定的缺陷,有时候并不是安全的,虽然发生的概率很小。我更推荐使用静态内部类单例模式,如果有兴趣的同学可以阅读这篇文章单例设计模式

静态内部类单例模式:

 public static OkHttpClientUtil getInstance() {        return SingleHolder.singleTon;    }    private static class SingleHolder {        private static final OkHttpClientUtil singleTon = new OkHttpClientUtil();    }

第一次加载单例类时并不会初始化singleTon ,只有在第一次调用getInstance()是才回导致singleTon的初始化,因此第一次调用getInstance()方法时会导致虚拟机加载SingleHolder类,这种方式不仅能够确保线程安全,还能保证单例对象的唯一性,同时也是延迟了单例的实例化,所以更推荐使用静态内部类形式的单例模式。

设置网络连接超时时间

 mOkHttpClient = new OkHttpClient.Builder()                            .retryOnConnectionFailure(true)                            .connectTimeout(15, TimeUnit.SECONDS)                            .writeTimeout(20,TimeUnit.SECONDS)                            .readTimeout(20,TimeUnit.SECONDS)                            .build();

retryOnConnectionFailure(true):网络连接失败重新请求。
connectTimeout(15, TimeUnit.SECONDS):网络连接超时时间是20秒
writeTimeout(20,TimeUnit.SECONDS),readTimeout(20,TimeUnit.SECONDS):读写超时时间各位20秒

添加拦截器
拦截器:Application interceptor应用拦截器和Network Interceptor网络拦截器
借鉴 Interceptors 拦截器

Application interceptor:

  • 不需要担心中间过程的响应,如重定向和重试.
  • 总是只调用一次,即使HTTP响应是从缓存中获取.
  • 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
  • 允许短路而不调用 Chain.proceed(),即中止调用.
  • 允许重试,使 Chain.proceed()调用多次.

Network Interceptor:

  • 能够操作中间过程的响应,如重定向和重试.
  • 当网络短路而返回缓存响应时不被调用.
  • 只观察在网络上传输的数据.
  • 携带请求来访问连接.

    设置应用拦截器的HttpLoggingInterceptor拦截器:

 HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);        mOkHttpClient = new OkHttpClient.Builder()                            .addInterceptor(interceptor)                            .retryOnConnectionFailure(true)                            .connectTimeout(15, TimeUnit.SECONDS)                            .writeTimeout(20,TimeUnit.SECONDS)                            .readTimeout(20,TimeUnit.SECONDS)                            .build();

网络拦截器在下面的缓存设置中设置。

请求缓存设置

 Cache cache = new Cache(new File(context.getCacheDir(), "HttpCache"),                            1024 * 1024 * 10); mOkHttpClient = new OkHttpClient.Builder()                            .cache(cache)                            .addInterceptor(interceptor)                            .retryOnConnectionFailure(true)                            .connectTimeout(15, TimeUnit.SECONDS)                            .writeTimeout(20,TimeUnit.SECONDS)                            .readTimeout(20,TimeUnit.SECONDS)                            .build();

google推荐使用context.getCacheDir()路径,当应用删除时缓存自动清空文件夹的数据。

设置网络拦截器Network Interceptor:

    private Interceptor cacheControlInterceptor = new Interceptor() {        @Override        public Response intercept(Chain chain) throws IOException {        //拦截request            Request request = chain.request();            if (!NetUtil.isNetworkConnected()) {                //如果没有网络则设置Request读取缓存                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();            }            //拦截response            Response originalResponse = chain.proceed(request);            if (NetUtil.isNetworkConnected()) {                //有网的时候获取自定义的Request请求设置,是否读取缓存                String cacheControl = request.cacheControl().toString();                return originalResponse.newBuilder()                        .removeHeader("Pragma")//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效首先清空Pragma                        .header("Cache-Control", cacheControl)                        .build();            } else {                //没有网络的时候统一设置读取缓存                return originalResponse.newBuilder()                        .removeHeader("Pragma")                        //only-if-cached只查询缓存而不会请求服务器                        .header("Cache-Control", "public, only-if-cached, max-stale=" + CACHE_STALE_LONG)                        .build();            }        }    };

可以在此设置网络请求request的header配置,例如如果没有网络的情况下读取缓存数据。
response拦截也是一样通过header配置,在没有网络的时候都是读取缓存。
但是在有网络的情况下就不一样了,不能在有网络的情况下一概而论的设置cacheControl是否读取缓存,比如在下拉刷新的需要进行网络请求数据,如果在此进行统一设置为读取缓存的话就不合适了,所以在有网络的情况下根据当前的Request的header配置进行设置cacheControl。

下面贴出最终的okhttpclient配置代码:

public class OkHttpClientUtil {    //网络缓存时间为7天    public static final int CACHE_STALE_LONG = 60 * 60 * 24 * 7;    //查询缓存的Cache-Control设置,为if-only-cache时只查询缓存而不会请求服务器,max-stale可以配合设置缓存失效时间    public static final String CACHE_CONTROL_CACHE = "only-if-cached, max-stale=" + CACHE_STALE_LONG;    //查询网络的Cache-Control设置,头部Cache-Control设为max-age=0时则不会使用缓存而请求服务器    public static final String CACHE_CONTROL_NETWORK = "max-age=0";    public final OkHttpClient mOkHttpClient;    private OkHttpClientUtil() {        Cache cache = new Cache(new File(App.getContext().getCacheDir(), "HttpCache"),                1024 * 1024 * 10);//10M        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);        mOkHttpClient = new OkHttpClient.Builder()                .cache(cache)                .addInterceptor(interceptor)                .addNetworkInterceptor(cacheControlInterceptor)                .retryOnConnectionFailure(true)                .connectTimeout(15, TimeUnit.SECONDS)                .writeTimeout(20, TimeUnit.SECONDS)                .readTimeout(20, TimeUnit.SECONDS)                .build();    }    private Interceptor cacheControlInterceptor = new Interceptor() {        @Override        public Response intercept(Chain chain) throws IOException {            Request request = chain.request();            if (!NetUtil.isNetworkConnected()) {                //如果没有网络则设置Request是读取缓存                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();            }            Response originalResponse = chain.proceed(request);            if (NetUtil.isNetworkConnected()) {                //有网的时候获取自定义的Request请求设置,是否读取缓存                String cacheControl = request.cacheControl().toString();                return originalResponse.newBuilder()                        .removeHeader("Pragma")//Pragma:no-cache,在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同,为了确保缓存生效                        .header("Cache-Control", cacheControl)                        .build();            } else {                //没有网络的时候统一设置读取缓存                return originalResponse.newBuilder()                        .removeHeader("Pragma")                        // //only-if-cached只查询缓存而不会请求服务器                        .header("Cache-Control", "public, only-if-cached, max-stale=" + CACHE_STALE_LONG)                        .build();            }        }    };    //静态内部类单例模式    private static class SingleHolder {        private static final OkHttpClientUtil mInstance = new OkHttpClientUtil();    }    public static OkHttpClientUtil getInstance() {            return SingleHolder.mInstance;    }}
0 0
原创粉丝点击