Android okhttp详解

一:okhttp是什么

       大家都知道HTTP是现代应用网络请求的方式,是一个简单的请求-响应协议。在数据和媒体中进行交换。有效地请求使您的东西加载更快,并节省带宽。OKHttp是安卓开发中十分常用的网络请求框架。

二:为什么使用

      1:缓存响应数据来减少重复的网络请求

       2:可以从很多常用的连接问题中自动恢复;(Okhttp处理了很多的网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服        务器配置了多个IP地址,当第一个IP连接失败时,OkHttp会尝试连接下一个IP。)

       3:支持gzip压缩响应体

       4:使用简单,可扩展性非常的强;(责任链模式使得很容易添加一个自定义拦截器对请求和返回结果进行处理)

三:用来干什么

      1:支持一般的get请求,post请求

       2:基于Http的文件上传,文件下载,上传下载的进度回调

       3:图片加载

       4:支持请求回调,对象返回

       5:   表单请求

四:请求流程

       1:生成一个OkHttpClient(用以总的控制)

       2:用各种键值对包装我们的Request

       3:将请求加入队列生成一个Call管理请求

       4:若是同步请求直接执行excute等待处理返回Response,若为异步则实现Callback回调,在onResponse里获取Response参数

请求流程

五:同步与异步的实现

在发起请求时,整个框架主要通过Call来封装每一次的请求。同时Call持有OkHttpClient和一份HttpEngine。而每一次的同步或者异步请求都会有Dispatcher的参与。

Dispatcher

Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了 SynchronousQueue这种阻塞队列。SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用peek操作,因为只有移除元素时才有元素。显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中是最快处理任务的方式。对于高频繁请求的场景,无疑是最适合的。

同步

Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response;

同步请求

异步

异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前 的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的 getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。

异步请求

六:拦截器

拦截器

拦截器可以注册为应用拦截器和网络拦截器。假如有一个压缩拦截器和一个检验和拦截器:你需要决定是先数据进行压缩然后检验和,还是先检验和然后进行压缩。OkHttp使用列表来跟踪拦截器,并且拦截器按顺序被调用。

应用拦截器

应用拦截器

1:不需要关心像重定向和重试这样的中间响应。

2:总是调用一次,即使HTTP响应从缓存中获取服务。

3:监视应用原始意图。不关心OkHttp注入的像If-None-Match头。

4:允许短路并不调用Chain.proceed()。

5:允许重试并执行多个Chain.proceed()调用。

网络拦截器

网络拦截器

1:可以操作像重定向和重试这样的中间响应。

2:对于短路网络的缓存响应不会调用。

3:监视即将要通过网络传输的数据。

4:访问运输请求的Connection。

七:线程池技术

相比我们对于异步任务的需求应该遇到了不少,首先想到的便是Thread,handler等异步机制,Java已经做了很好的封装,但是当我们需要使用许多异步任务,而这些任务只做了一点点事情就完成了它的使命,当我们不断的创建,销毁线程的时候,对系统的开销是相当大的,因为这些过程也是耗费时间的,这个时候我们就需要用到线程池了,我们只要往池子里放任务,他就会自动帮你管理线程的创建与销毁,利用缓存复用减少线程销毁创建,优化系统性能。以下是线程池的优点:

1:通过对线程进行缓存,减少了创建销毁的时间损失

2:通过控制线程数量阀值,减少了当线程过少时带来的CPU闲置(比如说长时间卡在I\O上了)与线程过多时对JVM的内存与线程切换压力而Disatcher内部的核心即线程池

Dispatch线程池

int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁。

int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理

long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive

TimeUnit unit: 时间单位,一般用秒

BlockingQueue workQueue: 工作队列

ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等

OkHttp内Dispatcher原理其实当我们调用client.newCall(request).enqueue(new callback(){...})的时候实质是下图


反向代理模式

为了解决单生产者多消费者问题,OkHttp采用了反向代理模式,来解决非阻塞,高并发问题

工作模式

Dispatch模式

OkHttp内Dispatcher原理其实当我们调用client.newCall(request).enqueue(new callback(){...})的时候实质是判断线程池内有无空闲,否则进入等待队列,AsyncCall实质是Runnable,当任务执行完毕后,总会调用finally{client.dispatcher().finished(this);}清除此任务。我们回头看看OkhttpClient初始化内其它一些参数Expires,Cache-Control,Last-Modified,If-Modified-Since,ETag,If-None-Match...这些都牵扯到缓存问题。

Response response = getResponseWithInterceptorChain();

其实这个方法是返回我们的response对象。到这里整个流程已经完毕。

使用OkHttpClient 的时候需要注意以下几点:

1:最好只使用一个共享的OkHttpClient 实例,将所有的网络请求都通过这个实例处理。因为每个OkHttpClient 实例都有自己的连接池和线程池,重用这个实例能降低延时,减少内存消耗,而重复创建新实例则会浪费资源。

2:OkHttpClient的线程池和连接池在空闲的时候会自动释放,所以一般情况下不需要手动关闭,但是如果出现极端内存不足的情况,可以使用以下代码释放内存:

      client.dispatcher().executorService().shutdown();   //清除并关闭线程池

      client.connectionPool().evictAll();                 //清除并关闭连接池

      client.cache().close();       

3:如果对一些请求需要特殊定制,可以使用

OkHttpClient eagerClient = client.newBuilder()

    .readTimeout(500, TimeUnit.MILLISECONDS)

    .build();

这样创建的实例与原实例共享线程池、连接池和其他设置项,只需进行少量配置就可以实现特殊需求。

Request

Request是网络请求的对象,其本身的构造函数是private的,只能通过Request.Builder来构建。下面代码展示了常用的设置项。

Request request = new Request.Builder()

        .url("https://api.github.com/repos/square/okhttp/issues")                         //设置访问url

        .get()  //类似的有post、delete、patch、head、put等方法,对应不同的网络请求方法

        .header("User-Agent", "OkHttp Headers.java")                                       //设置header

        .addHeader("Accept", "application/json; q=0.5")                                     //添加header

        .removeHeader("User-Agent")                                                                //移除header

        .headers(new Headers.Builder().add("User-Agent", "OkHttp Headers.java").build())   

         //移除原有所有header,并设置新header

        .addHeader("Accept", "application/vnd.github.v3+json")

        .build();                                                                                                   //构建request

RequestBody

在Request中使用post、patch等方法时,需要传入一个RequestBody参数,除了上一节讲到的构造方法外,RequestBody还有两个子类:FormBody和MultipartBody。

FromBody用于提交表单键值对,其作用类似于HTML中的

标记。

RequestBody formBody = new FormBody.Builder()                             //提交表单键值对

        .add("platform", "android")                                                            //添加键值对

        .add("name", "XXX")

        .add("subject", "Hello")

        .addEncoded(URLEncoder.encode("详细","GBK"), URLEncoder.encode("无","GBK"))

        //添加已编码的键值对

        .add("描述","你好")          //其实会自动编码,但是无法控制编码格式

        .build();

使用MultipartBody.Builder可以构建与HTML文件上传格式兼容的复杂请求体。多块请求体中每块请求都是一个独立的请求体,都可以定义自己的请求头。这些请求头应该用于描述对应的请求体,例如Content-Disposition,Content-Length,和Content-Type会自动被添加到请求头中。

Call

Call对象表示一个已经准备好可以执行的请求,用这个对象可以查询请求的执行状态,或者取消当前请求。它具有以下方法:

Call call=client.newCall(request);                //获取Call对象

Response response=call.execute();                 //同步执行网络请求,不要在主线程执行

call.enqueue(new Callback());                     //异步执行网络请求

call.cancel();                                    //取消请求

call.isCanceled();                                //查询是否取消

call.isExecuted();                                //查询是否被执行过

要注意的是,每个Call对象只能执行一次请求。如果想重复执行相同的请求,可以:

Call reCall=client.newCall(call.request());       //获取另一个相同配置的Call对象

Response

Response是网络请求的结果下面是一些常用方法:

Response response=call.execute();                //获取Response对象

response.code();                                 //请求的状态码

response.isSuccessful();                         //如果状态码为[200..300),则表明请求成功

Headers headers=response.headers();              //获取响应头

List names=response.headers("name");     //获取响应头中的某个字段

ResponseBody body=response.body();               //获取响应体

其中ResponseBody代表响应体,用于操作网络请求返回的内容。常用方法如下:

body.contentLength();                           //body的长度

String content=body.string();                   //以字符串形式解码bodybyte[] byteContent=body.bytes();                //以字节数组形式解码body

InputStreamReader reader=body.charStream();     //将body以字符流的形式解码

InputStream inputStream=body.byteStream();      //将body以字节流的形式解码

ResponseBody

1:ResponseBody必须关闭,不然可能造成资源泄漏,你可以通过以下方法关闭ResponseBody,对同一个ResponseBody只要关闭一次就可以了。

Response.close();

Response.body().close();

Response.body().source().close();

Response.body().charStream().close();

Response.body().byteString().close();

Response.body().bytes();

Response.body().string();

2:ResponseBody只能被消费一次,也就是string(),bytes(),byteStream()或 charStream()方法只能调用其中一个。

3:如果ResponseBody中的数据很大,则不应该使用bytes() 或 string()方法,它们会将结果一次性读入内存,而应该使用byteStream()或 charStream(),以流的方式读取数据。

八:OkHttp中的设计模式

1:责任链模式:拦截器链

2:单例模式:线程池

3:观察者模式:各种回调监听

4:策略模式:缓存策略

5:Builder模式:OkHttpClient的构建过程

你可能感兴趣的:(Android okhttp详解)