一、概述
在Android开发中,一般很少直接使用HttpURLConnection / HttpsURLConnection,比较常用的开源网络库有okhttp、retrofit等。
二、okhttp简单用法
okhttp支持异步网络请求/同步网络请求,它默认支持:(1)HTTP/2 相同域名下的请求共用一个socket;(2)通过连接池减少请求时延(如果HTTP/2不可用);(3)支持GZIP压缩;(4)支持Response缓存。
2.1 添加依赖
在build.gradle中添加okhttp依赖如下:
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.9.3")
}
2.2 接收数据
OKhttp使用Call来表示一次网络请求,请求建立后通过Response读取返回的内容。okhttp支持异步请求和同步请求:
(1)异步方式不会阻塞线程,在网络结果返回后会在其他线程通过callback返回;
(2)同步方式会阻塞当前线程直到结果返回后再继续执行;不能在主线程调用。使用方式如下:
// 1.单例模式,构建的okHttpClient被所有请求复用
object HttpManager {
val okHttpClient = OkHttpClient.Builder()
.connectionPool(ConnectionPool(2, 35, TimeUnit.SECONDS))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
}
fun sendRequest() {
// 2.构建request
val request = Request.Builder()
.url("https://www.baidu.com").post(requestBody).addHeader("COOKIE", "uid=bc").build()
// 3.异步发送请求
HttpManager.okHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
// logcat输出response的内容
// ps:如果response内容不是字符串,则用response.body().inputStream()读取
Log.d("BCHttpDemo", response.body?.string() ?: "")
}
})
// 4.同步发送请求;不能在主线程发送,否则会抛NetworkOnMainThreadException异常
okHttpClient.newCall(request).execute()
}
运行后,可以看到logcat输出了百度的HTML内容如下所示:
com.bc.example D/BCHttpDemo: <!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
2.3 发送数据
在构建Request时,通过Request.Builder().url().post(requestBody)设置需要用POST方式发送的数据(支持传送多种格式,例如json、form、ProtoBuf等),如下所示:
// 1.构建okHttpClient,建议单例模式
val okHttpClient = OkHttpClient.Builder()
.connectionPool(ConnectionPool(2, 35, TimeUnit.SECONDS))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
// 2.构造需要发送的JSON数据
val jsonObject = JSONObject()
jsonObject.put("name", "BC")
val jsonMediaType = "application/json; charset=utf-8".toMediaTypeOrNull()
// 设置RequestBody的数据格式为json类型
val requestBody = RequestBody.create(jsonMediaType, jsonObject.toString())
// 3.构建request
val request = Request.Builder()
.url("https://www.baidu.com").post(requestBody).addHeader("COOKIE", "cookie").build()
// 4.异步发送请求
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
Log.d("BCHttpDemo", response.body?.string() ?: "")
}
})
三、Retrofit简单用法
Retrofit网络库也是一个比较常用的网络库,它底层基于okhttp。Retrofit通过在接口上添加注解来动态生成网络请求,并且支持多种ConverterFactory(例如GsonConverterFactory)、CallAdapterFactory(例如RxJavaCallAdapterFactory)将输入/输出转换为指定的格式。
3.1 基础用法
3.1.1 添加依赖
在build.gradle中添加Retrofit依赖如下:
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
}
3.1.2 定义网络请求接口
public interface SampleRequestInterface {
@HTTP(method = "GET", path = "/api", hasBody = true)
Call<BaseResponse> getCall();
}
3.1.3 Retrofit发送请求
private void sendRequest() {
try {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.baidu.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 通过代理实现
SampleRequestInterface sampleRequest = retrofit.create(SampleRequestInterface.class);
Call<BaseResponse> call = sampleRequest.getCall();
// 1.异步发送请求
call.enqueue(new Callback<BaseResponse>() {
@Override
public void onResponse(Call call, Response response) {
response.body();
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
// 2.同步发送请求
Response<BaseResponse> response = call.execute();
response.body();
} catch (Exception exception) {
exception.printStackTrace();
}
}
3.2 Retrofit动态代理
在上述retrofit.create创建请求的源码实现中,Retrofit采用了动态代理的模式(代理模式参考:juejin.cn/post/696842… ):
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
关于Retrofit的的使用只做简单介绍,后面主要结合网络理论知识分析okhttp源码。
四、OkHttp源码分析
OkHttp内部使用了线程池、连接池、建造者模式、工厂模式、责任链模式等技术,支持连接复用、GZIP压缩、Response缓存等特性。根据OkHttp的简单使用例子:
// 1.单例模式,构建的okHttpClient被所有请求复用
object HttpManager {
val okHttpClient = OkHttpClient.Builder()
.connectionPool(ConnectionPool(2, 35, TimeUnit.SECONDS))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
}
fun sendRequest() {
// 2.构建request
val request = Request.Builder()
.url("https://www.baidu.com").post(requestBody).build()
// 3.异步发送请求
HttpManager.okHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) { }
override fun onResponse(call: Call, response: Response) { }
})
}
其中涉及到的主要类是OkHttpClient、Request、Call、Dispatcher。OkHttpClient是创建Call的工厂;Request表示一次HTTP请求发送;Call表示一次完整的网络请求,用来发送Request并读取Response;Dispatcher内部管理着线程池,是OkHttpClient用来分发Call的分发器。
4.1 OkHttpClient
OkHttpClient本身使用了建造者模式OkHttpClient.Builder()来构造OkHttpClient对象。在构造出OkHttpClient对象后,OkHttpClient采用工厂模式来创建Call对象。
OkHttpClient对象持有线程池、分发器Dispatcher、拦截器list,设置了网络请求的超时时间等。官方建议创建出来的OkHttpClient对象采用单例模式,之后的网络请求Call都复用这个OkHttpClient对象,减少网络延迟和资源消耗。
/**
* Call的工厂类
* ## OkHttpClients Should Be Shared
* OkHttp performs best when you create a single `OkHttpClient` instance and reuse it for all of
* your HTTP calls. This is because each client holds its own connection pool and thread pools.
* Reusing connections and threads reduces latency and saves memory. Conversely, creating a client
* for each request wastes resources on idle pools.
*/
open class OkHttpClient internal constructor(builder: Builder) : Cloneable,
Call.Factory, WebSocket.Factory {
/** 异步请求分发器,内部通过线程池分发异步请求 */
val dispatcher: Dispatcher = builder.dispatcher
/** 连接池 */
val connectionPool: ConnectionPool = builder.connectionPool
/** interceptors拦截器集合 */
val interceptors: List<Interceptor> = builder.interceptors.toImmutableList()
/** networkInterceptors拦截器集合 */
val networkInterceptors: List<Interceptor> = builder.networkInterceptors.toImmutableList()
/** 事件监听器工厂;可以监听callStart、dnsStart、dnsEnd、connectStart、
* connectEnd、requestHeadersStart、requestBodyStart、responseHeadersStart、
* responseBodyStart、callEnd、cacheHit等事件 */
val eventListenerFactory: EventListener.Factory = builder.eventListenerFactory
/** 缓存 */
val cache: Cache? = builder.cache
/** DNS */
val dns: Dns = builder.dns
/** socket工厂 */
val socketFactory: SocketFactory = builder.socketFactory
/** ssl socket工厂 */
val sslSocketFactory: SSLSocketFactory
/** 协议集合 */
val protocols: List<Protocol> = builder.protocols
/** 连接超时时间Default(单位ms),默认10s */
val connectTimeoutMillis: Int = builder.connectTimeout
/** 读超时时间Default(单位ms),默认10s */
val readTimeoutMillis: Int = builder.readTimeout
/** 写超时时间Default(单位ms),默认10s */
val writeTimeoutMillis: Int = builder.writeTimeout
constructor() : this(Builder())
/** 创建Call对象 */
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
open fun newBuilder(): Builder = Builder(this)
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
internal var connectionPool: ConnectionPool = ConnectionPool()
internal val interceptors: MutableList<Interceptor> = mutableListOf()
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
internal var cache: Cache? = null
internal var dns: Dns = Dns.SYSTEM
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
internal var connectTimeout = 10_000
internal var readTimeout = 10_000
internal var writeTimeout = 10_000
fun build(): OkHttpClient = OkHttpClient(this)
}
}
4.2 Request
Request表示一次HTTP请求发送,其内部也采用了建造者模式,默认采用GET方法,并支持HEAD、POST、DELETE、PUT、PATCH方法。Request包含了发送请求的url、method、headers、body等信息,如下所示:
class Request internal constructor(
val url: HttpUrl, val method: String,
val headers: Headers, val body: RequestBody?,
internal val tags: Map<Class<*>, Any>
) {
fun newBuilder(): Builder = Builder(this)
open class Builder {
internal var url: HttpUrl? = null
internal var method: String
internal var headers: Headers.Builder
internal var body: RequestBody? = null
constructor() {
this.method = "GET"
this.headers = Headers.Builder()
}
open fun url(url: String): Builder {
return url(url.toHttpUrl())
}
/**
* 添加header
* OkHttp会自动在header中添加`Content-Length` and `Content-Encoding`
*/
open fun addHeader(name: String, value: String) = apply {
headers.add(name, value)
}
open fun get() = method("GET", null)
open fun head() = method("HEAD", null)
open fun post(body: RequestBody) = method("POST", body)
open fun delete(body: RequestBodyT) = method("DELETE", body)
open fun put(body: RequestBody) = method("PUT", body)
open fun patch(body: RequestBody) = method("PATCH", body)
open fun build(): okhttp3.Request {
return Request(url, method, headers.build(), body, tags.toImmutableMap())
}
}
}
4.3 Dispatcher
Call表示一次完整的网络请求,用来发送Request并读取Response。例子中在创建完OkHttpClient、Request、Call后,realCall.enqueue()
会由OkHttpClient的Dispatcher进行分发,如下所示:
class RealCall(
val client: OkHttpClient, val originalRequest: Request, val forWebSocket: Boolean
) : Call {
override fun enqueue(responseCallback: Callback) {
// 由OkHttpClient的Dispatcher进行分发。
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
}
Dispatcher内部持有线程池,下面主要分析Dispatcher是如何分发异步请求的,可以看到在Call进入队列enqueue()后,会先查找同域名下是否有其他异步请求,然后将准备状态的请求提升为运行态,然后在线程池上运行:
/**
* Dispatcher内部通过线程池分发网络请求Call
*/
class Dispatcher {
/** 并发的请求数 */
var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
// 在修改并发数后,会检查一下是否有可以运行的请求
promoteAndExecute()
}
/**
* 每个域名下的并发请求数
* 注意不是对单个IP地址的并发请求数,因为一个域名只能对应一个IP地址,而一个IP却可以对应多个域名
*/
var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
// 在修改并发数后,会检查一下是否有可以运行的请求
promoteAndExecute()
}
/** 线程池,用来调度异步请求 */
val executorService = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false)
)
/** 准备状态的异步请求AsyncCall list */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** 运行状态的异步请求AsyncCall list */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** 运行状态的同步请求calls list */
private val runningSyncCalls = ArrayDeque<RealCall>()
/**
* 异步请求AsyncCall进入队列
*/
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
// 1.查找同域名下是否有其他异步请求,并修改该域名下异步请求的并发数
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
// 2.将准备状态的异步请求中符合条件的请求calls提升为运行状态,并在线程池上运行它们
promoteAndExecute()
}
/**
* 查找是否有同域名下的异步请求,有则返回该异步请求AsyncCall
*/
private fun findExistingCallWithHost(host: String): AsyncCall? {
for (existingCall in runningAsyncCalls) {
if (existingCall.host == host) return existingCall
}
for (existingCall in readyAsyncCalls) {
if (existingCall.host == host) return existingCall
}
return null
}
/**
* 将准备状态的异步请求readyAsyncCalls中符合条件的请求提升为运行状态的异步请求runningAsyncCalls中,并在线程池上运行它们
*/
private fun promoteAndExecute(): Boolean {
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // 是否超过最大请求限制
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 是否超过单个域名的最大请求限制
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
// 3.遍历所有符合条件的异步请求AsyncCall并在线程池上运行
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
}
4.4 Call
Call表示一次完整的网络请求,用来发送Request并读取Response。RealCall是Call的实现类,AsyncCall是RealCall的内部类。在OkHttpClient的Dispatcher调度执行异步AsyncCall后,会执行AsyncCall的run()方法:
/**
* okhttp业务层和网络层的桥接层
* 对建立连接、发送请求、读取响应、IO流进行了封装
*/
class RealCall(val client: OkHttpClient,
/** 原始请求 */
val originalRequest: Request, val forWebSocket: Boolean) : Call {
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
@Volatile
var callsPerHost = AtomicInteger(0)
val host: String
val request: Request = originalRequest
val call: RealCall = this@RealCall
override fun run() {
// 启动计时器
timeout.enter()
// 关键代码,通过责任链并最终返回Response
val response = getResponseWithInterceptorChain()
// 返回Response
responseCallback.onResponse(this@RealCall, response)
}
}
/**
* 责任链模式
*/
internal fun getResponseWithInterceptorChain(): Response {
// 拦截器list
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
val response = chain.proceed(originalRequest)
return response
}
}
执行建立连接、发送Request、读取Response等工作都是在RealCall责任链模式中的各拦截器Interceptor实现的,下一章再继续分析okhttp责任链。
The End
欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
OkHttp官方文档:square.github.io/okhttp/
OkHttp github:github.com/square/okht…