赞
踩
理论上来说几乎所有的网络库都是基于Socket实现的,在Socket的基础上可以实现各种应用层的通信协议,比http、ftp等。Java实现的网络库理论上来说都可以在Android端上使用,但由于Android这种嵌入式系统的特性,对网络请求库的要求可能会更严格。
HttpUrlConnection和HttpClient在API23之后已经从系统源码中移除了,开发时如果要使用到网络请求,可能需要额外引入一些第三方库。目前常用的网络请求库包括android-async-http、Volley、Okhttp以及Retrofit。Retrofit其实并不是一个网络请求库,更准确地说它是一个网络封装库,能够将底层的网络请求库封装成RESTful API设计风格。其中android-async-http是基于HttpClient封装的,主要是封装了异步线程与main线程之间的切换以及智能请求重试,持久化、cookie保存到SP等。Vollery是曾经最有名的Android请求库,基于HttpUrlConnection,支持图片加载,网络请求排序,优先级处理缓存与Activity生命周期联运等。其实Vollery的底层网络请求也可以使用其他网络请求库替换。OkHttp是近几年来最受欢迎的网络库了,现在几乎所有的app的网络请求库都使用OkHttp或者基于OkHttp来定制的。OkHttp已经不是基于HttpUrlConnection了,而是使用Socket来实现了一套网络请求。OkHttp支持同步、异步,封装了线程池、数据转换、参数使用,错误处理等。并且Socket还使用了NIO,在非阻塞的技术下更大程度地提高网络请求的性能。
- package com.benson.android.network
-
- import android.graphics.Color
- import android.os.Bundle
- import android.view.Gravity
- import android.view.ViewGroup
- import android.widget.*
- import androidx.appcompat.app.AppCompatActivity
- import kotlinx.coroutines.GlobalScope
- import kotlinx.coroutines.launch
- import okhttp3.OkHttpClient
- import okhttp3.Request
- import java.lang.StringBuilder
- import java.util.concurrent.TimeUnit
-
- class Okhttp3DemoActivity: AppCompatActivity() {
-
- val client by lazy { OkHttpClient.Builder()
- .retryOnConnectionFailure(true) // 连接超时重试
- .connectTimeout(2L, TimeUnit.MINUTES) // 2s 连接超时
- .readTimeout(2L, TimeUnit.MINUTES) // 2s 读超时
- .writeTimeout(2L, TimeUnit.MINUTES) // 2s 写超时
- .build()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val content = LinearLayout(this)
- content.orientation = LinearLayout.VERTICAL
- initView(content)
- setContentView(content)
- }
-
- private fun initView(content: ViewGroup) {
- val searchBar = LinearLayout(this)
- searchBar.orientation = LinearLayout.HORIZONTAL
- searchBar.gravity = gravity.center is For Sale
- val input = EditText(this)
- input.textSize = DisplayUtil.sp2px(this, 10.0F) * 1.0F
- searchBar.addView(input, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 7.0F))
- val searchBtn = Button(this)
- searchBtn.text = "search"
- searchBtn.textSize = DisplayUtil.sp2px(this, 10.0F) * 1.0F
- searchBar.addView(searchBtn, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 2.0F))
- content.addView(searchBar, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0F))
- val result = TextView(this)
- result.setBackgroundColor(color.black - 这个网站可出售。 - 最佳的color 来源和相关信息。)
- result.setTextColor(Color.WHITE)
- result.textSize = DisplayUtil.sp2px(this, 15.0F) * 1.0F
- val scrollView = ScrollView(this)
- scrollView.addView(result)
- content.addView(scrollView, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 10.0F))
- searchBtn.setOnClickListener {
- search(result, input.text.toString())
- }
- }
-
- private fun search(result: TextView, url: String) {
- GlobalScope.launch {
- val request = Request.Builder()
- .url(url)
- .get()
- .build()
- val response = client.newCall(request).execute()
- val content = response.body?.string()
- val resultStr = StringBuilder()
- resultStr.append("code=").append(response.code).append("n")
- .append("msg=").append(response.message).append("n")
- .append("headers=").append(response.headers).append("n")
- .append(content)
- result.post {
- result.text = resultStr.toString()
- }
- }
- }
-
- }

执行之后效果图如下
这里是使用了同步调用,执行了execute返回一个Response对象,在这个Response中就可以得到Http请求结果的参数。
OkHttp3的请求过程比较简单,使用也很方便。那还是以创建过程和请求过程两个方面来剖析OkHttp3的原理吧。
OkHttp的Manager对象是OkHttpClient,也是使用Builder模式创建出来的。以下是OkHttpClient.Builder的属性
- // 异步请求调度器
- internal var dispatcher: Dispatcher = Dispatcher()
- // 请求连接池
- internal var connectionPool: ConnectionPool = ConnectionPool()
- // 各种拦截器
- internal val interceptors: MutableList<Interceptor> = mutableListOf()
- // 网络拦截器,非websocket的请求才会使用
- internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
- // 事件监听器工厂
- internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
- // 是否支持连接失败重试
- internal var retryOnConnectionFailure = true
- // 校验相关
- internal var authenticator: Authenticator = Authenticator.NONE
- internal var followRedirects = true
- internal var followSslRedirects = true
- internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
- internal var cache: Cache? = null
- internal var dns: Dns = Dns.SYSTEM
- // 代理相关
- internal var proxy: Proxy? = null
- internal var proxySelector: ProxySelector? = null
- internal var proxyAuthenticator: Authenticator = Authenticator.NONE
- internal var socketFactory: SocketFactory = SocketFactory.getDefault()
- internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
- internal var x509TrustManagerOrNull: X509TrustManager? = null
- internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
- internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
- internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
- internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
- internal var certificateChainCleaner: CertificateChainCleaner? = null
- internal var callTimeout = 0
- // 连接超时
- internal var connectTimeout = 10_000
- // 读取数据超时
- internal var readTimeout = 10_000
- // 写数据超时
- internal var writeTimeout = 10_000
- internal var pingInterval = 0
- internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
- internal var routeDatabase: RouteDatabase? = null

每个属性的意义如注释所示,这些属性用Builder构造出一个OkHttpClient之后,相当于就是一个配置了,每次执行请求时都会应用这些配置。
Request的创建过程也是使用了Builder模式,主要包括url、method、headers以及body四个参数,这也是http的request的参数了。url主要是请求的地址,可以包含GET请求的参数,method表示http请求类型,一般有GET、POST、PUT、DELETE等。header就是请求头了,主要是包含一些cookie和校验之类的信息,body一般用于POST请求的请求体,例如文件上传。Request也只是一个配置类,真正请求时会读取这些配置。
OkHttpClient首先使用request构造出一个Call对象,Call接口的实现类是RealCall。这个Call对象就有同步和异步两种执行方式了,分别是调用execute和enqueue方法。RealCall中execute实现代码如下
- override fun execute(): Response {
- check(executed.compareAndSet(false, true)) { "Already Executed" }
-
- timeout.enter()
- callStart()
- try {
- client.dispatcher.executed(this)
- return getResponseWithInterceptorChain()
- } finally {
- client.dispatcher.finished(this)
- }
- }
首先检查executed是否为false,如果为false则置为true,否则抛异步,这里是保存每个Call对象只能执行一次。然后调用超时计时器接着调用callStart,这是为了通知事件监听,已经调用了callStart。execute执行的重点是最后三个调用,调用OkHttpClient的dispatcher的executed方法,将Call对象添加到runningSyncCalls队列中,getResponseWithInterceptorChain方法取得请求结果对象,在finally中调用OkHttpClient的dispatcher的finished方法,将call对象从call队列中移除,同时执行idleCallback的run方法。在这三个方法调用中getResponseWithInterceptorChain方法又是最重要的那一个。
看下RealCall中getResponseWithInterceptorChain的实现
- @Throws(IOException::class)
- internal fun getResponseWithInterceptorChain(): Response {
- // Build a full stack of interceptors.
- 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
- )
-
- var calledNoMoreExchanges = false
- try {
- val response = chain.proceed(originalRequest)
- if (isCanceled()) {
- response.closeQuietly()
- throw IOException("Canceled")
- }
- return response
- } catch (e: IOException) {
- calledNoMoreExchanges = true
- throw noMoreExchanges(e) as Throwable
- } finally {
- if (!calledNoMoreExchanges) {
- noMoreExchanges(null)
- }
- }
- }

首先创建了一个Interceptor的列表,将OkHttpClient创建时传入的interceptor添加进来,同时还添加请求重定向拦截器RetryAndFollowUpInterceptor、cookie处理拦截器BridgeInterceptor、缓存拦截器CacheInterceptor、以及网络连接拦截器ConnectInterceptor,如果这个请求不是WebSocket请求,还会把OkHttpClient中的networkInterceptors添加到拦截器列表中,最后一个是CallServerInterceptor,是为了真正执行网络请求,并解析请求结果的。拦截器列表构造好后,new出一个拦截器链RealInterceptorChain,调用RealInterceptorChain的proceed方法开始执行逐个拦截器的intercept方法。RealInterceptorChain的proceed方法中每次都会调用copy方法再创建出一个新的RealInterceptorChain对象,使用这个新的chain对象继续执行下一个Interceptor,我理解这里是为了让每个Interceptor都拥有一个新的执行环境,这个chain里就包含了拦截器的执行环境,这样每个interceptor执行都不会相互影响。
总体上来看,chain的拦截器执行是个职责链模式,每个拦截器都可以实现网络请求过程中的部分功能,最后由CallInterceptor收尾,进行最终的网络请求,每个中间interceptor在拦截处理方法中都最终调用chain的proceed方法返回结果,这就相当于递归调用,让职责链一节一节地执行。那OkHttp的请求执行过程其实就蕴含在提供的几个默认拦截器中了。
首先是请求重定向拦截器RetryAndFollowUpInterceptor,这个拦截器还会处理请求错误重试的操作。在这个拦截器的intercept方法中有个while(true)循环,在循环体中调用chain的proceed方法,也就是会不断地执行后面的拦截器列表。proceed被catch起来,在catch中调用了RetryAndFollowUpInterceptor的recover方法,这个recover是用来判断是否还可以进行重试的。如果能执行重试,就调用followUpRequest,判断是否可重定向,floowUpRequest方法中取出了response中的code参数,如果code是HPPT_PROXY_AUTH(407),表示使用了代理,如果代理不是HTTP请求,则抛异常,否则使用代码认证并返回一个request对象。如果code是HTTP_UNAUTHORIZED(401)则表示要用户认证。如果code是HTTP_PERM_REDIRECT(308), HTTP_TEMP_REDIRECT(307), HTTP_MULT_CHOICE(300), HTTP_MOVED_PERM(301), HTTP_MOVED_TEMP(302), HTTP_SEE_OTHER(303)则表示重定向,调用buildRedirectRequest构造出一个新的重定向Request对象。如果code是HTTP_CLIENT_TIMEOUT(408)并且未命中过,则执行返回response的request。followUpRequest得到一个新的request对象后,继续while循环中的请求过程,把后面的拦截器再执行一遍。这个while循环中没有看到break关键字,所以这个while循环只有抛出异常和return两条出路。return就是返回一个response给开始的execute,抛异常则有多种,比如不支持重试并且请求异常了,或者重试次数达到最大限制数了,这里的最大重试数据为MAX_FOLLOW_UPS=20。
然后是BridgeInterceptor,这个拦截器是把OkHttp定义的字段转化成Http协议的字段同时处理cookie,这里主要是给request添加一些http请求的默认参数,当请求构造时未传入这些参数时,就会使用BridgeInterceptor的默认参数,以此来构造一个Http协议完整的请求头和请求体。在BridgeInterceptor的intercept中间位置,调用了chain.proceed继续执行后面的拦截器并得到一个Response,这个response被BridgeInterceptor进一步解析。因此这个BridgeInterceptor的真正作用就是在真正请求前给request添加一些默认参数,并将request中传入的参数转化为http协议的参数,例如将content_type转化为header中的"Content-Type",将请求后的结果进行进一步地解析处理。
第三个是缓存相关的CacheInterceptor,在其intercept方法中首先就从传入的cache对象中根据request取出一个cacheCandidate对象,而cache是个Cache对象,默认是以url作为缓存的key标识。然后使用CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()取出缓存的request和response。这里如果request为null并且response也为null,则表示本应从缓存中取,但缓存没有了,那就给一个空的response。如果request为null而response不为null,则表示从缓存中取成功,用缓存的response构造出最终的response。如果request也不为null,表表示还是要进行网络请求,执行chain.proceed,如果response的code是HTTP_NOT_MODIFIED(304),表示服务端告诉客户端,缓存还可以使用,则response还是使用缓存的response来构造,否则就使用网络请求回来的response,并将request和response存入缓存中。
ConnectInterceptor的作用就比较简单了,只是开启一个网络连接,这个网络连接可能会被已经返回的response使用,也可能会被失败了的缓存response使用,OkHttp框架的网络连接使用Exchange对象来表示。
最后就是CallServierInterceptor,最终会使用这个拦截器进行真正的网络请求。
- override fun intercept(chain: Interceptor.Chain): Response {
- val realChain = chain as RealInterceptorChain
- val exchange = realChain.exchange!!
- val request = realChain.request
- val requestBody = request.body
- val sentRequestMillis = System.currentTimeMillis()
-
- exchange.writeRequestHeaders(request)
-
- var invokeStartEvent = true
- var responseBuilder: Response.Builder? = null
- if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
- // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
- // Continue" response before transmitting the request body. If we don't get that, return
- // what we did get (such as a 4xx response) without ever transmitting the request body.
- if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
- exchange.flushRequest()
- responseBuilder = exchange.readResponseHeaders(expectContinue = true)
- exchange.responseHeadersStart()
- invokeStartEvent = false
- }
- if (responseBuilder == null) {
- if (requestBody.isDuplex()) {
- // Prepare a duplex body so that the application can send a request body later.
- exchange.flushRequest()
- val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
- requestBody.writeTo(bufferedRequestBody)
- } else {
- // Write the request body if the "Expect: 100-continue" expectation was met.
- val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
- requestBody.writeTo(bufferedRequestBody)
- bufferedRequestBody.close()
- }
- } else {
- exchange.noRequestBody()
- if (!exchange.connection.isMultiplexed) {
- // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
- // from being reused. Otherwise we're still obligated to transmit the request body to
- // leave the connection in a consistent state.
- exchange.noNewExchangesOnConnection()
- }
- }
- } else {
- exchange.noRequestBody()
- }
-
- if (requestBody == null || !requestBody.isDuplex()) {
- exchange.finishRequest()
- }
- if (responseBuilder == null) {
- responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
- if (invokeStartEvent) {
- exchange.responseHeadersStart()
- invokeStartEvent = false
- }
- }
- var response = responseBuilder
- .request(request)
- .handshake(exchange.connection.handshake())
- .sentRequestAtMillis(sentRequestMillis)
- .receivedResponseAtMillis(System.currentTimeMillis())
- .build()
- var code = response.code
- if (code == 100) {
- // Server sent a 100-continue even though we did not request one. Try again to read the actual
- // response status.
- responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
- if (invokeStartEvent) {
- exchange.responseHeadersStart()
- }
- response = responseBuilder
- .request(request)
- .handshake(exchange.connection.handshake())
- .sentRequestAtMillis(sentRequestMillis)
- .receivedResponseAtMillis(System.currentTimeMillis())
- .build()
- code = response.code
- }
-
- exchange.responseHeadersEnd(response)
-
- response = if (forWebSocket && code == 101) {
- // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
- response.newBuilder()
- .body(EMPTY_RESPONSE)
- .build()
- } else {
- response.newBuilder()
- .body(exchange.openResponseBody(response))
- .build()
- }
- if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
- "close".equals(response.header("Connection"), ignoreCase = true)) {
- exchange.noNewExchangesOnConnection()
- }
- if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
- throw ProtocolException(
- "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
- }
- return response
- }

首先调用了exchange.writeRequestHeaders将请求头写入到连接,如果请求不是GET或者PUT的,就要先处理请求体,使用exchange创建Sink对象并将requestBody写入到Sink中去。
这里需要再回到ConnectInterceptor中去看如果创建的一个连接,一个连接的底层是用什么实现的。前面说到过,interceptor chain中的exchange就是一个连接的抽象,在ConnectInterceptor中就对exchange进行了初始化。调用了RealCall的initExchange方法,在initExchange方法中使用了exchangeFinder,这个finder中拥有一个连接池和一个地址,地址由请求的url构建,而连接池是OkHttpClient的连接池,也就是说同一个OkHttpClient发出的请求,共用一个连接池,当同一个OkHttpClient对一个地址发送多个请求时,可能就可以复用连接。使用exchangeFinder调用其find方法是为创建一个ExchangeCodec对象,在find方法中调用findHealthyConnection方法先获取一个连接对象,根据方法名就可以看出,这个方法会去连接池找可以复用的连接。
findHealthConnection方法中在一个while循环中调用findConnection方法获取一个连接,如果判断连接isHealth,则return。继续跟到findConnection方法中去,首先判断这个call对象中是否已有connection,如果有则判断其可用的话,就复用这个connection。否则再调用RealConnectionPool的callAcquirePooledConnection方法从连接池中获取,连接池中有个RealConnection的ConcurrentLinkedQueue用来保存连接,循环判断连接池中的连接可以被传入的地址使用的话,就将这个连接设置给call对象并将这个连接对象返回。如果从连接池中取不到连接,则新建一个RealConnection对象,并调用其connect方法新建一个连接。connect方法中调用了connectSocket方法创建了Socket对象,并将Socket保存在RealConnection的rawSocket属性中,创建Socket后调用其connect方法与服务端建立一个网络连接。
再回到CallServerInterceptor中,当请求体写完后,调用exchanged的flushRequest方法,flushRequest方法中调用了BufferSink的flush方法,这个方法里最终调用了Socket的OutputStream的write,将数据写到服务端连接中去。
写完请求头数据了就需要开始读服务端返回的http结果了,先调用exchange.readResponseHeaders读取响应头,读取响应头时,把response的code、message、protocol也同时给解析了。如果判断响应体为null,则将请求体数据写到服务端。将读取到的responseBuilder构造成一个response对象,判断如果不是websocket并且code不是101,则还需要请求响应体数据,这样一个请求Response才算完全构造成功,最后将这个response返回给拦截链。
OkHttp框架基于职责链模式,将一个请求的过程分拆为多个小过程,每个小过程由一个Interceptor来完成,并且使用者可以在所有内置interceptor之前插入自定义的interceptor对请求过程加以扩展。网络连接部分OkHttp框架使用Exchange对象予以抽象,Interceptor只实现请求逻辑,具体的数据读写由Exchange来完成,因此理论来说底层的数据请求我们还可以替换。整个框架对于逻辑处理与数据处理耦合较低,扩展性较强。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。