OkHttp基本使用及网络封装


1、网络基础

1.2、Cookie 、Session和Token

Cookie 、Session、Token:都是用来做持久化处理的,目的就是让客户端和服务端相互认识。Http请求默认是不持久的没有状态的,谁也不认识谁。

Cookie: 是存放在客户端的信息,这个信息是来自于服务器返回的信息,下次请求带过去,如果用户离开网站后,如果Cookie已过期一般是会被清除的。如果Cookie没过期下次访问网站还是会带过去。(相对危险)

Session: 是存放在服务器上面的客户端临时信息,用户离开网站是会被清除的。(相对安全,耗资源)

Token(App)”令牌”:用户身份的验证,有点类似于Cookie,相对来说更安全。

1.3、Http缓存

Cache-Control(缓存策略):Public、private、no-cache、max-age 、no-store(不缓存)

Expires(缓存的过期策略):指名了缓存数据有效的绝对时间,告诉客户端到了这个时间点(比照客户端时间点)后本地缓存就作废了,在这个时间点内客户端可以认为缓存数据有效,可直接从缓存中加载展示。

如果有缓存并且过期了那么发起请求,服务端会给我们数据?(不一定会给)服务器的数据没有变动就不会给,状态码会变为304,自己拿之前过期的缓存。

1.5、Https

Http和Https的区别:

Https = Http + 加密 + 验证身份 + 完整
端口:Http (80)  Https (443)

Http的缺点:数据是没有加密传输,可能遭遇窃听;不验证通信方的身份,可能会遭遇伪装;无法验证报文的完整性,可能会遭遇篡改。

TLS/SSL协议:加密:对称加密(AES,DES)+ 非对称加密 (RSA,DSA);证书:要钱(便宜),建立连接的速度会拖慢,TCP由3次握手变为8次握手。

Http/1.1和Http/2.0的区别:

Http/2.0采用二进制格式而非文本格式;Http/2.0支持完全的多路复用;Http/2.0使用报头压缩,降低开销Http/2.0让服务器将响应主动推送给客户端,(带内容推送,不带内容推送的通知)。

2、OkHttp

异步和同步:跟线程没什么关系,打电话,同步:打电话 -> 处理(没挂断) -> 反馈;异步:打电话 -> 处理(挂断)-> 打回来

网络框架要怎么处理:网络是耗时的,因此需要开线程,用线程池;处理网络,HttpUrlConnection(简单) 或者输入流+Socket(麻烦);网络请求头信息处理,缓存的处理,文件格式上传的方式(表单提交,拼格式);路由的一些操作,Http/2.0复用等等。

OkHttp要点——okio:原生的JavaIO+自定义封装,其实就是对于io的封装;Socket:连接;拦截器。

OkHttp流程:

1、Request里面封装了url,method,header等基本信息,然后通过okhttpClient.newCall(request),将Request转化成了RealCall。

2、RealCall里面有enqueue,通过call.enqueue(),转换成了AsyncCall。

3、AsyncCall是RealCall的内部类,AsyncCall继承了Runnable。AsyncCall给了OKhttp的Dispatcher(线程池),executorService().execute(call);

4、最终去执行了AsyncCall.execute()方法,执行getResponseWithInterceptorChain返回 Response。

synchronized void enqueue(AsyncCall call) {  
    //判断当前正在执行的任务数量,最大是64,正在执行的任务中的host,最大是5   
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        //加入到正在执行      
        runningAsyncCalls.add(call);
        // 线程池,     
        executorService().execute(call);    
    } else {
        // 加入准备执行的集合,等待执行      
        readyAsyncCalls.add(call);    
    }  
}

2.1、官网和导入

官网

http://square.github.io/okhttp/

引入

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

2.2、测试地址

http://httpbin.org/
http://httpbin.org/get?id=123
http://httpbin.org/post

2.3、get请求

同步请求

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://httpbin.org/get?id=123").build();
try {
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}

异步请求

System.out.println(Thread.currentThread().getId());
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://httpbin.org/get?id=123").build();
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            System.out.println(Thread.currentThread().getId());
        }
    }
});

2.4、post请求

同步请求,提交表单

OkHttpClient client = new OkHttpClient();
FormBody body = new FormBody
        .Builder()
        .add("name", "shuaige")
        .add("age", "19")
        .build();
Request request = new Request.Builder().url("http://httpbin.org/post").post(body).build();
try {
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}

异步提交,提交json

OkHttpClient okHttpClient  = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10,TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .build();
 
Book book = new Book();
book.setName("android");
//使用Gson
Gson gson = new Gson();
//使用Gson将对象转换为json字符串
String json = gson.toJson(book);
//MediaType设置Content-Type 标头中包含的媒体类型值
RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json);
Request request = new Request.Builder()
        .url("http://172.20.192.168:8080/getbookByJson")//请求的url
        .post(requestBody)
        .build();
//创建Call
Call call = okHttpClient.newCall(request);
//加入队列 异步操作
call.enqueue(new Callback() {
    //请求错误回调方法
    @Override
    public void onFailure(Call call, IOException e) {
        System.out.println("连接失败");
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }

2.5、设置url参数-HttpUrl

OkHttpClient client = new OkHttpClient();
HttpUrl httpUrl = HttpUrl.parse("http://httpbin.org/get").
        newBuilder().
        addQueryParameter("city", "beijing").
        addQueryParameter("id", "123").
        build();
String url = httpUrl.toString();
System.out.println(httpUrl.toString());// http://httpbin.org/get?city=beijing&key=123
Request request = new Request.Builder().url(url).build();
try {
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}

2.6、请求头的设置-addHeader

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().
        url("http://httpbin.org/get?id=123").
        addHeader("User-Agent", "this my head").
        addHeader("Accept", "text/plain, text/html").
        build();
try {
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}

2.7、上传文件

RequestBody imageBody = RequestBody.create(MediaType.parse("image/jpeg"), new File("文件路径+文件名"));
MultipartBody body = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("name", "name")
        .addFormDataPart("filename", "文件名", imageBody).build();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://192.168.1.6:8080/web/UploadServlet").post(body).build();
try {
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}

boundary

2.8、定义拦截器

//定义拦截器
Interceptor interceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        long start = System.currentTimeMillis();
        Request request  = chain.request();
        Response response = chain.proceed(request);
        long end = System.currentTimeMillis();
        System.out.println("interceptor: cost time = " + (end - start));
        return response;
    }
};
// 创建 OkHttpClient 对象
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(interceptor)
        .build();

2.9、缓存

使用缓存的前提是服务器支持缓存

// 创建缓存对象,缓存文件和缓存大小
Cache cache = new Cache(new File("cache.cache"), 1024 * 1024);
// 创建 OkHttpClient 对象
OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .build();
// 创建 Request 对象
Request request = new Request.Builder()
        .url("http://httpbin.org/get?id=id")
        .cacheControl(CacheControl.FORCE_NETWORK)//只从网络读取
        //.cacheControl(CacheControl.FORCE_CACHE)//只读取缓存
        //.cacheControl(new CacheControl.Builder().noCache().build())//永远不使用缓存
        //.cacheControl(new CacheControl.Builder().maxStale(365, TimeUnit.DAYS).build())//最大缓存时效365天
        .build();
// OkHttpClient 执行 Request
try {
    Response response = client.newCall(request).execute();
    Response responseCache = response.cacheResponse();
    Response responseNet = response.networkResponse();
    if (responseCache != null) {
        //从缓存响应
        System.out.println("response from cache");
    }
    if (responseNet != null) {
        //从网络响应
        System.out.println("response from net");
    }
    System.out.println("response:" + response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

Okhttp实现缓存方式:在CacheInterceptor缓存拦截器中,根据缓存策略CacheStrategy分配。CacheDiskLruCache

2.10、文件下载

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("文件地址").
        addHeader("Accept-Encoding","identity").
        addHeader("Range", "bytes=0-").
        build();
try {
    Response response = client.newCall(request).execute();
    System.out.println("content-length : "+response.body().contentLength());
    if (response.isSuccessful()) {
        Headers headers = response.headers();
        for (int i = 0; i < headers.size(); i++) {
            System.out.println(headers.name(i) + " : " + headers.value(i));
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

content-lengthRange

3、源码分析

Volley是基于HttpUrlConnection;OkHttp基于Okio(原生的JavaIO + 自定义封装) + Socket连接。

3.1、OkHttpClient


OkHttpClient相当于配置中心,所有的请求都会共享这些配置,比如出错是否重试、共享的连接池。主要配置有:

//调度器,主要管理线程,用于调度后台发起的网络请求,有后台总请求数和单主机总请求数的控制。
//private int maxRequests = 64;private int maxRequestsPerHost = 5;
final Dispatcher dispatcher;

//支持的应用层协议,即HTTP/1.1、HTTP/2等
final List<Protocol> protocols;

//应用层支持的Socket设置,即使用明文传输(HTTP)还是某个版本的TLS(用于HTTPS)。
final List<ConnectionSpec> connectionSpecs;

//自己定义的拦截器配置
final List<Interceptor> interceptors;

//自己定义的和网络请求交互的Interceptor配置。
final List<Interceptor> networkInterceptors;

//管理CooKie的控制器
final CookieJar cookieJar;

//Cache存储的配置。默认没有,如果要用,需要自己配置存储的文件位置和存储空间上线
final @Nullable Cache cache;

//验证HTTPS握手过程中下载到的证书所属者是否和自己要访问的主机名一致
final HostnameVerifier hostnameVerifier;

//一般用于防止网站证书被人仿制,开发者可以做自签名
final CertificatePinner certificatePinner;

//自动重新认证。配置后,如果401,会直接调用authenticator
final Authenticator authenticator;

//遇到重定向,是否自动follow在HTTP和HTTPS之间切换重定向
final boolean followSslRedirects;

//遇到重定向,是否自动follow
final boolean followRedirects;

//请求失败是否重试。重试只适用于同一个域名的多个IP切换重试、Socket失效重试等
final boolean retryOnConnectionFailure;

//建立连接的超时时间
final int connectTimeout;

//发起请求到读到响应数据的超时时间
final int readTimeout;

//发起请求并被目标服务器接受的超时时间
final int writeTimeout;

CertificatePinner的配置公钥示例:

 String hostname = "publicobject.com";
 CertificatePinner certificatePinner = new CertificatePinner.Builder()
 .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
 .build();
 OkHttpClient client = OkHttpClient.Builder()
 .certificatePinner(certificatePinner)
 .build();
 Request request = new Request.Builder()
 .url("https://" + hostname)
 .build();
 client.newCall(request).execute();

3.2、RealCall


newCall(Request)方法会返回一个RealCall对象,它是Call接口的实现。

final class RealCall implements Call {

当调用RealCall.execute()的时候,getResponseWithInterceptorChain()会被调用,它会发起网络请求并拿到返回的响应,装进一个Response对象并作为返回值返回。

Response result = getResponseWithInterceptorChain();

RealCall.enqueue()被调用的时候大同小异,区别在于enqueue会使用Dispatcher的线程池来把请求放在后台线程进行,但实质上使用的同样也是getResponseWithInterceptorChain()方法。

getResponseWithInterceptorChain()方法里做的事:把所有配置好的Interceptor放在一个List里,然后作为参数,创建一个RealInterceptorChain对象,并调用chain.proceed(request)发起请求和获取响应。

RealCall类中的getResponseWithInterceptorChain()方法作用:将请求Request转变成响应Response

Response getResponseWithInterceptorChain() throws IOException {
    // 拦截器的一个集合
    List<Interceptor> interceptors = new ArrayList<>();
    // 客户端的所有自定义拦截器,可以添加自己定义的
    interceptors.addAll(client.interceptors());
    // OKhttp 5 个拦截器 ,责任链设计模式,每一个拦截器只处理与他相关的部分
    interceptors.add(retryAndFollowUpInterceptor);// 重试
    interceptors.add(new BridgeInterceptor(client.cookieJar()));// 基础
    interceptors.add(new CacheInterceptor(client.internalCache()));// 缓存
    interceptors.add(new ConnectInterceptor(client));// 建立连接,连接服务器
    interceptors.add(new CallServerInterceptor(forWebSocket));// 写数据
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
}

3.3、拦截器

在RealInterceptorChain中,多个Interceptor会依次调用自己的intercepter方法,方法会做三件事情

1、对请求进行预处理

2、预处理之后,重新调用RealInterceptorChain.proceed()把请求交给下一个Interceptor。

3、在下一个Interceptor处理完成并返回之后,拿到Response进行后续处理

从上到下,每级Interceptor:

1、首先是开发者自己设置的addInterceptor(Interceptor),在所有其他Interceptor处理之前,进行最早的预处理工作,以及收到Response之后,做最后的善后工作。如果有统一的header要添加,可以在这里设置。

2、RetryAndFollowUpInterceptor:

处理重试的一个拦截器,会去处理一些异常,只要不是致命的异常就会重新发起一次请求(把Request给下级),如果是致命的异常就会抛给上一级;
会处理一些重定向等等,比如 3XX 307、407 就会从头部中获取新的路径,生成一个新的请求交给下一级(重新发起一次请求)。

3、BridgeInterceptor

做一个简单的处理,设置一些通用的请求头,Content-Type、Connection、Content-Length的计算和添加、Cookie。做一些返回的处理,如果返回的数据被压缩了采用ZipSource, 保存 Cookie。

4、CacheInterceptor

在缓存可用的情况下,直接读取本地的缓存的数据,如果本地的缓存没有直接去服务器,如果本地的缓存有,首先判断有没有缓存策略,然后判断有没有过期,如果没有过期直接拿缓存,如果过期了需要添加一些之前头部信息如If-Modified-Since,这个时候后台有可能会返回304代表还是可以拿本地缓存,每次读取到新的响应后做一次缓存。

5、ConnectInterceptor

findHealthyConnection()找一个连接,首先判断有没有健康的,没有就创建(建立Scoket,握手连接),连接缓存得到一条结论:OkHttp是基于原生的Socket + okio(原生IO的封装)。封装HttpCodec里面封装了okio的Source(输入)和Sink(输出),我们通过HttpCodec就可以操作 Socket的输入输出,我们就可以像服务器写数据和读取返回数据。

TCP连接(如果是HTTP)或者是建立在TCP连接之上的TLS连接(如果是HTTPS),并且会创建出对应的HttpCodec对象(用于编码解码HTTP请求)。

6、addNetworkInterceptor:开发者自己设置的,这里设置的Interceptor会看到每个请求和响应的数据(包括重定向以及重试的一些中间请求和响应),并且看到的是完整原始数据,而不死没加Content-Length的请求数据,或者Body还没有被gzip解压的响应数据。

7、CallServerInterceptor

它负责实质的请求和响应的I/O操作,即往Socket里写入请求数据和从Socket里读取响应数据。Socket就是TCP的端口。

连接三个核心类(连接复用)

RealConnection: 建立连接的一个对象的封装

ConnectionPool:保存了连接

StreamAllocation: 找一些连接,做一下封装

4、Okio

程序内部(内存)和外部(本地文件和网络)进行数据交互的过程,就叫输入输出。

从文件里或者从网络上读数据到内存里,就叫输入;从内存里写到文件里或者发送到网络上,就叫输出。Java I/O 作用只有一个:和外界做数据交互。使用流,例如 FileInputStream / FileOutputStream

Okio特点:基于插管的,而且是单向的,输入源叫 Source,输出目标叫 Sink,支持 Buffer,可以对 Buffer 进行操作,但不强制使用Buffer。

try (Source source = Okio.buffer(Okio.source(new File("./io/text.txt")))) {
    Buffer buffer = new Buffer();
    source.read(buffer, 1024);
    System.out.println(buffer.readUtf8Line());
    System.out.println(buffer.readUtf8Line());
} catch (IOException e) {
    e.printStackTrace();
}

文章作者:
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 !
  目录