WIFI直连
如何创建一个使用Wi-Fi P2P的应用 创建使用Wi-Fi P2P功能的应用需要多个步骤,其中包括注册广播接受者,发现设备,连接设备,传输数据等步骤。下面是关于这些步骤的介绍。
初始化设置
在调用wifi-p2p的API前,你须要确保相应设备支持Wi-Fi P2P。如果该设备支持Wi-Fi P2P,那么你可以在你的应用中获取WifiP2pManager实例,并进行广播接受者的创建和注册,以及调用其它Wi-Fi P2P 的API。
1.在manifest文件当中声明相关权限,同时声明你应用的SDK 最小版本号(因为Android4.0才开始支持WiFi直连,所以版本号是14)。
<uses-sdk android:minSdkVersion="14" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.检查是WiFi是否开启。一个比较好的检查方式是,当广播接受者收到WIFI_P2P_STATE_CHANGED_ACTION广播时,对其进行判断看WiFi是否开启。然后将WiFi的状态通知给Activity,让其对此作出相应的处理(例如提示用户或者开启WiFi)。
@Override public void onReceive(Context context, Intent intent) { ... String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { // Wifi P2P is enabled } else { // Wi-Fi P2P is not enabled } } ... }
3、在你的Activity的onCreate方法当中,获取WifiP2pManager的实例。然后调用initialize()方法来将你的应用注册到 Wi-Fi P2P framework当中,这个方法将会返回一个WifiP2pManager.Channel对象,它把你的应用与底层的 Wi-Fi P2P framework连接起来。
建议你同时创建一个广播接受者,并把WifiP2pManager和WifiP2pManager.Channel以及Activity引用传给它。这可以让你的broadcast receiver在接收到特定时间的广播时,能够很方便的通知Activity或者对Wi-Fi进行操作。
WifiP2pManager mManager; Channel mChannel; BroadcastReceiver mReceiver; ... @Override protected void onCreate(Bundle savedInstanceState){ ... mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); mChannel = mManager.initialize(this, getMainLooper(), null); mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this); ... }
4、创建一个Intent filter,然后把下面这些intent加入其中,这样你的广播接收者就能接收到WiFi变化相应的广播。
IntentFilter mIntentFilter; ... @Override protected void onCreate(Bundle savedInstanceState){ ... mIntentFilter = new IntentFilter(); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
5、在Activity的onResume()方法当中注册广播接受者,在onPause方法当中注销它。
/* register the broadcast receiver with the intent values to be matched */ @Override protected void onResume() { super.onResume(); registerReceiver(mReceiver, mIntentFilter); } /* unregister the broadcast receiver */ @Override protected void onPause() { super.onPause(); unregisterReceiver(mReceiver); }
发现设备
调用discoverPeers()方法,就可以开始发现周边适合连接的设备。这个方法的调用是异步的,如果你设置了一个WifiP2pManager.ActionListener监听器,那么这个方法的调用结果会通过ActionListener回调返回。onSuccess()方法只告诉你该发现节点的任务执行成功,但不会提供所发现节点的任何信息。
mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { ... } @Override public void onFailure(int reasonCode) { ... } });
如果发现节点的任务执行成功,并且检测到了适合节点。系统会发送WIFI_P2P_PEERS_CHANGED_ACTION广播,当你收到这个广播的时候,你就可以调用requestPeers()方法,来获取发现了节点列表。
PeerListListener myPeerListListener; ... if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() if (mManager != null) { mManager.requestPeers(mChannel, myPeerListListener); } }
连接节点
当你找到自己想要连接的设备时,你可以通过调用connect()方法来连接这个设备,调用这个方法需要一个WifiP2pConfig对象作为参数。WifiP2pConfig对象包含了进行连接的一些配置信息,包括设备地址,认证方式,谁作为GroupOwner等。这个方法调用的结果会通过WifiP2pManager.ActionListener返回。
//obtain a peer from the WifiP2pDeviceList WifiP2pDevice device; WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; mManager.connect(mChannel, config, new ActionListener() { @Override public void onSuccess() { //success logic } @Override public void onFailure(int reason) { //failure logic } });
传输数据
一旦设备间的连接成功建立,你就可以通过socket来传输数据,基本的步骤如下。 1、创建ServerSocket,并调用accept()方法在指定端口等待客户端的连接,注意这个过程将会阻塞线程,请在后台线程完成这个它。
2、创建ClientSocket,然后使用服务器IP和端口去连接充当服务器的设备。
3、 客户端往服务器发送数据。当客户端成功连接上服务器端之后,你就可以以字节流的形式向服务器发送数据了。
4、服务器接收数据。当服务器的accept()接受一个客户端连接之后,服务器端口能够收到客户端发来的数据了。
注意在WiFi P2P连接中,Group Owner和Group Client都能够作为Server Socket,并且当一个Socket连接建立后,双方都可以收发数据。
下面是一个通过WiFi P2P从客户端往服务器端发送图片的例子,以下是服务器端部分代码。
public static class FileServerAsyncTask extends AsyncTask { private Context context; private TextView statusText; public FileServerAsyncTask(Context context, View statusText) { this.context = context; this.statusText = (TextView) statusText; } @Override protected String doInBackground(Void... params) { try { /** * Create a server socket and wait for client connections. This * call blocks until a connection is accepted from a client */ ServerSocket serverSocket = new ServerSocket(8888); Socket client = serverSocket.accept(); /** * If this code is reached, a client has connected and transferred data * Save the input stream from the client as a JPEG file */ final File f = new File(Environment.getExternalStorageDirectory() + "/" + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis() + ".jpg"); File dirs = new File(f.getParent()); if (!dirs.exists()) dirs.mkdirs(); f.createNewFile(); InputStream inputstream = client.getInputStream(); copyFile(inputstream, new FileOutputStream(f)); serverSocket.close(); return f.getAbsolutePath(); } catch (IOException e) { Log.e(WiFiDirectActivity.TAG, e.getMessage()); return null; } } /** * Start activity that can handle the JPEG image */ @Override protected void onPostExecute(String result) { if (result != null) { statusText.setText("File copied - " + result); Intent intent = new Intent(); intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse("file://" + result), "image/*"); context.startActivity(intent); } } }
以下是客户端部分代码
Context context = this.getApplicationContext(); String host; int port; int len; Socket socket = new Socket(); byte buf[] = new byte[1024]; ... try { /** * Create a client socket with the host, * port, and timeout information. */ socket.bind(null); socket.connect((new InetSocketAddress(host, port)), 500); /** * Create a byte stream from a JPEG file and pipe it to the output stream * of the socket. This data will be retrieved by the server device. */ OutputStream outputStream = socket.getOutputStream(); ContentResolver cr = context.getContentResolver(); InputStream inputStream = null; inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg")); while ((len = inputStream.read(buf)) != -1) { outputStream.write(buf, 0, len); } outputStream.close(); inputStream.close(); } catch (FileNotFoundException e) { //catch logic } catch (IOException e) { //catch logic } /** * Clean up any open sockets when done * transferring or if an exception occurred. */ finally { if (socket != null) { if (socket.isConnected()) { try { socket.close(); } catch (IOException e) { //catch logic } } } }
OKHttp架构(最广泛的框架)
Download OKHttp3
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
不要忘了在清单文件声明访问Internet的权限,如果使用 DiskLruCache
,那还得声明写外存的权限。
异步GET请求
-
new OKHttpClient
-
构造Request对象
-
通过前两步中的对象构建Call对象
-
通过Call#enqueue(Callback)方法来提交异步请求:
String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .get()//默认就是GET请求,可以不写 .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: "); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "onResponse: " + response.body().string()); } });
异步发起的请求会被加入到 Dispatcher
中的 runningAsyncCalls
双端队列中通过线程池来执行。
同步GET请求
前面几个步骤和异步方式一样,只是最后一部是通过 Call#execute()
来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行,否则有可能引起ANR异常,Android3.0
以后已经不允许在主线程访问网络。
String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .build(); final Call call = okHttpClient.newCall(request); new Thread(new Runnable() { @Override public void run() { try { Response response = call.execute(); Log.d(TAG, "run: " + response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }).start();
POST方式提交String
这种方式与前面的区别就是在构造Request对象时,需要多构造一个RequestBody对象,用它来携带我们要提交的数据。在构造 RequestBody
需要指定MediaType
,用于描述请求/响应 body
的内容类型,RequstBody的几种构造方式:
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8"); String requestBody = "I am Jdqm."; Request request = new Request.Builder() .url("https://api.github.com/markdown/raw") .post(RequestBody.create(mediaType, requestBody)) .build(); OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message()); Headers headers = response.headers(); for (int i = 0; i < headers.size(); i++) { Log.d(TAG, headers.name(i) + ":" + headers.value(i)); } Log.d(TAG, "onResponse: " + response.body().string()); } });
响应内容:
http/1.1 200 OK Date:Sat, 10 Mar 2018 05:23:20 GMT Content-Type:text/html;charset=utf-8 Content-Length:18 Server:GitHub.com Status:200 OK X-RateLimit-Limit:60 X-RateLimit-Remaining:52 X-RateLimit-Reset:1520661052 X-CommonMarker-Version:0.17.4 Access-Control-Expose-Headers:ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval Access-Control-Allow-Origin:* Content-Security-Policy:default-src 'none' Strict-Transport-Security:max-age=31536000; includeSubdomains; preload X-Content-Type-Options:nosniff X-Frame-Options:deny X-XSS-Protection:1; mode=block X-Runtime-rack:0.019668 Vary:Accept-Encoding X-GitHub-Request-Id:1474:20A83:5CC0B6:7A7C1B:5AA36BC8 onResponse: <p>I am Jdqm.</p>
POST方式提交流
RequestBody requestBody = new RequestBody() { @Nullable @Override public MediaType contentType() { return MediaType.parse("text/x-markdown; charset=utf-8"); } @Override public void writeTo(BufferedSink sink) throws IOException { sink.writeUtf8("I am Jdqm."); } }; Request request = new Request.Builder() .url("https://api.github.com/markdown/raw") .post(requestBody) .build(); OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message()); Headers headers = response.headers(); for (int i = 0; i < headers.size(); i++) { Log.d(TAG, headers.name(i) + ":" + headers.value(i)); } Log.d(TAG, "onResponse: " + response.body().string()); } });
POST提交文件
OkHttpClient okHttpClient = new OkHttpClient(); RequestBody requestBody = new FormBody.Builder() .add("search", "Jurassic Park") .build(); Request request = new Request.Builder() .url("https://en.wikipedia.org/w/index.php") .post(requestBody) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message()); Headers headers = response.headers(); for (int i = 0; i < headers.size(); i++) { Log.d(TAG, headers.name(i) + ":" + headers.value(i)); } Log.d(TAG, "onResponse: " + response.body().string()); } });
提交表单时,使用 RequestBody
的实现类FormBody
来描述请求体,它可以携带一些经过编码的 key-value
请求体,键值对存储在下面两个集合中:
private final List<String> encodedNames; private final List<String> encodedValues;
POST方式提交分块代码请求
MultipartBody 可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如它的 Content-Disposition
。如果 Content-Length
和 Content-Type
可用的话,他们会被自动添加到请求头中。
private static final String IMGUR_CLIENT_ID = "..."; private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png"); private void postMultipartBody() { OkHttpClient client = new OkHttpClient(); // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image MultipartBody body = new MultipartBody.Builder("AaB03x") .setType(MultipartBody.FORM) .addPart( Headers.of("Content-Disposition", "form-data; name=\"title\""), RequestBody.create(null, "Square Logo")) .addPart( Headers.of("Content-Disposition", "form-data; name=\"image\""), RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png"))) .build(); Request request = new Request.Builder() .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID) .url("https://api.imgur.com/3/image") .post(body) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } }); }
拦截器-interceptor
OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor
分为两类:
-
一类是全局的
interceptor
,该类interceptor
在整个拦截器链中最早被调用,通过OkHttpClient.Builder#addInterceptor(Interceptor)
传入; -
另外一类是非网页请求的
interceptor
,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的interceptor
被保存在List<Interceptor> interceptors
集合中,按照添加顺序来逐个调用,具体可参考RealCall#getResponseWithInterceptorChain()
方法。通过OkHttpClient.Builder#addNetworkInterceptor(Interceptor)
传入;
这里举一个简单的例子,例如有这样一个需求,我要监控App通过 OkHttp
发出的所有原始请求,以及整个请求所耗费的时间,针对这样的需求就可以使用第一类全局的 interceptor
在拦截器链头去做。
OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new LoggingInterceptor()) .build(); Request request = new Request.Builder() .url("http://www.publicobject.com/helloworld.txt") .header("User-Agent", "OkHttp Example") .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { ResponseBody body = response.body(); if (body != null) { Log.d(TAG, "onResponse: " + response.body().string()); body.close(); } } });
public class LoggingInterceptor implements Interceptor { private static final String TAG = "LoggingInterceptor"; @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); long startTime = System.nanoTime(); Log.d(TAG, String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long endTime = System.nanoTime(); Log.d(TAG, String.format("Received response for %s in %.1fms%n%s", response.request().url(), (endTime - startTime) / 1e6d, response.headers())); return response; } }
针对这个请求,打印出来的结果:
Sending request http://www.publicobject.com/helloworld.txt on null User-Agent: OkHttp Example Received response for https://publicobject.com/helloworld.txt in 1265.9ms Server: nginx/1.10.0 (Ubuntu) Date: Wed, 28 Mar 2018 08:19:48 GMT Content-Type: text/plain Content-Length: 1759 Last-Modified: Tue, 27 May 2014 02:35:47 GMT Connection: keep-alive ETag: "5383fa03-6df" Accept-Ranges: bytes
注意到一点是这个请求做了重定向,原始的 request url
是 http://www.publicobject.com/helloworld.tx
,而响应的 request url
是 https://publicobject.com/helloworld.txt
,这说明一定发生了重定向,但是做了几次重定向其实我们这里是不知道的,要知道这些的话,可以使用 addNetworkInterceptor()
去做
。
Okhttp默认情况下使用的是系统。
其他
-
推荐让
OkHttpClient
保持单例,用同一个OkHttpClient
实例来执行你的所有请求,因为每一个OkHttpClient
实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个OkHttpClient
实例,显然就是一种资源的浪费。当然,也可以使用如下的方式来创建一个新的OkHttpClient
实例,它们共享连接池、线程池和配置信息。OkHttpClient eagerClient = client.newBuilder() .readTimeout(500, TimeUnit.MILLISECONDS) .build(); Response response = eagerClient.newCall(request).execute();
-
每一个Call(其实现是RealCall)只能执行一次,否则会报异常,具体参见
RealCall#execute()
OKHttp3源码分析
在OkHttp3中,其灵活性很大程度上体现在可以 intercept
其任意一个环节,而这个优势便是okhttp3整个请求响应架构体系的精髓所在,先放出一张主框架请求流程图,接着再分析源码。
String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: "); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "onResponse: " + response.body().string()); } });
这大概是一个最简单的一个例子了,在new OkHttpClient()
内部使用构造器模式初始化了一些配置信息:支持协议、任务分发器(其内部包含一个线程池,执行异步请求)、连接池(其内部包含一个线程池,维护connection)、连接/读/写超时时长等信息。
public Builder() { dispatcher = new Dispatcher(); //任务调度器 protocols = DEFAULT_PROTOCOLS; //支持的协议 connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); cookieJar = CookieJar.NO_COOKIES; socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; proxyAuthenticator = Authenticator.NONE; authenticator = Authenticator.NONE; connectionPool = new ConnectionPool(); //连接池 dns = Dns.SYSTEM; followSslRedirects = true; followRedirects = true; retryOnConnectionFailure = true; connectTimeout = 10_000;//超时时间 readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0; }
第一行创建了一个Dispatcher
任务调度器,它定义了三个双向任务队列,两个异步队列:准备执行的请求队列 readyAsyncCalls
、正在运行的请求队列 runningAsyncCalls
;一个正在运行的同步请求队列 runningSyncCalls
;
public final class Dispatcher { private int maxRequests = 64; //最大请求数量 private int maxRequestsPerHost = 5; //每台主机最大的请求数量 private @Nullable Runnable idleCallback; /** Executes calls. Created lazily. */ private @Nullable ExecutorService executorService; //线程池 /** Ready async calls in the order they'll be run. */ private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); /** 这个线程池没有核心线程,线程数量没有限制,空闲60s就会回收*/ public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } }
另外还有一个线程池 executorService
,这个线程池跟Android中的CachedThreadPool非常类似,这种类型的线程池,适用于大量的耗时较短的异步任务。下一篇文章 将对OkHttp框架中的线程池做一个总结。
接下来接着看Request的构造,这个例子Request比较简单,指定了请求方式 GET
和请求 url
public static class Builder { HttpUrl url; String method; Headers.Builder headers; RequestBody body; Object tag; public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); } public Builder url(HttpUrl url) { if (url == null) throw new NullPointerException("url == null"); this.url = url; return this; } public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); } ... }
紧接着通过 OkHttpClient
和 Request
构造一个 Call
对象,它的实现是RealCall
public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket){ // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); }
可以看到在 RealCall
的构造方法中创建了一个RetryAndFollowUpInterceptor
,用于处理请求错误和重定向等,这是 Okhttp
框架的精髓 interceptor chain
中的一环,默认情况下也是第一个拦截器,除非调用 OkHttpClient.Builder#addInterceptor(Interceptor)
来添加全局的拦截器。关于拦截器链的顺序参见 RealCall#getResponseWithInterceptorChain()
方法。
public void enqueue(Callback responseCallback) { synchronized (this) { //每个请求只能之执行一次 if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
可以看到,一个 Call
只能执行一次,否则会抛异常,这里创建了一个 AsyncCall
并将Callback传入,接着再交给任务分发器 Dispatcher
来进一步处理。
synchronized void enqueue(AsyncCall call) { //正在执行的任务数量小于最大值(64),并且此任务所属主机的正在执行任务小于最大值(5) if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
从 Dispatcher#enqueue()
方法的策略可以看出,对于请求的入队做了一些限制,若正在执行的请求数量小于最大值(默认64),并且此请求所属主机的正在执行任务小于最大值(默认5),就加入正在运行的队列并通过线程池来执行该任务,否则加入准备执行队列中。
现在回头看看 AsyncCall
,它继承自 NamedRunnable
,而 NamedRunnable
实现了 Runnable
接口,它的作用有2个: ①采用模板方法的设计模式,让子类将具体的操作放在 execute()
方法中; ②给线程指定一个名字,比如传入模块名称,方便监控线程的活动状态;
public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { //采用模板方法让子类将具体的操作放到此execute()方法 execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); }
final class AsyncCall extends NamedRunnable { //省略... @Override protected void execute() { boolean signalledCallback = false; try { //调用 getResponseWithInterceptorChain()获得响应内容 Response response = getResponseWithInterceptorChain(); //① if (retryAndFollowUpInterceptor.isCanceled()) { //这个标记为主要是避免异常时2次回调 signalledCallback = true; //回调Callback告知失败 responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; //回调Callback,将响应内容传回去 responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { //不管请求成功与否,都进行finished()操作 client.dispatcher().finished(this);//② } } }
先看注释②的行finally块中执行的 client.dispatcher().finished(this)
void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); } private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { //从正在执行的队列中将其移除 if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); //推动下一个任务的执行 runningCallsCount = runningCallsCount();//同步+异步的正在执行任务数量 idleCallback = this.idleCallback; } //如果没有正在执行的任务,且idleCallback不为null,则回调通知空闲了 if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } }
其中promoteCalls()
为推动下一个任务执行,其实它做的也很简单,就是在条件满足的情况下,将 readyAsyncCalls
中的任务移动到 runningAsyncCalls
中,并交给线程池来执行,以下是它的实现。
private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. //若条件允许,将readyAsyncCalls中的任务移动到runningAsyncCalls中,并交给线程池执行 for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } //当runningAsyncCalls满了,直接退出迭代 if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
接下来就回到注释①处的响应内容的获取 getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); //这是一个List,是有序的 interceptors.addAll(client.interceptors());//首先添加的是用户添加的全局拦截器 interceptors.add(retryAndFollowUpInterceptor); //错误、重定向拦截器 //桥接拦截器,桥接应用层与网络层,添加必要的头、 interceptors.add(new BridgeInterceptor(client.cookieJar())); //缓存处理,Last-Modified、ETag、DiskLruCache等 interceptors.add(new CacheInterceptor(client.internalCache())); //连接拦截器 interceptors.add(new ConnectInterceptor(client)); //从这就知道,通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效 if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } //真正访问服务器的拦截器 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); }
可以看这块重点就是 interceptors
这个集合,首先将前面的 client.interceptors()
全部加入其中,还有在创建 RealCall
时的 retryAndFollowUpInterceptor
加入其中,接着还创建并添加了BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor
,最后通过RealInterceptorChain#proceed(Request)
来执行整个 interceptor chain
,可见把这个拦截器链搞清楚,整体流程也就明朗了。
RealInterceptorChain#proceed()
public Response proceed(Request request) throws IOException { return proceed(request, streamAllocation, httpCodec, connection); } public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { //省略异常处理... // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); //省略异常处理... return response; }
从这段实现可以看出,是按照添加到 interceptors
集合的顺序,逐个往下调用拦截器的intercept()方法,所以在前面的拦截器会先被调用。这个例子中自然就是 RetryAndFollowUpInterceptor
了。
public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); //创建一个StreamAllocation StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; //统计重定向次数,不能大于20 int followUpCount = 0; Response priorResponse = null; while (true) { if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response; boolean releaseConnection = true; try { //调用下一个interceptor的来获得响应内容 response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getLastConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } //重定向处理 Request followUp = followUpRequest(response, streamAllocation.route()); if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; } }
这个拦截器就如同它的名字retry and followUp
,主要负责错误处理和重定向等问题,比如路由错误、IO异常等。
接下来就到了BridgeInterceptor#intercept()
,在这个拦截器中,添加了必要请求头信息,gzip处理等。
public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); //从这开始给请求添加了一些请求头信息 RequestBody body = userRequest.body(); if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); } } if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { GzipSource responseBody = new GzipSource(networkResponse.body().source()); Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); String contentType = networkResponse.header("Content-Type"); responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody))); } return responseBuilder.build(); }
这个拦截器处理请求信息、cookie、gzip等,接着往下是 CacheInterceptor
public Response intercept(Chain chain) throws IOException { Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache != null) { cache.trackResponse(strategy); } if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, fail. if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // If we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { //调用下一个拦截器进行网络请求 networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } } // If we have a cache response too, then we're doing a conditional get. if (cacheResponse != null) { if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }
这个拦截器主要工作是做做缓存处理,如果有有缓存并且缓存可用,那就使用缓存,否则进行调用下一个拦截器 ConnectionInterceptor
进行网络请求,并将响应内容缓存。
public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); }
这个拦截器主要是打开一个到目标服务器的 connection
并调用下一个拦截器 CallServerInterceptor
,这是拦截器链最后一个拦截器,它向服务器发起真正的网络请求。
public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); realChain.eventListener().requestHeadersStart(realChain.call()); httpCodec.writeRequestHeaders(request); realChain.eventListener().requestHeadersEnd(realChain.call(), request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != 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".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { // Write the request body if the "Expect: 100-continue" expectation was met. realChain.eventListener().requestBodyStart(realChain.call()); long contentLength = request.body().contentLength(); CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); } else if (!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. streamAllocation.noNewStreams(); } } httpCodec.finishRequest(); if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(false); } Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); int 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 responseBuilder = httpCodec.readResponseHeaders(false); response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); code = response.code(); } realChain.eventListener() .responseHeadersEnd(realChain.call(), response); if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }
从上面的请求流程图可以看出,OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor 分为两类: ①一类是全局的 interceptor,该类 interceptor 在整个拦截器链中最早被调用,通过 OkHttpClient.Builder#addInterceptor(Interceptor) 传入; ②另外一类是非网页请求的 interceptor ,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的 interceptor 被保存在 List<Interceptor> interceptors 集合中,按照添加顺序来逐个调用,具体可参考 RealCall#getResponseWithInterceptorChain() 方法。通过 OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 传入;
OkHttp3架构分析
在OkHttp3中,其灵活性很大程度上体现在,可以intercept其任意一个环节,而这个优势便是okhttp3整个请求响应架构体系的精髓所在:
Okhttp请求流程
-
在OkHttp3中,每一个请求任务都封装为一个Call,其实现为RealCall。
-
而所有的策略几乎都可以通过OkHttpClient传入
-
所有全局策略与数据,除了存储在允许上层访问的OkHttpClient实例以外,还有一部分是存储在只允许包可见的Internal.instance中(如连接池、路由黑名单等)
-
OkHttp中用户可传入的interceptor分为两类,一类是全局interceptor,该类interceptor在请求开始之前最早被调用,另外一类为非网页请求的networkInterceptor,这类interceptor只有在非网页请求中会被调用,并且是在组装完成请求之后,真正发起请求之前被调用(这块具体可以参看RealCall#getResponseWithInterceptorChain()方法)
-
整个请求过程通过RealInterceptorChain#proceed来连接,在每个interceptor中调用下一个interceptor来完成整个请求流程,并且在回到当前interceptor后完成响应处理
-
在异步请求中,我们通过Callback来获得简单清晰的请求回调(onFailure、onResponse)
-
在OkHttpClient中,我们可以传入EventListener的工厂方法,为每一个请求创建一个EventListener,来接收非常细的事件回调
完整interceptor-chain
OkHttp3中的线程池
OkHttp
中的对所有的任务采用 NamedRunnable
,约束每个执行单元给出对应的业务名称,以便于线程维护。
1.异步请求线程池-OkHttp Dispatcher
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }
-
该线程池与Android下的
Executors.newCachedThreadPool()
比较类似; -
无任务上限,自动回收闲置60s的线程,适用于大量耗时较短的任务;
-
虽然线程池无任务上限,但是Dispatcher对入口
enqueue()
进行了把关,最大的异步任务数默认是64,同一个主机默认是5,当然这两个默认值是可以修改的,Dispatcher提供的修改接口;
okHttpClient.dispatcher().setMaxRequests(128); okHttpClient.dispatcher().setMaxRequestsPerHost(10);
-
通过两个双端队列来维护准备执行的任务和正在执行的任务:
Deque<AsyncCall> readyAsyncCalls
,Deque<AsyncCall> runningAsyncCalls
; -
在每个任务结束时,都会检查
readyAsyncCalls
是否有任务,在条件满足的情况下,按照先进先出的原则将任务移动到runningAsyncCalls
中,并在线程池中执行;
异步请求线程池
2.连接池清理线程池-OkHttp ConnectionPool
/** * Background threads are used to cleanup expired connections. There will be at most a single * thread running per connection pool. The thread pool executor permits the pool itself to be * garbage collected. */ private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
-
该线程池用来清理长时间闲置的和泄漏的连接;
-
该线程池本身无任务上限,线程闲置60s自动回收;
-
虽然任务无上限,但其通过
cleanupRunning
标记来控制只有一个线程在运行,当连接池中没有连接后才会被重新设置为false
;
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); }
-
次工作线程会不断地清理,当清理完一遍后超时连接后,根据当前连接池中最近的下一个空闲超时连接计算出一个阻塞时间并阻塞,直到连接池中没有任何连接才结束,并将
cleanupRunning
设为false
; -
在每次有连接加入连接池时,如果当前没有清理任务运行,会加入一个清理任务到到线程池中执行;
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); }
连接池清理线程池
3. 缓存整理线程池-OkHttp DiskLruCache
// Use a single background thread to evict entries. Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));
-
该线程池用于整理本地请求缓存数据;
-
缓存的整理包含: 达到阀值大小的文件,删除最近最少使用的记录,在有关操作达到一定数量以后对记录进行重建;
-
最大运行线程数1,无需考虑线程安全问题,自动回收闲置60s的线程;
4. HTTP2异步事务线程池-OkHttp Http2Connection
-
HTTP2采用了多路复用,因此需要维护连接有效性,本线程池就是用于维护相关的各类HTTP2事务;
-
线程池本身无任务上限,自动回收闲置60s的线程;
-
每一个HTTP2连接都有这么一个线程池存在;