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
分配。Cache
和DiskLruCache
。
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-length
和Range
。
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();
}