第五章 网络 之 Android网络知识&框架(二)
文章目录
- 一.Android实现网络请求的主流方法
- 1.1 HttpClient
- (1)简介
- (2)实现
- 1.2 HttpURLConnection
- (1)简介
- (2)实现
- 1.3 对比
- 二.主流网络请求库
- 2.1 简介
- 2.2 对比(Android-Async-Http、Volley、OkHttp、Retrofit)
- 三.okHttp
- 3.1 简介
- 3.2 使用步骤
- (1)添加okHttp和okIo
- (2)创建OkHttpClient对象
- (3)get/post请求数据
- (4)get/post调用
- 3.3 源码解读
- (一)整体框架
- (二)工作原理
- 2.1 OkHttpClient 构造
- 2.2 OkHttpClient 请求网络
- 2.3 OkHttp 执行流程总结
- 2.4 OkHttp 设计模式总结
- (三)源码解读
- 3.1 重要的类
- 3.2 请求流程
- (1)创建OkHttpClient
- (2)创建Call对象,并发起同步/异步请求
- (3)Dispatcher 调度器对同步/异步请求进行处理
- (4)getResponseWithInterceptorChain()/拦截器链
- (5)client.dispatcher().finished(this);
- 3.3 同步与异步(线程池)的实现
- 3.4 拦截器
- (一)处理网络响应的拦截器机制
- (二)Interceptor解析
- (1)RetryAndFollowUpInterceptor
- (2)BridgeInterceptor
- (3)CacheInterceptor
- (4)ConnectInterceptor(核心,连接池)
- (5)CallServerInterceptor
- 3.5 缓存策略
- 3.6 连接池
- 3.7 重连机制
- 3.8 Gzip的使用方式
- 3.9 安全性
- 3.10 平台适应性
- 四.Volley
- 4.1 简介
- 4.2 特点
- 4.3 使用
- 4.3.1 Volley的get和post请求方式的使用
- (1)网络请求之Get方式请求数据
- (2)网络请求之Post方式请求数据
- 4.3.2 Volley的网络请求队列建立和取消队列请求
- (1)建立请求队列(使用Singleton模式)
- (2)取消网络请求
- 4.3.3 Volley与Activity生命周期的联动
- 4.3.4 Volley加载网络图片
- (1)使用ImageRequest加载网络图片
- (2)使用ImageLoader加载及缓存网络图片
- 4.3.5 Volley的二次回调封装
- 4.4 工作原理/源码分析
- 五.Retrofit
- 5.1 简介
- 5.2 特点
- 5.3 使用
- 5.4 工作原理/源码分析
- (5.4.1)工作流程
- (5.4.2)重要角色
- (5.4.3)源码分析
- (5.4.4)设计模式
- (5.4.5)总结
- 5.5 总结
- (5.5.1)Retrofit使用7步骤
- (5.5.2)网络通信8步骤
一.Android实现网络请求的主流方法
HttpURLConnection和HttpClient:这两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、以及连接池等功能。
1.1 HttpClient
(1)简介
DefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具体的实现类,它们都拥有众多的API,而且实现比较稳定,bug数量也很少。
但同时也由于HttpClient的API数量过多,使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,结构复杂,维护成本高。
Android SDK中包含了HttpClient,在Android6.0版本直接删除了HttpClient类库。
(2)实现
LoginHttpClientUtils.java
public Boolean LoginGet(Context context,String username,String password){ try{ String path = "http://192.168.1.138:8080/chyWebTest/LoginServlet?username="+username+"&password="+password; Boolean isSuccess = false; //1.创建一个HttpClient对象 HttpClient httpClient = new DefaultHttpClient(); //2.设置请求方式 HttpGet httpGet = new HttpGet(path); //3.执行一个Http请求(返回HttpResponse) HttpResponse httpResponse = httpClient.execute(httpGet); //4.获取请求的状态码 StatusLine statusLine = httpResponse.getStatusLine(); int code = statusLine.getStatusCode(); //5.判断状态吗后获取内容 if(code==200){ //获取实体内容,中封装有Http请求返回流信息 HttpEntity entity = httpResponse.getEntity(); InputStream inputStream = entity.getContent(); String result = StreamUtil.StreamToString(inputStream); if(result.equals(("success"))){ isSuccess = true; } } return isSuccess; }catch (Exception e){ e.printStackTrace(); } return null;}
LoginHttpClientActivity.java
//主线程创建一个Handler对象handler = new Handler(){ //calledFromWrongThreadException从错误的线程调用(UI操作只能通过主线程,子线程不能更新UI空间) @Override public void handleMessage(Message msg) { //重写handler的handleMessage方法,用来接受并处理子线程发来的消息,并可执行UI操作 if(msg.obj.equals("success")) { Toast.makeText(LoginHttpClientActivity.this,"登录成功",Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(LoginHttpClientActivity.this,"登录失败",Toast.LENGTH_SHORT).show(); } }};//子线程创建一个Message对象,将获取的数据绑定给Msg,通过主线程中handler对象将msg发送给主线程 Message msg = Message.obtain(); if(result)msg.obj = "success"; else msg.obj = "fail"; handler.sendMessage(msg);} //创建一个新线程执行HttpClient网络请求 Thread getThread = new Thread(new LoginGetThread()); getThread.start();class LoginGetThread implements Runnable{ public void run(){ login(username,password,0); }}
1.2 HttpURLConnection
(1)简介
HttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。虽然HttpURLConnection的API提供的比较简单,但是同时这也使得我们可以更加容易地去使用和扩展它。
Android2.2前有个重大BUG:调用close()函数会影响连接池,导致链接复用失效。
比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
private void disableConnectionReuseIfNecessary() { // 这是一个2.2版本之前的bug if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) { System.setProperty("http.keepAlive", "false"); }}
Android2.2后默认开启了gzip压缩、提高https性能(2.3)与缓冲机制(4.0)
(2)实现
1、创建一个UrlConnManager类,然后里面提供getHttpURLConnection()方法用于配置默认的参数并返回HttpURLConnection:
public static HttpURLConnection getHttpURLConnection(String url){ HttpURLConnection mHttpURLConnection=null; try { URL mUrl=new URL(url); mHttpURLConnection=(HttpURLConnection)mUrl.openConnection(); //设置链接超时时间 mHttpURLConnection.setConnectTimeout(15000); //设置读取超时时间 mHttpURLConnection.setReadTimeout(15000); //设置请求参数 mHttpURLConnection.setRequestMethod("POST"); //添加Header mHttpURLConnection.setRequestProperty("Connection","Keep-Alive"); //接收输入流 mHttpURLConnection.setDoInput(true); //传递参数时需要开启 mHttpURLConnection.setDoOutput(true); } catch (IOException e) { e.printStackTrace(); } return mHttpURLConnection ; }
发送POST请求,所以在UrlConnManager类中再写一个postParams()方法用来组织一下请求参数并将请求参数写入到输出流中
public static void postParams(OutputStream output,ListparamsList) throws IOException{ StringBuilder mStringBuilder=new StringBuilder(); for (NameValuePair pair:paramsList){ if(!TextUtils.isEmpty(mStringBuilder)){ mStringBuilder.append("&"); } mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8")); mStringBuilder.append("="); mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8")); } BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8")); writer.write(mStringBuilder.toString()); writer.flush(); writer.close(); }
接下来我们添加请求参数,调用postParams()方法将请求的参数组织好传给HttpURLConnection的输出流,请求连接并处理返回的结果
private void useHttpUrlConnectionPost(String url) { InputStream mInputStream = null; HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url); try { List postParams = new ArrayList<>(); //要传递的参数 postParams.add(new BasicNameValuePair("username", "moon")); postParams.add(new BasicNameValuePair("password", "123")); UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams); mHttpURLConnection.connect(); mInputStream = mHttpURLConnection.getInputStream(); int code = mHttpURLConnection.getResponseCode(); String respose = converStreamToString(mInputStream); Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose); mInputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
最后开启线程请求网络
private void useHttpUrlConnectionGetThread() { new Thread(new Runnable() { @Override public void run() { useHttpUrlConnectionPost("http://www.baidu.com"); } }).start(); }
1.3 对比
Android2.2前不建议使用HttpURLConnection,Android4.4后,底层实现被OkHttp替换
Android5.0后HttpClient被官方弃用
所以,在Android 2.2版本以及之前的版本使用HttpClient是较好的选择,而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。另外在Android 6.0版本中,HttpClient库被移除了,HttpURLConnection则是以后我们唯一的选择。
二.主流网络请求库
2.1 简介
网络请求开源库是一个将 网络请求+异步+数据处理 封装好的类库(网络请求是Android网络请求原生方法HttpClient或HttpURLConnection,异步包括多线程、线程池,数据处理包括序列化和反序列化)
使用网络请求库后,实现网络请求的需求同时不需要考虑:异步请求、线程池、缓存等等;降低开发难度,缩短开发周期,使用方便
2.2 对比(Android-Async-Http、Volley、OkHttp、Retrofit)
三.okHttp
3.1 简介
(1)支持http2/SPDY黑科技,共享同一个Socket来处理同一个服务器的所有请求(同一域名的所有请求stream共享同一个tcp连接),解决了HOL Blocking
SPDY(读作“SPeeDY”)是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY是对HTTP协议的加强。新协议的功能包括数据流的多路复用、支持服务器推送技术、请求优先级、HTTP报头压缩以及强制使用SSL传输协议。
(2)socket自动选择最好路线,并支持自动重连
(3)拥有自动维护的socket连接池,减少握手次数,减少请求延时
(4)拥有队列线程池,轻松写并发
(5)拥有Interceptors(拦截器)轻松处理请求与响应(透明GZIP压缩,转换从而减少数据流量)
(6)基于Headers的缓存策略减少重复的网络请求
3.2 使用步骤
(1)添加okHttp和okIo
(Okio是一款轻量级IO框架,是著名网络框架OkHttp的基石。Okio结合了java.io和java.nio,提供阻塞IO和非阻塞IO的功能,同时也对缓存等底层结构做了优化,能让你更轻快的获得、存储和处理数据。)
(2)创建OkHttpClient对象
(3)get/post请求数据
(4)get/post调用
OkHttpClient client = new OkHttpClient.Builder().build();Request request = new Request.Builder(). url("https://github.com/cozing"). build();Call call = client.newCall(request);try { //1.同步请求调用的方法是call.execute(),内部采用的是线程阻塞(一直等待直到线程返回结果)方式直接将结果返回到Response Response response = call.execute(); //2.异步请求调用的方法是call.enqueue(Callback callback),该方法需要传入一个Callback等待结果回调的接口 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.w("cozing", "请求失败"); } @Override public void onResponse(Call call, Response response) throws IOException { Log.w("cozing", "请求成功"); } });} catch (IOException e) { e.printStackTrace();}
3.3 源码解读
(一)整体框架
(二)工作原理
2.1 OkHttpClient 构造
定义一个OkHttpClient,OkHttpClient构造函数及其配置如下。
mOkHttpClient = new OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .retryOnConnectionFailure(true) .connectTimeout(TIME_OUT, TimeUnit.SECONDS) .readTimeout(TIME_OUT, TimeUnit.SECONDS) .build();
了解OkHttpClient属性
final Dispatcher dispatcher;//调度器 final @Nullable Proxy proxy;//代理 final List protocols;//协议 final List connectionSpecs;//传输层版本和连接协议 final List interceptors;//拦截器 final List networkInterceptors;//网络拦截器 final EventListener.Factory eventListenerFactory; final ProxySelector proxySelector;//代理选择器 final CookieJar cookieJar;//cookie final @Nullable Cache cache;//cache 缓存 final @Nullable InternalCache internalCache;//内部缓存 final SocketFactory socketFactory;//socket 工厂 final @Nullable SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https final @Nullable CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名 final HostnameVerifier hostnameVerifier;//主机名字确认 final CertificatePinner certificatePinner;//证书链 final Authenticator proxyAuthenticator;//代理身份验证 final Authenticator authenticator;//本地省份验证 final ConnectionPool connectionPool;//链接池 复用连接 final Dns dns; //域名 final boolean followSslRedirects;//安全套接层重定向 final boolean followRedirects;//本地重定向 final boolean retryOnConnectionFailure;//重试连接失败 final int connectTimeout;//连接超时 final int readTimeout;//读取超时 final int writeTimeout;//写入超时
这里OkHttpClient的构造采用了建造者模式。
2.2 OkHttpClient 请求网络
String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string();}
定义一个请求Request,Request中包含客户请求的参数:url、method、headers、requestBody和tag,也采用了建造者模式。
实际请求网络的是Call接口,OkHttp实现了Call.Factory接口,其真正的实现类是RealCall,OkHttp将真正的请求交给了RealCall,RealCall实现了Call中方法完成请求。这里采用了简单工厂模式。
interface Factory { Call newCall(Request request); } ... @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); }
RealCall中主要方法有
- 同步请求:client.newCall(request).execute
- 异步请求:client.newCall(request).enqueue(常用)
异步请求 RealCall.enqueue()
RealCall.java @Override public void enqueue(Callback responseCallback) { //TODO 不能重复执行 synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); //TODO 交给 dispatcher调度器 进行调度 client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
- synchronized (this) 确保每个call只能被执行一次不能重复执行
- Dispatcher 调度器 将 Call 加入队列,并通过线程池执行 Call
利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。
Dispatcher的属性和方法
//TODO 同时能进行的最大请求数 private int maxRequests = 64; //TODO 同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]] //TODO 如 https://restapi.amap.com restapi.amap.com - host private int maxRequestsPerHost = 5; /** * Ready async calls in the order they'll be run. * TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除 * 异步等待队列 * */ private final Deque readyAsyncCalls = new ArrayDeque<>(); /** * Running asynchronous calls. Includes canceled calls that haven't finished yet. * TODO 正在进行的异步队列 */ private final Deque runningAsyncCalls = new ArrayDeque<>();
Dispatcher管理两个异步请求队列,可对多个并发网络请求进行处理。
Dispatcher.enqueue方法用于执行异步请求,实现如下
//TODO 执行异步请求 synchronized void enqueue(AsyncCall call) { //TODO 同时请求不能超过并发数(64,可配置调度器调整) //TODO okhttp会使用共享主机即 地址相同的会共享socket //TODO 同一个host最多允许5条线程通知执行请求 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //TODO 加入运行队列 并交给线程池执行 runningAsyncCalls.add(call); //TODO AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现 executorService().execute(call); } else { //TODO 加入等候队列 readyAsyncCalls.add(call); } }
可见Dispatcher将Call加入队列中(若同时请求数未超过最大值,则加入运行队列,放到线程池中执行;否则加入等待队列),然后通过线程池执行call。
executorService() 本质上是一个线程池执行方法,用于创建一个线程池
public synchronized ExecutorService executorService() { if (executorService == null) { //TODO 线程池的相关概念 需要理解 //TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列 executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }
- 在 ConnectionPool 线程池中执行请求 AsyncCall
异步请求中Call实际上为AsyncCall,继承自NamedRunnable
final class AsyncCall extends NamedRunnable
NamedRunnable
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(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute();}
因此 AsyncCall 其实就是一个 Runnable,线程池实际上就是执行了execute()。
AsyncCall的execute()
final class AsyncCall extends NamedRunnable { @Override protected void execute() { boolean signalledCallback = false; try { //TODO 责任链模式 //TODO 拦截器链 执行请求 Response response = getResponseWithInterceptorChain(); //回调结果 if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; 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 { //TODO 移除队列 client.dispatcher().finished(this); } } }
从上述代码可以看出真正执行请求的是getResponseWithInterceptorChain(); 然后通过回调将Response返回给用户。
- 通过拦截器链 RealInterceptorChain 通过 责任链模式 真正执行网络请求
真正的执行网络请求和返回响应结果:getResponseWithInterceptorChain()
//TODO 核心代码 开始真正的执行网络请求 Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. //TODO 责任链 List interceptors = new ArrayList<>(); //TODO 在配置okhttpClient 时设置的intercept 由用户自己设置 interceptors.addAll(client.interceptors()); //TODO 负责处理失败后的重试与重定向 interceptors.add(retryAndFollowUpInterceptor); //TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息 //TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。 interceptors.add(new BridgeInterceptor(client.cookieJar())); //TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应 //TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改) //TODO 可配置用户自己设置的缓存拦截器 interceptors.add(new CacheInterceptor(client.internalCache())); //TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络 interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { //TODO 配置okhttpClient 时设置的networkInterceptors //TODO 返回观察单个网络请求和响应的不可变拦截器列表。 interceptors.addAll(client.networkInterceptors()); } //TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据 //TODO 进行http请求报文的封装与请求报文的解析 interceptors.add(new CallServerInterceptor(forWebSocket)); //TODO 创建责任链 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); //TODO 执行责任链 return chain.proceed(originalRequest); }
一共执行6种拦截器,一种是用户自定义拦截器client.interceptors(),另外五种是OkHttp自带拦截器:
- RetryAndFollowUpInterceptor,重试那些失败或者redirect的请求。
- BridgeInterceptor,请求之前对响应头做了一些检查,并添加一些头,然后在请求之后对响应做一些处理(gzip解压or设置cookie)。
- CacheInterceptor,根据用户是否有设置cache,如果有的话,则从用户的cache中获取当前请求的缓存。
- ConnectInterceptor,复用连接池中的连接,如果没有就与服务器建立新的socket连接。
- CallServerInterceptor,负责发送请求和获取响应。
从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。
责任链模式是OkHttp中最核心的设计模式。
我们着重分析一下,okhttp的设计实现,如何通过责任链来进行传递返回数据的。上述代码中可以看出interceptors,是传递到了RealInterceptorChain该类实现了Interceptor.Chain,并且执行了chain.proceed(originalRequest)。核心代码就是chain.proceed() 通过该方法进行责任链的执行。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); calls++; //TODO 获取下一个拦截链,即链中的拦截器集合index+1 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); //TODO 执行当前的拦截器-如果在配置okhttpClient,时没有设置intercept默认是先执行:retryAndFollowUpInterceptor 拦截器 Interceptor interceptor = interceptors.get(index); //TODO 执行下一个拦截器 Response response = interceptor.intercept(next); return response; }
从上述代码,我们可以知道,获取下一个(index+1)RealInterceptorChain 责任链,然后 执行当前(index)责任链interceptors.get(index),最后返回下一个责任链的Response。
其实就是按责任链顺序递归执行了拦截器
这样设计的一个好处就是,责任链中每个拦截器都会执行chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。执行过程如下图:
CacheInterceptor 缓存拦截器就是很好的证明,我们来通过CacheInterceptor 缓存拦截器来进行分析,大家就会明白了。
CacheInterceptor 的实现如下:
首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。
@Override public Response intercept(Chain chain) throws IOException {//TODO 获取request对应缓存的Response 如果用户没有配置缓存拦截器 cacheCandidate == null Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; //TODO 执行响应缓存策略 long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //TODO 如果networkRequest == null 则说明不使用网络请求 Request networkRequest = strategy.networkRequest; //TODO 获取缓存中(CacheStrategy)的Response Response cacheResponse = strategy.cacheResponse; if (cache != null) { cache.trackResponse(strategy); } //TODO 缓存无效 关闭资源 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. //TODO networkRequest == null 不实用网路请求 且没有缓存 cacheResponse == null 返回失败 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(); } //TODO 不使用网络请求 且存在缓存 直接返回响应 // If we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } }
上述的代码,主要做了几件事:
如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest
如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清除cacheCandidate缓存。
如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。
上部分代码,其实就是没有网络的时候的处理。
那么下部分代码肯定是,有网络的时候处理
//TODO 执行下一个拦截器 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()); } } //TODO 网络请求 回来 更新缓存 // If we have a cache response too, then we're doing a conditional get. //TODO 如果存在缓存 更新 if (cacheResponse != null) { //TODO 304响应码 自从上次请求后,请求需要响应的内容未发生改变 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()); } } //TODO 缓存Response 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; }
下部分代码主要做了这几件事:
执行下一个拦截器,也就是请求网络
责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。
这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。
这也是okhttp设计的最优雅最核心的功能。
- 执行调度器完成方法,移除队列
AsyncCall的execute()中(见第3步)中finally 执行了client.dispatcher().finished(this); 通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。
private void finished(Deque calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { //TODO calls 移除队列 if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); //TODO 检查是否为异步请求,检查等候的队列 readyAsyncCalls,如果存在等候队列,则将等候队列加入执行队列 if (promoteCalls) promoteCalls(); //TODO 运行队列的数量 runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } //闲置调用 if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } private void promoteCalls() { //TODO 检查 运行队列 与 等待队列 if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. //TODO 将等待队列加入到运行队列中 for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); //TODO 相同host的请求没有达到最大,加入运行队列 if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
同步请求 RealCall.excute()
//TODO 同步执行请求 直接返回一个请求的结果 @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); //TODO 调用监听的开始方法 eventListener.callStart(this); try { //TODO 交给调度器去执行 client.dispatcher().executed(this); //TODO 获取请求的返回数据 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { //TODO 执行调度器的完成方法 移除队列 client.dispatcher().finished(this); } }
- synchronized (this) 避免重复执行
- Dispatcher 调度器 将 Call 加入同步执行队列
//TODO 调度器执行同步请求 synchronized void executed(RealCall call) { runningSyncCalls.add(call); }
- 通过拦截器链 RealInterceptorChain 通过 责任链模式 真正执行网络请求
getResponseWithInterceptorChain()最核心的代码,请求网络得到响应数据,返回给用户。 - 执行调度器完成方法,移除队列
client.dispatcher().finished(this); 执行调度器的完成方法 移除队列
2.3 OkHttp 执行流程总结
- OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
- RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过 getResponseWithInterceptorChain() 函数实现
- getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。
2.4 OkHttp 设计模式总结
- 建造者模式
创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使
得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。
OkHttp中HttpClient、Request构造便是通过建造者模式
OkHttpClient
mOkHttpClient = new OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .retryOnConnectionFailure(true) .connectTimeout(TIME_OUT, TimeUnit.SECONDS) .readTimeout(TIME_OUT, TimeUnit.SECONDS) .build();
Request
Request request = new Request.Builder() .url(url) .build();
- 简单工厂模式
okhttp 实现了Call.Factory接口
interface Factory { Call newCall(Request request); }
我们看一下okhttpClient 如何实现的Call接口,代码如下
@Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); }
可以看出 真正的请求交给了 RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。
- 责任链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
OkHttp责任链验证
(1)模拟拦截器接口
public interface Interceptor { String interceptor(Chain chain); interface Chain { String request(); String proceed(String request); }}
(2)实现具体拦截器
public class BridgeInterceptor implements Interceptor { @Override public String interceptor(Chain chain) { System.out.println("执行 BridgeInterceptor 拦截器之前代码"); String proceed = chain.proceed(chain.request()); System.out.println("执行 BridgeInterceptor 拦截器之后代码 得到最终数据:"+proceed); return proceed; }}public class RetryAndFollowInterceptor implements Interceptor { @Override public String interceptor(Chain chain) { System.out.println("执行 RetryAndFollowInterceptor 拦截器之前代码"); String proceed = chain.proceed(chain.request()); System.out.println("执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:" + proceed); return proceed; }}public class CacheInterceptor implements Interceptor { @Override public String interceptor(Chain chain) { System.out.println("执行 CacheInterceptor 最后一个拦截器 返回最终数据"); return "success"; }}
(3)实现拦截器链接口
public class RealInterceptorChain implements Interceptor.Chain { private List interceptors; private int index; private String request; public RealInterceptorChain(List interceptors, int index, String request) { this.interceptors = interceptors; this.index = index; this.request = request; } @Override public String request() { return request; } @Override public String proceed(String request) { if (index >= interceptors.size()) return null; //获取下一个责任链 RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request); // 执行当前的拦截器 Interceptor interceptor = interceptors.get(index); return interceptor.interceptor(next); }}
(4)测试及结果
List interceptors = new ArrayList<>(); interceptors.add(new BridgeInterceptor()); interceptors.add(new RetryAndFollowInterceptor()); interceptors.add(new CacheInterceptor()); RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request"); request.proceed("request");
打印出的log日志
执行 BridgeInterceptor 拦截器之前代码执行 RetryAndFollowInterceptor 拦截器之前代码执行 CacheInterceptor 最后一个拦截器 返回最终数据执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:success执行 BridgeInterceptor 拦截器之后代码 得到最终数据:success
这也是OkHttp的核心设计思想
(三)源码解读
3.1 重要的类
(1)Route路由:对地址Adress的一个封装类
RouteSelector路由选择器:在OKhttp中其实其作用也就是返回一个可用的Route对象
(2)Platform平台:用于针对不同平台适应性
(3)RealConnection连接:Okhttp正式发起网络请求所使用的对象
ConnectionPool连接池:管理HTTP和SPDY连接的重用,减少网络延迟。连接池是将已经创建好的连接保存在一个缓冲池中,当有请求来时,直接使用已经创建好的连接。
每次请求生成StreamAllocation对象请求链接时,首先要做的不是new 一个新的RealConnection对象,而是从链接池中获取已经存在的并且可以复用的RealConnection,如果找不到可用的链接,则才new 一个新的RealConnection,并将新的连接放入连接池,等待其他请求复用
(4)Call请求(Request\Response):代表实际的http请求,它是连接Request和response的桥梁。由于重写,重定向,跟进和重试,你简单的请求Call可能产生多个请求Request和响应Response。OkHttp会使用Call来模化满足请求的任务,然而中间的请求和响应是必要的(重定向处理和IP出错)
Call执行有两种方式:
Synchronous:线程会阻塞直到响应可读。
Asynchronous:在一个线程中入队请求,当你的响应可读时在另外一个线程获取回调。
线程中的请求取消、失败、未完成,写请求主体和读响应主体代码会遇到IOException
(5)Dispatchar调度器:Dispatcher是okhttp3的任务调度核心类,负责管理同步和异步的请求,管理每一个请求任务的请求状态,并且其内部维护了一个线程池用于执行相应的请求,Dispatcher实现框架:
Dispatcher维护了三个队列
private final Deque readyAsyncCalls = new ArrayDeque<>();//等待执行的异步队列 private final Deque runningAsyncCalls = new ArrayDeque<>();//正在执行的异步队列 private final Deque runningSyncCalls = new ArrayDeque<>();//同步队列一个线程池 executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
5.1)Deque是一个双向队列接口,Deque接口具有丰富的抽象数据形式,它支持从队列两端点检索和插入元素
5.2)当需要执行的线程大于所能承受的最大范围时,就把未能及时执行的任务保存在readyAsyncCalls队列中。当线程池有空余线程可以执行时,会调用promoteCall()方法把等待队列readyAsyncCalls中的任务放到线程池执行,并把任务转移到runningAsyncCalls队列中
(6)Interceptor拦截器:拦截器是一个强大的机制,它可以监控,重写和重试Calls,OkHttp自带拦截器有5种:
1、RetryAndFollowUpInterceptor,重试那些失败或者redirect的请求。
2、BridgeInterceptor,请求之前对响应头做了一些检查,并添加一些头,然后在请求之后对响应做一些处理(gzip解压or设置cookie)。
3、CacheInterceptor,根据用户是否有设置cache,如果有的话,则从用户的cache中获取当前请求的缓存。
4、ConnectInterceptor,复用连接池中的连接,如果没有就与服务器建立新的socket连接。
5、CallServerInterceptor,负责发送请求和获取响应。
(7)Cache缓存:响应缓存
(8)OkHttpClient客户端:OkHttpClient是okhttp3框架的客户端,用于发送http请求(Requests)和读取读取交易返回数据(Responses)
3.2 请求流程
(1)创建OkHttpClient
创建OkHttpClient对象,OkHttpClient是okhttp3框架的客户端,用于发送http请求(Requests)和读取读取交易返回数据(Responses)。
官方建议使用单例创建OkHttpClient,即一个进程中只创建一次即可,以后的每次交易都使用该实例发送交易。这是因为OkHttpClient拥有自己的连接池和线程池,这些连接池和线程池可以重复使用,这样做利于减少延迟和节省内存。
//采用构建者模式OkHttpClient client = new OkHttpClient.Builder().build();
自定义Builder内部的每一个参数属性
(2)创建Call对象,并发起同步/异步请求
一个Call对象表示一次请求,Call其实是一个接口对象,它的具体实现类是RealCall
//采用构建者模式OkHttpClient client = new OkHttpClient.Builder().build();Request request = new Request.Builder(). url("https://github.com/cozing"). build();Call call = client.newCall(request);
2.1)传入Request对象,表示客户请求的参数
public final class Request { final HttpUrl url; final String method; final Headers headers; final @Nullable RequestBody body; final Object tag;
2.2)Call创建,实际创建RealCall对象
@Override public Call newCall(Request request) { return new RealCall(this, request, false /* for web socket */);}RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { final EventListener.Factory eventListenerFactory = client.eventListenerFactory(); this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // TODO(jwilson): this is unsafe publication and not threadsafe. this.eventListener = eventListenerFactory.create(this);}
okhttp3中提供了两种交易方式:一种是同步请求,第二种是异步请求。同步请求调用call.execute()方法,异步请求调用call.enqueue(Callback callback)方法
(3)Dispatcher 调度器对同步/异步请求进行处理
3.1)同步请求
call.execute()【RealCall.java】
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try {//1.调用调度器dispatcher的executed方法 client.dispatcher().executed(this);//2.通过拦截器对请求数据与拦截数据进行处理 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally {//3.调用调度器dispatcher的finish方法 client.dispatcher().finished(this); }}
1.调用调度器dispatcher的executed方法,将请求的RealCall放入runningSyncCalls队列中【Dispatcher.java】
public final class Dispatcher {private final Deque runningSyncCalls = new ArrayDeque<>();synchronized void executed(RealCall call) { runningSyncCalls.add(call);}
3.2)异步请求
call.enqueue(Callback callback)实现方式【RealCall.java】
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace();//调用调度器dispatcher的enqueue方法 client.dispatcher().enqueue(new AsyncCall(responseCallback));}
调度器dispatcher的equeue(new AsyncCall(responseCallback))方法【dispatcher.java】
private int maxRequests = 64;private int maxRequestsPerHost = 5;private final Deque readyAsyncCalls = new ArrayDeque<>();private final Deque runningAsyncCalls = new ArrayDeque<>();synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //如果当前运行中的请求数小于设定的最大请求数64,且当前运行中同一主机的请求数小于设定的最大请求数5 runningAsyncCalls.add(call); //将本次请求call加入正在执行异步队列 executorService().execute(call); //执行请求call } else { readyAsyncCalls.add(call); //将本次请求call加入等待执行异步队列,等到有空闲的请求线程时,会从该队列中取出请求并执行 }}
执行请求executorService().execute(call)【dispatcher.java】
调度器Dispatcher内部维护了一个ThreadPoolExecutor线程池executorService(),并直接将call对象传入线程池执行。这里的Call对象的实现对象是AsyncCall是Call的内部类,继承自NamedRunnable,用来开启一个线程。
当网络请求线程池执行该线程的run()方法时,会调用AsyncCall的execute()的方法,最后在execute()方法内部调用了和同步请求方法一样的getResponseWithInterceptorChain()。
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService;}
AsyncCall
final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } String host() { return originalRequest.url().host(); } Request request() { return originalRequest; } RealCall get() { return RealCall.this; } @Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; 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 { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } }}
(4)getResponseWithInterceptorChain()/拦截器链
【okhttp3中的精髓设计之一】
这个方法是通过拦截器链对请求数据和返回数据进行处理,内部采用责任链模式,将每一个拦截器对应负责的处理任务进行严格分配,最后将交易结果返回并回调到暴露给调用者的接口上。
依次添加了用户自定义的interceptor、retryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、 networkInterceptors、CallServerInterceptor,并将这些拦截器传递给了这个RealInterceptorChain。拦截器之所以可以依次调用,并最终再从后先前返回Response。
getResponseWithInterceptorChain()【RealCall.java】
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors());//用户自定义拦截器 interceptors.add(retryAndFollowUpInterceptor); //重试和重定向拦截器 interceptors.add(new BridgeInterceptor(client.cookieJar()));//负责添加交易请求头 interceptors.add(new CacheInterceptor(client.internalCache()));//缓存拦截器 interceptors.add(new ConnectInterceptor(client)); //网络连接拦截器 if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); //负责发送网络请求和读取网络响应 Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest);}
调用拦截器具体处理类RealInterceptorChain的proceed()方法来实现拦截:
执行当前拦截器的Intercept方法,并调用下一个(index+1)拦截器。下一个(index+1)拦截器的调用依赖于当前拦截器的Intercept方法中,对RealInterceptorChain的proceed方法的调用
【XXInterceptor】
return realChain.proceed(request, streamAllocation, httpCodec, connection);
当前拦截器的Response依赖于下一个拦截器的Intercept的Response。因此,就会沿着这条拦截器链依次调用每一个拦截器,当执行到最后一个拦截器之后,就会沿着相反的方向依次返回Response,最终得到我们需要的“终极版”Response。
【RealInterceptorChain】
@Override 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 { if (index >= interceptors.size()) throw new AssertionError(); calls++; // If we already have a stream, confirm that the incoming request will use it. if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must retain the same host and port"); } // If we already have a stream, confirm that this is the only call to chain.proceed(). if (this.httpCodec != null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must call proceed() exactly once"); } // 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); // Confirm that the next interceptor made its required call to chain.proceed(). if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } return response;}
(5)client.dispatcher().finished(this);
将本次请求从队列中移除。若是异步请求,则在一个异步请求结束后,将一个符合条件的异步请求从等待运行队列中放入运行队列中,并开始执行
private void finished(Deque 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; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); }}private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. }}
3.3 同步与异步(线程池)的实现
3.4 拦截器
(一)处理网络响应的拦截器机制
okHttp处理网络响应时采用的是拦截器机制,对interceptors依此调用。
okHttp自带的拦截器包括重连、组装请求头部、读/写缓存、建立socket连接、向服务器发送请求/接收响应拦截器。
用户可添加自定义Interceptor,包括应用拦截器和网络拦截器
- 调用OkHttpClient.Builder的addInterceptor()可以添加应用拦截器,只会被调用一次,可以处理网络请求回来的最终Response
- 调用addNetworkInterceptor()可以添加network拦截器,处理所有的网络响应(一次请求如果发生了redirect ,那么这个拦截器的逻辑可能会被调用两次)
public class OkHttpClient implements Cloneable, Call.Factory { final List interceptors; final List networkInterceptors; ...... }
(二)Interceptor解析
拦截器链通过责任链的模式,将网络请求过程中的职责功能都分割开,分别用不同的拦截器来完成失败重连、缓存处理、网络连接等问题。而且用户还可以添加自定义的拦截器,非常灵活,满足面向对象的开闭原则。
(1)RetryAndFollowUpInterceptor
该拦截器主要的作用负责失败自动重连和必要的重定向。当一个请求由于各种原因失败了,如果是路由或者连接异常,则尝试恢复,否则,根据响应码(ResponseCode),重定向方法会对Request进行再处理以得到新的Request,然后沿着拦截器链继续新的Request。当然,如果responseCode是200的话,这些过程就结束了。
(2)BridgeInterceptor
BridgeInterceptor的主要作用就是为请求(request before)添加请求头,为响应(Response Before)添加响应头。
负责将用户的Request转换成一个实际的网络请求Request,再调用下一个拦截器获取Response,然后将Response转换成用户的Response。
(3)CacheInterceptor
负责控制缓存,缓存的逻辑就在这里面
okHttp缓冲机制:
(3.1)HTTP缓冲机制
HTTP缓存有多种规则,根据是否需要重新向服务器发起请求来分类,我将其分为两大类(强制缓存,对比缓存)
(3.1.1)强制缓存
从上文我们得知,强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据,那么浏览器是如何判断缓存数据是否失效呢?
我们知道,在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中。
对于强制缓存来说,响应header中会有两个字段来标明失效规则(Expires/Cache-Control)
- Expires(失效)
Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。
不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。另一个问题是,到期时间是由服务端生成的,但是客户端时间可能跟服务端时间有误差,这就会导致缓存命中的误差。所以HTTP 1.1 的版本,使用Cache-Control替代。 - Cache-Control
Cache-Control 是最重要的规则。常见的取值有private、public、no-cache、max-age,no-store,默认为private。
private:客户端可以缓存
public:客户端和代理服务器都可缓存(前端的同学,可以认为public和private是一样的)
max-age=xxx:缓存的内容将在 xxx 秒后失效(若在xxx秒内再次请求这条数据,都会直接获取缓存数据库中的数据,直接使用。)
no-cache:需要使用对比缓存来验证缓存数据
no-store:所有内容都不会缓存,强制缓存,对比缓存都不会触发
(3.1.2)对比缓存
对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。
浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。
在对比缓存生效时,状态码为304,并且报文大小和请求时间大大减少。
原因是,服务端在进行标识比较后,只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端。对于对比缓存来说,缓存标识的传递是我们着重需要理解的,它在请求header和响应header间进行传递,一共分为两种标识传递:
- Last-Modified / If-Modified-Since
- Last-Modified:
服务器在响应请求时,告诉浏览器资源的最后修改时间。
- If-Modified-Since:
再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。
若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200;
若资源的最后修改时间小于或等于If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
- Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since)
- Etag:
服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。
- If-None-Match:
再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。
服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,
不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;
相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
对于比较缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互。
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则。
(3.2)重要的类
1、缓存Cache
Cache来自OkHttpClient
Cache中采用了DiskLruCache,以Request的URL的md5为key,相应Response为value。此外Cache中还通过外观模式对外提供了InternalCache接口变量,用于调用Cache中的方法,也满足面向对象的接口隔离原则和依赖倒置原则等。
DiskLruCache和LruCache内部都是使用了LinkedHashMap去实现缓存算法的,只不过前者针对的是将缓存存在硬盘(/sdcard/Android/data//cache),而后者是直接将缓存存在内存;
2、缓存策略CacheStrategy
CacheStrategy的内部工厂类Factory中有一个getCandidate方法,会根据实际的请求生成对应的CacheStrategy类返回,是个典型的简单工厂模式。其内部维护一个request和response,通过指定request和response来告诉CacheInterceptor是使用缓存还是使用网络请求,亦或两者同时使用。
(3.2)缓存框架
(3.3)源码分析
@Override public Response intercept(Chain chain) throws IOException { // 1.如果设置缓存并且当前request有缓存,则从缓存Cache中获取当前请求request的缓存response Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); // 2.传入的请求request和获取的缓存response通过缓存策略对象CacheStragy的工厂类get方法根据一些规则获取缓存策略CacheStrategy(这里的规则根据请求的request和缓存的Response的header头部信息生成的,比如是否有noCache标志位,是否是immutable不可变,缓存是否过期等等) CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); // 3.生成的CacheStrategy有2个变量,networkRequest和cacheRequest,如果networkRequest为Null表示不进行网络请求,如果cacheResponse为null,则表示没有有效缓存 Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; // 4.缓存不可用,关闭 if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // 5.如果networkRequest和cacheResponse都为Null,则表示不请求网络且缓存为null,返回504,请求失败 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(); } // 6.如果不请求网络,但存在缓存,则不请求网络,直接返回缓存,结束,不执行下一个拦截器 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } // 7.否则,请求网络,并调用下一个拦截器链,将请求转发到下一个拦截器 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()); } }//8.请求网络,并且网络请求返回HTTP_NOT_MODIFIED,说明缓存有效,则合并网络响应和缓存结果,同时更新缓存 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()); } } //9.若没有缓存,则写入缓存 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); } } return response;}
(3.4)流程
- 如果本地没有缓存,直接发送网络请求;
cacheResponse == null - 如果当前请求是Https,而缓存没有TLS握手,则重新发起网络请求;
request.isHttps() && cacheResponse.handshake() == null - 如果当前的缓存策略是不可缓存,直接发送网络请求;
!isCacheable(cacheResponse, request) - 请求头no-cache或者请求头包含If-Modified-Since或者If-None-Match,则需要服务器验证本地缓存是不是还能继续使用,直接网络请求;
requestCaching.noCache() || hasConditions(request) - 可缓存,并且ageMillis + minFreshMillis < freshMillis + maxStaleMillis(意味着虽过期,但可用,只是会在响应头添加warning),则使用缓存;
- 缓存已经过期,添加请求头:If-Modified-Since或者If-None-Match,进行网络请求;
(4)ConnectInterceptor(核心,连接池)
okhttp的一大特点就是通过连接池来减小响应延迟。如果连接池中没有可用的连接,则会与服务器建立连接,并将socket的io封装到HttpStream(发送请求和接收response)中
(4.1)重要的类
HttpCodec(Stream):数据交换的流,对请求的编码以及对响应数据的解码
(Stream:基于Connection的逻辑Http请求/响应对)
RealConnecton(Collection):Connection实现类,主要实现连接的建立等工作;
Http中Stream和Collection关系:
Http1(Http1.0)1:1一个连接只能被一个请求流使用
Http2(Http1.1)1:n一个连接可被多个请求流同时使用,且keep-alive机制保证连接使用完不关闭,当下一次请求与连接的Host相同时,连接可以直接使用,不用再次创建
StreamAllocation(流分配):会通过ConnectPool获取或者创建一个RealConnection来得到一个连接到Server的Connection连接,同时会生成一个HttpCodec用于下一个CallServerInterceptor,以完成最终的请求;
RouteDataBase:这是一个关于路由信息的白名单和黑名单类,处于黑名单的路由信息会被避免不必要的尝试;
ConnectionPool:连接池,实现连接的复用;
(4.2)连接流程框架
(4.3)连接流程源码
创建一个StreamAllocation并且分配一个Connection和HttpCodec,为最终的请求做准备
@Override 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, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection);}
1、 HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
newStream.java
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) { RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);}
findHealthyConnection.java找到一个可用连接
/** * 找可用连接,如果连接不可用,便会一直持续查找 */private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException { while (true) { RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled); // 如果是新的连接,则直接返回. synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } } //判断连接池中连接是否可用,如果不可用,则释放该连接并从连接池中移除,并继续寻找可用连接 if (!candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; } return candidate; }}
findConnection.java
/** * 返回连接到Host的新流.首选已存在的流,再选连接池的流,最后创建一个新流 */private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) throws IOException { Route selectedRoute; synchronized (connectionPool) { // 1.首选已存在的流 RealConnection allocatedConnection = this.connection;// 2.从连接池中取得连接(以URL为Key) Internal.instance.get(connectionPool, address, this, null); selectedRoute = route; } // 3.如果当前路由为空,选择下一条路由. if (selectedRoute == null) { selectedRoute = routeSelector.next(); } synchronized (connectionPool) { //4.根据IP地址和Route从连接池进行第二次查找 Internal.instance.get(connectionPool, address, this, selectedRoute); //5.根据route创建一个RealConnection route = selectedRoute; result = new RealConnection(connectionPool, selectedRoute); acquire(result); } // 6.建立Socket连接 result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled); routeDatabase().connected(result.route()); synchronized (connectionPool) { // 6.将生成的connection放入连接池 Internal.instance.put(connectionPool, result); // 7.多路复用 if (result.isMultiplexed()) { socket = Internal.instance.deduplicate(connectionPool, address, this); result = connection; }private void connectSocket(int connectTimeout, int readTimeout) throws IOException { Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout); source = Okio.buffer(Okio.source(rawSocket)); sink = Okio.buffer(Okio.sink(rawSocket));}
2、RealConnection connection = streamAllocation.connection();
(4.4)连接流程步骤
1.框架使用URL和配置好的OkHttpClient创建一个address。此地址指定我们将如何连接到网络服务器。
2.框架通过address从连接池中取回一个连接。
3.如果没有在池中找到连接,ok会选择一个route尝试连接。这通常意味着使用一个DNS请求, 以获取服务器的IP地址。如果需要,ok还会选择一个TLS版本和代理服务器。
4.如果获取到一个新的route,它会与服务器建立一个直接的socket连接、使用TLS安全通道(基于HTTP代理的HTTPS),或直接TLS连接。它的TLS握手是必要的。
5.开始发送HTTP请求并读取响应。
如果有连接出现问题,OkHttp将选择另一条route,然后再试一次。这样的好处是当服务器地址的一个子集不可达时,OkHttp能够自动恢复。而且当连接池过期或者TLS版本不受支持时,这种方式非常有用。
一旦响应已经被接收到,该连接将被返回到池中,以便它可以在将来的请求中被重用。连接在池中闲置一段时间后,它会被赶出。
(5)CallServerInterceptor
CallServerInterceptor的intercept()方法里 负责发送请求和获取响应,实际上都是由HttpStream类去完成具体的工作。
一个socket连接用来发送HTTP/1.1消息,这个类严格按照以下生命周期:
1、 writeRequestHeaders()发送request header
httpCodec.writeRequestHeaders(request);
2、打开一个sink来写request body,然后关闭sink
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
3、readResponseHeaders()读取response头部
responseBuilder = httpCodec.readResponseHeaders(true);
4、打开一个source来读取response body,然后关闭source
3.5 缓存策略
3.6 连接池
3.7 重连机制
3.8 Gzip的使用方式
3.9 安全性
3.10 平台适应性
四.Volley
4.1 简介
Volley的意思是"群发"、“并发”。
Volley是Google在2013年发布的一个Android网络通信库,使网络通信更快、更简单、更健壮。适用于数据不大但频繁的网络通信。
在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据。Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码。于是,一些Android网络通信框架也就应运而生,比如说AsyncHttpClient,它把HTTP所有的通信细节全部封装在了内部,我们只需要简单调用几行代码就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上显示网络图片的操作变得极度简单,开发者不用关心如何从网络上获取图片,也不用关心开启线程、回收图片资源等细节,Universal-Image-Loader已经把一切都做好了。
Android开发团队也是意识到了有必要将HTTP的通信操作再进行简单化,于是在2013年Google
I/O大会上推出了一个新的网络通信框架——Volley。Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。除了简单易用之外,Volley在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。
4.2 特点
优点
- 通信更快,更简单
- Get、Post网络请求及网络图像的高效率异步处理请求
- 对网络请求进行排序与优先级处理
- 网络请求的缓存
- 多级别取消请求(对于多个网络请求可进行取消)
- 和Activity生命周期的联动(Activity生命周期结束时撤销后台所有网络请求)
缺点
- 不适合进行较大文件上传和下载
Volley的网络请求线程池默认大小为4。意味着可以并发进行4个请求,大于4个,会排在队列中。如果所有的网络线程都被上传文件的任务占满了,你的网络请求只有在文件上传完毕后才能得到执行。就会使其他网络请求很慢。
Request#getBody() 方法返回byte[]类型,作为 Http.POST 和 Http.PUT body 中的数据。这就意味着需要把用 http 传输的数据一股脑读取到内存中。如果文件过大,内存占用就很高,很容易oom。 - 只支持HTTP请求
4.3 使用
4.3.1 Volley的get和post请求方式的使用
本质上是对Android原生Get和Post请求进行二次封装,并进行性能、效率的优化,在使用Get与Post请求方法前,我们需要确定请求接口数据类型(所请求数据返回的类型),Volley自带了三种返回类型:
- StringRequest:请求数据类型不确定(涵盖后两种)
- JsonObjectRequest:请求数据类型为JsonObject
- JsonArrayRequest:请求数据类型为JsonArray
(1)网络请求之Get方式请求数据
1、使用Get方式请求数据返回StringRequest对象
当想返回String类型的请求结果数据或者不清楚返回什么类型时可以用StringRequest对象。
下面使用Get请求方式返回一个String类型的手机归属地信息。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); volleyGet(); } /** * new StringRequest(int method,String url,Listener listener,ErrorListener errorListener) * method:请求方式,Get请求为Method.GET,Post请求为Method.POST * url:请求地址 * listener:请求成功后的回调 * errorListener:请求失败的回调 */ private void volleyGet() { String url = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=15850781443"; StringRequest request = new StringRequest(Method.GET, url, new Listener() { @Override public void onResponse(String s) {//s为请求返回的字符串数据 Toast.makeText(MainActivity.this,s,Toast.LENGTH_LONG).show(); } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show(); } }); //设置请求的Tag标签,可以在全局请求队列中通过Tag标签进行请求的查找 request.setTag("testGet"); //将请求加入全局队列中 MyApplication.getHttpQueues().add(request); }}
2、使用Get方式请求数据返回JsonObjectRequest对象
由于我们知道这个请求返回的是Json格式的数据,所以我们可以直接使用JsonObjectRequest作为请求返回结果对象。
下面使用Get请求方式返回一个Json格式的IP地址信息
/** * new JsonObjectRequest(int method,String url,JsonObject jsonObject,Listener listener,ErrorListener errorListener) * method:请求方式,Get请求为Method.GET,Post请求为Method.POST * url:请求地址 * JsonObject:Json格式的请求参数。如果使用的是Get请求方式,请求参数已经包含在url中,所以可以将此参数置为null * listener:请求成功后的回调 * errorListener:请求失败的回调 */private void volleyGet() { String url = "http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=218.4.255.255"; JsonObjectRequest request = new JsonObjectRequest(Method.GET, url, null, new Listener() { @Override public void onResponse(JSONObject jsonObject) {//jsonObject为请求返回的Json格式数据 Toast.makeText(MainActivity.this,jsonObject.toString(),Toast.LENGTH_LONG).show(); } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show(); } }); //设置请求的Tag标签,可以在全局请求队列中通过Tag标签进行请求的查找 request.setTag("testGet"); //将请求加入全局队列中 MyApplication.getHttpQueues().add(request);}
返回JsonArrayRequest对象的方法与JsonObjectRequest比较类似。
运行结果
(2)网络请求之Post方式请求数据
1、使用Post方式请求数据返回StringRequest对象
使用Post方式需要手动传递请求参数,可以重写Request类的getParams()方法将请求参数名和参数值放入Map中进行传递。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); volleyPost(); } /** * 使用Post方式返回String类型的请求结果数据 * * new StringRequest(int method,String url,Listener listener,ErrorListener errorListener) * method:请求方式,Get请求为Method.GET,Post请求为Method.POST * url:请求地址 * listener:请求成功后的回调 * errorListener:请求失败的回调 */ private void volleyPost() { String url = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm"; StringRequest request = new StringRequest(Method.POST, url, new Listener() { @Override public void onResponse(String s) {//s为请求返回的字符串数据 Toast.makeText(MainActivity.this,s,Toast.LENGTH_LONG).show(); } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show(); } }){ @Override protected Map getParams() throws AuthFailureError { Map map = new HashMap<>(); //将请求参数名与参数值放入map中 map.put("tel","15850781443"); return map; } } ; //设置请求的Tag标签,可以在全局请求队列中通过Tag标签进行请求的查找 request.setTag("testPost"); //将请求加入全局队列中 MyApplication.getHttpQueues().add(request); }}
2、使用Post方式请求数据返回JsonObject对象
/** * 使用Post方式返回JsonObject类型的请求结果数据 * * new JsonObjectRequest(int method,String url,JsonObject jsonObject,Listener listener,ErrorListener errorListener) * method:请求方式,Get请求为Method.GET,Post请求为Method.POST * url:请求地址 * JsonObject:Json格式的请求参数。如果使用的是Get请求方式,请求参数已经包含在url中,所以可以将此参数置为null;如果用Post方式,则可以将请求参数直接放在JsonObject进行传递 * listener:请求成功后的回调 * errorListener:请求失败的回调 */private void volleyPost() { String url = "http://www.kuaidi100.com/query"; Map map = new HashMap<>(); map.put("type","yuantong"); map.put("postid","229728279823"); //将map转化为JSONObject对象 JSONObject jsonObject = new JSONObject(map); JsonObjectRequest request = new JsonObjectRequest(Method.POST, url, jsonObject, new Listener() { @Override public void onResponse(JSONObject jsonObject) {//jsonObject为请求返回的Json格式数据 Toast.makeText(MainActivity.this,jsonObject.toString(),Toast.LENGTH_LONG).show(); } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show(); } }); //设置请求的Tag标签,可以在全局请求队列中通过Tag标签进行请求的查找 request.setTag("testPost"); //将请求加入全局队列中 MyApplication.getHttpQueues().add(request);}
4.3.2 Volley的网络请求队列建立和取消队列请求
使用Volley需要建立一个全局的请求队列,这样我们就可以将一个请求加入到这个全局队列中,并可以管理整个APP的所有请求,包括取消一个或所有的请求。
(1)建立请求队列(使用Singleton模式)
如果您的应用程序不断使用网络,可能最有效的方法是设置一个可以延长应用程序生命周期的RequestQueue实例。
要达到这种目标,有各种方式可以实现。 推荐的方法是实现封装RequestQueue和其他Volley功能的单例类。 另一种方法是在Application.onCreate()中继承Application并设置RequestQueue, 但这种做法是不鼓励的, 静态单例可以以更模块化的方式提供相同的功能。
一个关键的概念是RequestQueue必须用Application context而不是Activity context来实例化。 这样可以确保RequestQueue在您的应用程序生命周期中持续存在,而不是每次重新创建Activity时重新创建(例如,当用户旋转设备时)。
提供RequestQueue和ImageLoader功能的单例类的例子:
public class MySingleton { private static MySingleton mInstance; private RequestQueue mRequestQueue;//全局请求队列 private ImageLoader mImageLoader;//全局图片加载器 private static Context mCtx;//这里的上下文为Application上下文,确保请求队列在应用程序生命周期持续存在 private MySingleton(Context context) { mCtx = context; mRequestQueue = getRequestQueue(); mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() { private final LruCache cache = new LruCache(20); @Override public Bitmap getBitmap(String url) { return cache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { cache.put(url, bitmap); } }); }//获取请求队列实例 public static synchronized MySingleton getInstance(Context context) { if (mInstance == null) { mInstance = new MySingleton(context); } return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null) { // getApplicationContext() is key, it keeps you from leaking the Activity or BroadcastReceiver if someone passes one in.防止内存泄露 mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); } return mRequestQueue; } public void addToRequestQueue(Request req) { getRequestQueue().add(req); } public ImageLoader getImageLoader() { return mImageLoader; }}
使用RequestQueue
// Get a RequestQueueRequestQueue queue = MySingleton.getInstance(this.getApplicationContext()). getRequestQueue();// ...// Add a request (in this example, called stringRequest) to your RequestQueue.MySingleton.getInstance(this).addToRequestQueue(stringRequest);
(2)取消网络请求
2.1 取消某个request操作
@Override public void onStop() { for (Request <?> req : mRequestQueue) { req.cancel(); } }
2.2 取消请求队列中所有请求
@Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); mRequestQueue.cancelAll(this); }
2.3 根据请求标记的Tag终止某个请求
1、给请求添加标签tag
request.setTag("My Tag");mRequestQueue.add(request);
2、取消所有指定标签tag的请求,这样只会取消指定tag的请求,而其他请求不受影响
mRequestQueue.cancelAll("My Tag");
用于Volley与Activity生命周期的联动。
4.3.3 Volley与Activity生命周期的联动
Activity里面启动了网络请求,而在这个网络请求还没返回结果的时候,Activity被结束了,此时如果继续使用其中的Context等,除了无辜的浪费CPU,电池,网络等资源,有可能还会导致程序crash,所以,我们需要处理这种一场情况。使用Volley的话,我们可以在Activity停止的时候,同时取消所有或部分未完成的网络请求。Volley里所有的请求结果会返回给主进程,如果在主进程里取消了某些请求,则这些请求将不会被返回给主线程。Volley支持多种request取消方式。
简单来说就是Volley中的请求是与Activity的生命周期进行关联。这样可以在Android销毁时关闭Volley的请求,防止请求在后台运行造成内存溢出等情况发生。与Activity生命周期进行联动时需要设置Tag标签,因为取消请求需要在请求队列中通过Tag标签进行查找,在Activity的onStop中执行取消请求的操作。
(1)为request请求设置标签(一般为activity标签)
String TAG = "VOLLEY_ACTIVITY"... //设置请求的Tag标签,可以在全局请求队列中通过Tag标签进行请求的查找 request.setTag(TAG); //将请求加入全局队列中 MyApplication.getHttpQueues().add(request);
(2)在Activity关闭时(onStop)取消请求队列中的请求
@Overrideprotected void onStop() { super.onStop(); //通过Tag标签取消请求队列中对应的全部请求 MyApplication.getHttpQueues().cancelAll("abcGet");}
4.3.4 Volley加载网络图片
(1)加载图片缓存功能:LruCache、ImageCache
(2)加载网络图片及监听:ImageRequest、ImageLoader、NetWorkImageView
(1)使用ImageRequest加载网络图片
public class MainActivity extends AppCompatActivity { private ImageView image; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); image = (ImageView) findViewById(R.id.image); loadImageByVolley(); } /** * 通过Volley加载网络图片 * * new ImageRequest(String url,Listener listener,int maxWidth,int maxHeight,Config decodeConfig,ErrorListener errorListener) * url:请求地址 * listener:请求成功后的回调 * maxWidth、maxHeight:设置图片的最大宽高,如果均设为0则表示按原尺寸显示 * decodeConfig:图片像素的储存方式。Config.RGB_565表示每个像素占2个字节,Config.ARGB_8888表示每个像素占4个字节等。 * errorListener:请求失败的回调 */ private void loadImageByVolley() { String url = "http://pic20.nipic.com/20120409/9188247_091601398179_2.jpg"; ImageRequest request = new ImageRequest( url, new Listener() { @Override public void onResponse(Bitmap bitmap) { image.setImageBitmap(bitmap); } }, 0, 0, Config.RGB_565, new ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { image.setImageResource(R.mipmap.ic_launcher); } }); //设置请求的Tag标签,可以在全局请求队列中通过Tag标签进行请求的查找 request.setTag("loadImage"); //通过Tag标签取消请求队列中对应的全部请求 MyApplication.getHttpQueues().add(request); }}
(2)使用ImageLoader加载及缓存网络图片
MainActivity.java
public class MainActivity extends AppCompatActivity { private ImageView image; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); image = (ImageView) findViewById(R.id.image); loadImageWithCache(); }/** * 通过ImageLoader加载及缓存网络图片 * * new ImageLoader(RequestQueue queue,ImageCache imageCache) * queue:请求队列 * imageCache:一个用于图片缓存的接口,一般需要传入它的实现类 * * getImageListener(ImageView view, int defaultImageResId, int errorImageResId) * view:ImageView对象 * defaultImageResId:默认的图片的资源Id * errorImageResId:网络图片加载失败时显示的图片的资源Id */ private void loadImageWithCache() { String url = "http://pic20.nipic.com/20120409/9188247_091601398179_2.jpg"; ImageLoader loader = new ImageLoader(MyApplication.getHttpQueues(), new BitmapCache()); ImageListener listener = loader.getImageListener(image,R.mipmap.ic_launcher,R.mipmap.ic_launcher); //加载及缓存网络图片 loader.get(url,listener); }}
BitmapCache.java
public class BitmapCache implements ImageLoader.ImageCache{ //LruCache是基于内存的缓存类 private LruCache lruCache; //LruCache的最大缓存大小 private int max = 10 * 1024 * 1024; public BitmapCache() { lruCache = new LruCache(max){ @Override //缓存图片的大小 protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } @Override public Bitmap getBitmap(String s) { return lruCache.get(s); } @Override public void putBitmap(String s, Bitmap bitmap) { lruCache.put(s,bitmap); }}
4.3.5 Volley的二次回调封装
自定义二次回调方法,方便全局使用统一的接口(而不需要在每次回调重新复写)。可控、可自定义定制需求;且方便,灵活。
1、自定义抽象接口,封装请求成功回调方法与请求失败回调方法
public abstract class VolleyInterface { //上下文 public static Context context; //请求成功 public static Response.Listener listener; //请求失败 public static Response.ErrorListener errorListener; public VolleyInterface(Context mContext, Response.Listener mListener, Response.ErrorListener mErrorListener){ this.context = mContext; this.listener = mListener; this.errorListener = mErrorListener; } public abstract void onMySuccess(String result); public abstract void onMyError(VolleyError error); //请求成功的监听方法 public Response.Listener loadingListener(){ //请求成功实例化 listener = new Response.Listener() { @Override public void onResponse(String s) { //这里可以添加成功提示 onMySuccess(s); } }; return listener; } //请求失败的监听方法 public Response.ErrorListener errorListener(){ //请求失败实例化 errorListener = new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { onMyError(volleyError); //这里可以添加成功提示 } }; return errorListener; }}
2、创建一个网络请求管理类VolleyRequest
public class VolleyRequest { public static StringRequest stringRequest; public static Context context; public static void RequestGet( Context mContext,//上下文 String url, String tag, VolleyInterface vif)//请求回调的接口,请求成功和失败 { //防止重复请求,删除所有相同标签的请求 MyApplication.getHttpQueue().cancelAll(tag); stringRequest = new StringRequest(Request.Method.GET,url, vif.loadingListener(),vif.errorListener()); stringRequest.setTag(tag); //请求添加到队列中 MyApplication.getHttpQueue().add(stringRequest); MyApplication.getHttpQueue().start(); } public static void RequestPost( Context mcontext,//上下文 String url, String tag, final Map params,//请求参数,通过Map传递 VolleyInterface vif)//回调接口 { //获取请求队列,cancelAll防止重复请求,删掉所有相同标签的请求 MyApplication.getHttpQueue().cancelAll(tag); stringRequest = new StringRequest(url,vif.loadingListener(), vif.errorListener()){ @Override protected Map getParams() throws AuthFailureError { return params; } }; stringRequest.setTag(tag); MyApplication.getHttpQueue().add(stringRequest); MyApplication.getHttpQueue().start(); }}
3、使用
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); volley_Get(); } public void volley_Get() { String url= "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=15850781443"; VolleyRequest.RequestGet(this, url, "abcGet", new VolleyInterface (this,VolleyInterface.listener,VolleyInterface.errorListener) { @Override public void onMySuccess(String result) { Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); } @Override public void onMyError(VolleyError error) { Toast.makeText(MainActivity.this, error.toString(), Toast.LENGTH_SHORT).show(); } }); }}
4.4 工作原理/源码分析
1、Volley原理图
Google官方Volley原理图
2、Volley工作原理
右上图可知,Volley的整个过程有三类线程在工作:
- 主线程:所有的请求结果都会被分发到主线程。
- 缓存线程(子线程):Volley默认开启1个cache Thread用于读取本地缓存/处理本地缓存队列(cacheQueue)。
- 网络线程(子线程):Volley默认开启4个network Thread用于处理网络请求(也可以自己创建更多网络请求)/处理网络请求队列(networkQueue)。
开发者无需关心子线程的内部实现流程,只需在主线程中将一个Request请求加入请求队列RequestQueue,然后在主线程中实现一个接口回调拿到请求结果Response即可。即Volley在主线程中投放一个任务,并且异步地在主线程中获得任务的结果。Volley简化开发者搭建了一个网络请求异步任务框架。
Volley初始化以后就创建了5个后台线程在处理请求。只要你没做处理,这5个线程一直在后台跑。为了节省资源,在同一个App中最好使用同一个单例Volley RequestQueue队列来处理所有请求,以免创建过多线程浪费资源。还有在推出这个应用时,应该调用 RequestQueue#stop方法来干掉所有Volley线程。如此才是使用Volley最优雅的方式
Volley请求处理是一个异步的过程:(Volley的工作从:将一个Request请求添加到RequestQueue请求队列中开始)
- 在主线程中默认先将请求提交给Cache Thread来处理,Cache Thread按照请求的优先级把Request添加到本地缓存队列CacheQueue中,
- 缓存分发器CacheDispatcher轮询本地是否已经缓存了这次请求的结果,
- 如果命中,则从缓存中读取数据并且解析。解析完的结果被分发到主线程中。因此Volley默认自动缓存网络请求结果。
- 如果没有命中,则将这次请求提交给Network Thread处理,Network Thread将请求添加到网络请求队列NetworkQueue中,
- 网络分发器NetworkDispatcher处理发送网络HTTP请求,获取响应结果并解析同时把结果写入缓存。解析完的结果被分发到主线程中。
五.Retrofit
5.1 简介
Retrofit是Square公司基于RESTful风格推出的网络请求框架封装,是基于OKHttp的网络请求框架的二次封装,其本质仍是OKHttp(通过HTTP进行网络请求)。
准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
网络请求的工作本质是OKHttp完成,Retrofit仅负责网络请求接口的封装。
Retrofit与OKHttp的关系如下图:
- App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作。
- 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析。
5.2 特点
- 基于OKHttp & 遵循Restful API设计风格
REST,即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化"。
它是一种互联网应用程序的API设计理念:
URL定位资源+用HTTP动词描述操作。
常用的HTTP动词有下面五个:
1.GET(SELECT):从服务器取出资源(一项或多项)。
2.POST(CREATE):在服务器新建一个资源。
3.PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
4.PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
5.DELETE(DELETE):从服务器删除资源。
REST简单来说就是url地址中只包含名词表示资源,使用http动词表示动作进行操作资源
- 功能强大
(1)支持同步 & 异步网络请求
(2)支持多种数据的解析 & 序列化格式(Gson、Json、XML、Protobuf)
(3)提供对Rxjava支持
RxJava是一个在Java VM上使用可观测的序列来组成异步的、基于事件的程序的库。
虽然,在Android中,我们可以使用AsyncTask来完成异步任务操作,但是当任务的梳理比较多的时候,我们要为每个任务定义一个AsyncTask就变得非常繁琐。
RxJava能帮助我们在实现异步执行的前提下保持代码的清晰。
它的原理就是创建一个Observable来完成异步任务,组合使用各种不同的链式操作,来实现各种复杂的操作,最终将任务的执行结果发射给Observer进行处理。
当然,RxJava不仅适用于Android,也适用于服务端等各种场景。
简单来说,RxJava2.0是非常好用的一个异步链式库,遵循观察者模式。
- 简介易用
(1)通过注解配置网络请求参数
(2)采用大量设计模式简化使用 - 可扩展性好
(1)功能模块高度封装
(2)解耦彻底:如:自定义Converters
Retrofit适用于任何网络请求的需求场景都应优先选择,特别是后台API遵循Restful API设计风格&项目中使用到Rxjava。
5.3 使用
实例:采用Post方法对 有道API 发送网络请求,
- 添加Retrofit库的依赖(包括:Retrofit开源库、OkHttp网络库、数据解析器继承、注册网络权限)
//依赖包导入dependencies { compile 'com.squareup.retrofit2:retrofit:2.0.2' compile 'com.squareup.okhttp3:okhttp:3.4.1' compile 'com.squareup.retrofit2:converter-gson:2.0.2' } //网络权限
- 创建 接收服务器返回数据(网络请求数据类型) 的类 与 用于描述网络请求 的接口
服务器返回数据的类应根据返回数据的格式和数据解析方式(Json、XML等)定义
根据 有道API 返回数据Json格式,创建 接收服务器返回数据 的类:
public class Translation { private String type; private int errorCode; private int elapsedTime; private List> translateResult; public String getType() { return type; } public void setType(String type) { this.type = type; } public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public int getElapsedTime() { return elapsedTime; } public void setElapsedTime(int elapsedTime) { this.elapsedTime = elapsedTime; } public List> getTranslateResult() { return translateResult; } public void setTranslateResult(List> translateResult) { this.translateResult = translateResult; } public static class TranslateResultBean { /** * src : merry me * tgt : 我快乐 */ public String src; public String tgt; public String getSrc() { return src; } public void setSrc(String src) { this.src = src; } public String getTgt() { return tgt; } public void setTgt(String tgt) { this.tgt = tgt; } }}
创建用于描述网络请求的接口,采用注解描述网络请求参数
Retrofit将 Http请求 抽象成 Java接口:采用 注解 描述网络请求参数 和配置网络请求参数
原理是采用 动态代理 动态 将该接口的注解“翻译”成一个 Http 请求,最后再执行 Http 请求
注:接口中的每个方法的参数都需要使用注解标注,否则会报错
注解类型总结
public interface PostRequest_Interface { @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=") @FormUrlEncoded Call getCall(@Field("i") String targetSentence); //采用@Post表示Post方法进行请求(传入部分url地址) // 采用@FormUrlEncoded注解的原因:API规定采用请求格式x-www-form-urlencoded,即表单形式 // 需要配合@Field 向服务器提交需要的字段 // getCall() = 接收网络请求数据的方法 // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类) // 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call}
- 创建 Retrofit 实例,设置数据解析器(Converter)与网络请求适配器(CallAdapter)
数据解析器(Converter):Retrofit支持多种数据解析方式,使用时需要在Gradle添加依赖
数据解析器 | Gradle依赖 |
---|---|
Gson | com.squareup.retrofit2:converter-gson:2.0.2 |
Jackson | com.squareup.retrofit2:converter-jackson:2.0.2 |
Simple XML | com.squareup.retrofit2:converter-simplexml:2.0.2 |
Protobuf | com.squareup.retrofit2:converter-protobuf:2.0.2 |
网络请求适配器(CallAdapter):
Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava
使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加
Retrofit 提供的 CallAdapter
使用时需要在Gradle添加依赖:
网络请求适配器 | Gradle依赖 |
---|---|
guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
rxjava | com.squareup.retrofit2:adapter-rxjava:2.0.2 |
- 创建 网络请求接口实例 并 配置网络请求参数
- 调用接口方法返回Call对象,并发送网络请求(异步 / 同步)(封装了 数据转换、线程切换的操作)
- 处理服务器返回的数据
通过response类的body()对返回的数据进行处理
public class PostRequest extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); request(); } public void request() { //步骤4:创建Retrofit对象 Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://fanyi.youdao.com/") // 设置 网络请求 Url .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖) //.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台 .build(); // 步骤5:创建 网络请求接口 的实例 PostRequest_Interface request = retrofit.create(PostRequest_Interface.class); //对 发送请求 进行封装(设置需要翻译的内容) Call call = request.getCall("I love you"); //步骤6:发送网络请求(异步) call.enqueue(new Callback() { //请求成功时回调 @Override public void onResponse(Call call, Response response) { // 步骤7:处理返回的数据结果:输出翻译的内容 System.out.println(response.body().getTranslateResult().get(0).get(0).getTgt()); } //请求失败时回调 @Override public void onFailure(Call call, Throwable throwable) { System.out.println("请求失败"); System.out.println(throwable.getMessage()); } }); }}
5.4 工作原理/源码分析
(5.4.1)工作流程
Retrofit工作流程与一般网络通信过程类似,它是通过使用大量的设计模式进行功能模块的解耦,使得网络通信过程进行得更加简单 & 流畅。
具体过程解释如下:
- 通过解析 网络请求接口的注解 配置 网络请求参数,创建请求(Build Request)
//用于描述网络请求的接口,采用注解描述网络请求参数和配置网络请求参数public interface GetRequest_Interface { @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car") Call getCall(); // @GET注解的作用:采用Get方法发送网络请求 // getCall() = 接收网络请求数据的方法 // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类) // 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call}
- 通过 动态代理 生成 网络请求对象/执行器(Call)
// 创建 网络请求接口 的实例 GetRequest_Interface request = retrofit.create(GetRequest_Interface.class); //对 发送请求 进行封装 Call call = request.getCall();
- 通过 网络请求适配器(CallAdapter) 将 网络请求对象 进行平台(Android、Rxjava、java8等)适配到具体的Call
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址 .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台 .build();
- 通过 网络请求执行器(Call) 发送网络请求
//发送网络请求(异步) call.enqueue(new Callback() { //请求成功时回调 @Override public void onResponse(Call call, Response response) { //请求处理,输出结果 response.body().show(); } //请求失败时候的回调 @Override public void onFailure(Call call, Throwable throwable) { System.out.println("连接失败"); } });// 发送网络请求(同步)Response response = call.execute();
- 通过 数据转换器(Converter) 解析服务器返回的数据
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址 .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台 .build();
- 通过 回调执行器(CallBackExcutor) 切换线程(子线程 ->>主线程),用户在主线程处理返回数据
//发送网络请求(异步) call.enqueue(new Callback() { //请求成功时回调 @Override public void onResponse(Call call, Response response) { // 对返回数据进行处理 response.body().show(); } //请求失败时候的回调 @Override public void onFailure(Call call, Throwable throwable) { System.out.println("连接失败"); } });// 发送网络请求(同步) Response response = call.execute(); // 对返回数据进行处理(通过response类的body()处理返回数据) response.body().show();
(5.4.2)重要角色
(5.4.3)源码分析
- 创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://fanyi.youdao.com/") .addConverterFactory(GsonConverterFactory.create()) .build();
Retrofit实例是使用建造者模式通过Builder类创建的。
建造者模式:将一个复杂对象的构建与表示分离,使用户在不知道对象的创建细节情况下就可以直接创建复杂的对象。
(1.1)new Retrofit
<-- Retrofit类 --> public final class Retrofit { private final Map serviceMethodCache = new LinkedHashMap<>(); // 网络请求配置对象(对网络请求接口中方法注解进行解析后得到的对象) // 作用:存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等 private final HttpUrl baseUrl; // 网络请求的url地址 private final okhttp3.Call.Factory callFactory; // 网络请求器的工厂 // 作用:生产网络请求器(Call) // Retrofit是默认使用okhttp private final List adapterFactories; // 网络请求适配器工厂的集合 // 作用:放置网络请求适配器工厂 // 网络请求适配器工厂作用:生产网络请求适配器(CallAdapter) // 下面会详细说明 private final List converterFactories; // 数据转换器工厂的集合 // 作用:放置数据转换器工厂 // 数据转换器工厂作用:生产数据转换器(converter) private final Executor callbackExecutor; // 回调方法执行器private final boolean validateEagerly; // 标志位// 作用:是否提前对业务接口中的注解进行验证转换的标志位<-- Retrofit类的构造函数 -->Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List converterFactories, List adapterFactories, Executor callbackExecutor, boolean validateEagerly) { this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = unmodifiableList(converterFactories); this.adapterFactories = unmodifiableList(adapterFactories); // unmodifiableList(list)近似于UnmodifiableList(list) // 作用:创建的新对象能够对list数据进行访问,但不可通过该对象对list集合中的元素进行修改 this.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly; ... // 仅贴出关键代码}
成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量,即配置好:
- serviceMethod:包含所有网络请求信息的对象
- baseUrl:网络请求的url地址
- callFactory:网络请求工厂,默认使用OkHttpCall
- adapterFactories:网络请求适配器工厂的集合,本质上配置了网络请求适配器工厂,默认是ExecutorCallAdapterFactory
- converterFactories:数据转换器工厂的集合,本质是配置了数据转换器工厂
- callbackExecutor:回调方法执行器,默认回调方法执行器作用:切换线程(子线程-主线程)
所谓xxxFactory、“xxx工厂”其实是设计模式中工厂模式的体现:将“类实例化的操作”与“使用对象的操作”分开,使得使用者不用知道具体参数就可以实例化出所需要的“产品”类。
这里介绍一下:CallAdapterFactory:该Factory生产的是CallAdapter。
CallAdapter
- 定义
网络请求执行器(Call)的适配器,Call在Retrofit里默认是OkHttpCall,在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory - 作用
将默认的网络请求执行器(OkHttpCall)转换成适合被不同平台来调用的网络请求执行器形式。如:一开始Retrofit只打算利用OkHttpCall通过ExecutorCallbackCall切换线程;但后来发现使用Rxjava更加方便(不需要Handler来切换线程)。想要实现Rxjava的情况,那就得使用RxJavaCallAdapterFactoryCallAdapter将OkHttpCall转换成Rxjava(Scheduler)。Retrofit还支持java8、Guava平台。
// 把response封装成rxjava的Observeble,然后进行流式操作Retrofit.Builder.addCallAdapterFactory(newRxJavaCallAdapterFactory().create()); // 关于RxJava的使用这里不作更多的展开
- 好处
用最小代价兼容更多平台,即能适配更多的使用场景
(1.2)Builder()
<-- Builder类-->public static final class Builder { private Platform platform; private okhttp3.Call.Factory callFactory; private HttpUrl baseUrl; private List converterFactories = new ArrayList<>(); private List adapterFactories = new ArrayList<>(); private Executor callbackExecutor; private boolean validateEagerly;// 从上面可以发现, Builder类的成员变量与Retrofit类的成员变量是对应的// 所以Retrofit类的成员变量基本上是通过Builder类进行配置// 开始看步骤1<-- 步骤1 -->// Builder的构造方法(无参) public Builder() { this(Platform.get());// 用this调用自己的有参构造方法public Builder(Platform platform) ->>步骤5(看完步骤2、3、4再看)// 并通过调用Platform.get()传入了Platform对象// 继续看Platform.get()方法 ->>步骤2// 记得最后继续看步骤5的Builder有参构造方法 }...}<-- 步骤2 -->class Platform { private static final Platform PLATFORM = findPlatform(); // 将findPlatform()赋给静态变量 static Platform get() { return PLATFORM; // 返回静态变量PLATFORM,即findPlatform() ->>步骤3 }<-- 步骤3 -->private static Platform findPlatform() { try { Class.forName("android.os.Build"); // Class.forName(xxx.xx.xx)的作用:要求JVM查找并加载指定的类(即JVM会执行该类的静态代码段) if (Build.VERSION.SDK_INT != 0) { return new Android(); // 此处表示:如果是Android平台,就创建并返回一个Android对象 ->>步骤4 } } catch (ClassNotFoundException ignored) { } try { // 支持Java平台 Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } try { // 支持iOS平台 Class.forName("org.robovm.apple.foundation.NSObject"); return new IOS(); } catch (ClassNotFoundException ignored) { }// 从上面看出:Retrofit2.0支持3个平台:Android平台、Java平台、IOS平台// 最后返回一个Platform对象(指定了Android平台)给Builder的有参构造方法public Builder(Platform platform) --> 步骤5// 说明Builder指定了运行平台为Android return new Platform(); }...}<-- 步骤4 -->// 用于接收服务器返回数据后进行线程切换在主线程显示结果static class Android extends Platform { @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) { return new ExecutorCallAdapterFactory(callbackExecutor); // 创建默认的网络请求适配器工厂 // 该默认工厂生产的 adapter 会使得Call在异步调用时在指定的 Executor 上执行回调 // 在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory // 采用了策略模式 } @Override public Executor defaultCallbackExecutor() { // 返回一个默认的回调方法执行器 // 该执行器作用:切换线程(子->>主线程),并在主线程(UI线程)中执行回调方法 return new MainThreadExecutor(); } static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); // 获取与Android 主线程绑定的Handler @Override public void execute(Runnable r) { handler.post(r); // 该Handler是上面获取的与Android 主线程绑定的Handler // 在UI线程进行对网络请求返回数据处理等操作。 } }// 切换线程的流程:// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象//2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程 }// 下面继续看步骤5的Builder有参构造方法<-- 步骤5 -->// Builder类的构造函数2(有参) public Builder(Platform platform) { // 接收Platform对象(Android平台) this.platform = platform;// 通过传入BuiltInConverters()对象配置数据转换器工厂(converterFactories)// converterFactories是一个存放数据转换器Converter.Factory的数组// 配置converterFactories即配置里面的数据转换器 converterFactories.add(new BuiltInConverters());// BuiltInConverters是一个内置的数据转换器工厂(继承Converter.Factory类)// new BuiltInConverters()是为了初始化数据转换器 }
对Builder类分析完毕,总结:
Builder设置了默认的
- 平台类型对象Platform:Android
- 网络请求适配器工厂:CallAdapterFactory(CallAdapter用于对原始Call进行再次封装,如Call到Observable)
- 数据转换器工厂: converterFactory
- 回调执行器:callbackExecutor
特别注意,这里只是设置了默认值,但未真正配置到具体的Retrofit类的成员变量当中
(1.3)baseUrl(“http://fanyi.youdao.com/”)
<-- 步骤1 -->public Builder baseUrl(String baseUrl) { // 把String类型的url参数转化为适合OKhttp的HttpUrl类型 HttpUrl httpUrl = HttpUrl.parse(baseUrl); // 最终返回带httpUrl类型参数的baseUrl() // 下面继续看baseUrl(httpUrl) ->> 步骤2 return baseUrl(httpUrl); }<-- 步骤2 --> public Builder baseUrl(HttpUrl baseUrl) { //把URL参数分割成几个路径碎片 List pathSegments = baseUrl.pathSegments(); // 检测最后一个碎片来检查URL参数是不是以"/"结尾 // 不是就抛出异常 if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } this.baseUrl = baseUrl; return this; }
总结:baseUrl()用于配置Retrofit类的网络请求url地址:将传入的String类型url转化为适合OKhttp的HttpUrl类型的url
(1.4)addConverterFactory(GsonConverterFactory.create())
GsonConverterFactory.creat()
public final class GsonConverterFactory extends Converter.Factory {<-- 步骤1 --> public static GsonConverterFactory create() { // 创建一个Gson对象 return create(new Gson()); ->>步骤2 }<-- 步骤2 --> public static GsonConverterFactory create(Gson gson) { // 创建了一个含有Gson对象实例的GsonConverterFactory return new GsonConverterFactory(gson); ->>步骤3 } private final Gson gson;<-- 步骤3 --> private GsonConverterFactory(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); this.gson = gson; }
所以,GsonConverterFactory.creat()是创建了一个含有Gson对象实例的GsonConverterFactory,并返回给addConverterFactory()
接下来继续看:addConverterFactory()
// 将上面创建的GsonConverterFactory放入到 converterFactories数组// 在第二步放入一个内置的数据转换器工厂BuiltInConverters()后又放入了一个GsonConverterFactory public Builder addConverterFactory(Converter.Factory factory) { converterFactories.add(checkNotNull(factory, "factory == null")); return this; }
总结:用于创建一个含有Gson对象实例的GsonConverterFactory并放入到数据转换器工厂converterFactories里。即Retrofit默认使用Gson进行解析,若使用其他解析方式(如Json、XML或Protocobuf),也可通过自定义数据解析器来实现(必须继承 Converter.Factory)
(1.5)build()
public Retrofit build() { <-- 配置网络请求执行器(callFactory)--> okhttp3.Call.Factory callFactory = this.callFactory; // 如果没指定,则默认使用okhttp // 所以Retrofit默认使用okhttp进行网络请求 if (callFactory == null) { callFactory = new OkHttpClient(); } <-- 配置回调方法执行器(callbackExecutor)--> Executor callbackExecutor = this.callbackExecutor; // 如果没指定,则默认使用Platform检测环境时的默认callbackExecutor // 即Android默认的callbackExecutor if (callbackExecutor == null) { callbackExecutor = platform.defaultCallbackExecutor(); } <-- 配置网络请求适配器工厂(CallAdapterFactory)--> List adapterFactories = new ArrayList<>(this.adapterFactories); // 向该集合中添加了步骤2中创建的CallAdapter.Factory请求适配器(添加在集合器末尾) adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); // 请求适配器工厂集合存储顺序:自定义1适配器工厂、自定义2适配器工厂...默认适配器工厂(ExecutorCallAdapterFactory) <-- 配置数据转换器工厂:converterFactory --> // 在步骤2中已经添加了内置的数据转换器BuiltInConverters()(添加到集合器的首位) // 在步骤4中又插入了一个Gson的转换器 - GsonConverterFactory(添加到集合器的首二位) List converterFactories = new ArrayList<>(this.converterFactories); // 数据转换器工厂集合存储的是:默认数据转换器工厂( BuiltInConverters)、自定义1数据转换器工厂(GsonConverterFactory)、自定义2数据转换器工厂....// 注://1. 获取合适的网络请求适配器和数据转换器都是从adapterFactories和converterFactories集合的首位-末位开始遍历// 因此集合中的工厂位置越靠前就拥有越高的使用权限 // 最终返回一个Retrofit的对象,并传入上述已经配置好的成员变量 return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); }
总结:在最后一步中,通过前面步骤设置的变量,将Retrofit类的所有成员变量都配置完毕。
所以,成功创建了Retrofit的实例
(1.6)总结
Retrofit使用建造者模式通过Builder类建立了一个Retrofit实例,具体创建细节是配置了:
- 平台类型对象(Platform - Android)
单例获取不同平台,如Android平台中MainThreadExecutor - 网络请求的url地址(baseUrl)
- 网络请求工厂(callFactory):默认使用OkHttpCall
- 网络请求适配器工厂的集合(adapterFactories):本质是配置了网络请求适配器工厂- 默认是ExecutorCallAdapterFactory
CallAdapter用于对原始Call进行再次封装,找到对应的执行器,如RxjavaCallFactory对应的Observable,可将Call转换为Observable - 数据转换器工厂的集合(converterFactories):本质是配置了数据转换器工厂
converterFactory
数据解析Converter,将response通过converterFactory转换成对应的数据形式,GsonConverterFactory,FastJsonConverterFactory。 - 回调方法执行器(callbackExecutor):默认回调方法执行器作用是:切换线程(子线程 - 主线程)
由于使用了建造者模式,所以开发者并不需要关心配置细节就可以创建好Retrofit实例,建造者模式get。
在创建Retrofit对象时,你可以通过更多更灵活的方式去处理你的需求,如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能
- 创建 网络请求接口实例 并 配置网络请求参数
<-- 步骤1:定义接收网络数据的类 --><-- JavaBean.java -->public class JavaBean { .. // 这里就不介绍了 }<-- 步骤2:定义网络请求的接口类 --><-- AccessApi.java -->public interface AccessApi { // 注解GET:采用Get方法发送网络请求 // Retrofit把网络请求的URL分成了2部分:1部分baseurl放在创建Retrofit对象时设置;另一部分在网络请求接口设置(即这里) // 如果接口里的URL是一个完整的网址,那么放在创建Retrofit对象时设置的部分可以不设置 @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car") // 接受网络请求数据的方法 Call getCall(); // 返回类型为Call<*>,*是解析得到的数据类型,即JavaBean}<-- 步骤3:在MainActivity创建接口类实例 -->AccessApi NetService = retrofit.create(AccessApi.class); <-- 步骤4:对发送请求的url进行封装,即生成最终的网络请求对象 --> Call call = NetService.getCall();
(2.1)源码分析
Retrofit是通过外观模式 & 代理模式 使用create()方法创建网络请求接口的实例。同时,通过网络请求接口里设置的注解进行了网络请求参数的配置
外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。
代理模式:通过访问代理对象的方式来间接访问目标对象。
主要分析步骤3、4:
步骤3:AccessApi NetService = retrofit.create(NetService.class);在MainActivity创建网络接口类实例
public T create(final Class service) { if (validateEagerly) { // 判断是否需要提前验证 eagerlyValidateMethods(service); // 具体方法作用: // 1. 给接口中每个方法的注解进行解析并得到一个ServiceMethod对象 // 2. 以Method为键将该对象存入LinkedHashMap集合中 // 特别注意:如果不是提前验证则进行动态解析对应方法(下面会详细说明),得到一个ServiceMethod对象,最后存入到LinkedHashMap集合中,类似延迟加载(默认) } // 创建了网络请求接口的动态代理对象,即通过动态代理创建网络请求接口的实例 (并最终返回) // 该动态代理是为了拿到网络请求接口实例上所有注解 return (T) Proxy.newProxyInstance( service.getClassLoader(), // 动态生成接口的实现类 new Class<?>[] { service }, // 动态创建实例 new InvocationHandler() { // 将代理类的实现交给 InvocationHandler类作为具体的实现(下面会解释) private final Platform platform = Platform.get(); // 在 InvocationHandler类的invoke()实现中,除了执行真正的逻辑(如再次转发给真正的实现类对象),还可以进行一些有用的操作 // 如统计执行时间、进行初始化和清理、对接口调用进行检查等。 @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // 下面会详细介绍 invoke()的实现 // 即下面三行代码 ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }// 特别注意// return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)// 可以解读为:getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class).newInstance(invocationHandler);// 即通过动态生成的代理类,调用interfaces接口的方法实际上是通过调用InvocationHandler对象的invoke()来完成指定的功能// 先记住结论,在讲解步骤4的时候会再次详细说明<-- 关注点1:eagerlyValidateMethods() -->private void eagerlyValidateMethods(Class<?> service) { Platform platform = Platform.get(); for (Method method : service.getDeclaredMethods()) { if (!platform.isDefaultMethod(method)) { loadServiceMethod(method); } // 将传入的ServiceMethod对象加入LinkedHashMap集合 // 使用LinkedHashMap集合的好处:lruEntries.values().iterator().next()获取到的是集合最不经常用到的元素,提供了一种Lru算法的实现 } }
创建网络接口实例采用外观模式 & 代理模式
- 外观模式:定义一个统一接口,用来包装子系统中一个/多个复杂的类,客户端可通过调用外观类的方法来调用内部子系统中所有的方法。
Retrofit对象的外观(门店) = retrofit.create()
通过这一外观方法就可以在内部调用各个方法创建网络请求接口的实例和配置网络请求参数,即用户只需要通过Retrofit.create()这一外观角色便完成任意类型网络接口的创建。
- 代理模式
代理模式:通过访问代理对象的方式来间接访问目标对象
分为静态代理 & 动态代理:
静态代理:代理类在程序运行前已经存在的代理方式
动态代理:代理类在程序运行前不存在、运行时由程序动态生成的代理方式
return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)通过代理模式中的动态代理模式,动态生成网络请求接口的代理类,并将代理类的实例创建交给InvocationHandler类 作为具体的实现,并最终返回一个动态代理对象。
使用动态代理的好处:
- 当NetService对象调用getCall()接口中方法时会进行拦截,调用都会集中转发到 InvocationHandler#invoke (),可集中进行处理
- 获得网络请求接口实例上的所有注解
- 更方便封装ServiceMethod
执行网络接口中方法时会被网络动态代理对象Proxy.newProxyInstance()拦截并最终调用InvocationHandler#invoke ()进行集中处理。下面分析InvocationHandler类 # invoke()里的具体实现
new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // 作用:读取网络请求接口里的方法,并根据前面配置好的属性配置serviceMethod对象 // 一个serviceMethod 对象 对应网络请求接口里的一个方法,loadServiceMethod(method)负责加载ServiceMethod //serviceMethod对象根据从Retrofit对象中获取对应的数据适配器、内容转换器对网络请求接口方法中注解的参数类型和注解内容进行解析从而完成网络请求参数的配置,即配置好ServiceMethod对象 //配置ServiceMethod对象包括对ServiceMethod的域进行赋值(方法的标注)与ParameterHandler<?>对象(方法参数的标注) ServiceMethod serviceMethod = loadServiceMethod(method); // 作用:根据配置好的serviceMethod对象与输入的网络请求接口参数args创建okHttpCall对象 OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); // 作用:调用OkHttp,并根据okHttpCall返回rejava的Observe对象或者返回Call //将第二步创建的OkHttpCall对象传给第一步创建的serviceMethod对象中对应的网络请求适配器工厂的adapt() //返回对象类型:Android默认是Call<>;若设置了RxJavaCallAdapterFactory,返回的则是Observable<> //这里采用了装饰者模式:ExecutorCallbackCall = 装饰者,而里面真正去执行网络请求的还是OkHttpCall。使用装饰模式的原因:希望在OkHttpCall发送请求时做一些额外操作。这里的额外操作是线程转换,即将子线程切换到主线程 return serviceMethod.callAdapter.adapt(okHttpCall); }
步骤4:Call call = NetService.getCall();对发送请求的url进行封装,生成最终的网络请求对象
- NetService对象实际上是动态代理对象Proxy.newProxyInstance()(步骤3中已说明),并不是真正的网络请求接口创建的对象
- 当NetService对象调用getCall()时会被动态代理对象Proxy.newProxyInstance()拦截,然后调用自身的InvocationHandler # invoke()
- invoke(Object proxy, Method method, Object… args)会传入3个参数:Object proxy:(代理对象)、Method method(调用的getCall())、Object… args(方法的参数,即getCall()中的)
- 接下来利用Java反射获取到getCall()的注解信息,配合args参数创建ServiceMethod对象。
- 最终创建并返回一个OkHttpCall类型的Call对象(OkHttpCall类是OkHttp的包装类,创建了OkHttpCall类型的Call对象还不能发送网络请求,需要创建Request对象才能发送网络请求)
(2.2)总结
Retrofit是Retrofit核心类,对外提供接口。通过retrofit.create()创建retrofit实例,外观模式。在create()方法中,使用动态代理模式对请求的接口中方法进行封装(ServiceMethod),初始化OkhttpCall。
Retrofit采用了外观模式统一调用创建网络请求接口实例和网络请求参数配置的方法,具体步骤如下:
- 动态创建网络请求接口的实例(代理模式 - 动态代理)
- 创建 serviceMethod 对象(建造者模式 & 单例模式(缓存机制))
ServiceMethod是核心处理类,解析方法和注解,toRequest()方法中生成HttpRequest。创建responseConverter(将response流转换为String或实体),创建callAdapter。 - 对 serviceMethod 对象进行网络请求参数配置:通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络请求的url地址、网络请求执行器、网络请求适配器 & 数据转换器。(策略模式)
- 对 serviceMethod 对象加入线程切换的操作,便于接收数据后通过Handler从子线程切换到主线程从而对返回数据结果进行处理(装饰模式)
- 最终创建并返回一个OkHttpCall类型的网络请求对象
OkHttpCall是对okhttp3.Call的封装调用
- 发送网络请求(封装了数据转换、线程切换的操作)
Retrofit默认使用OkHttp,即OkHttpCall类(实现了 retrofit2.Call接口)。OkHttpCall提供了两种网络请求方式:
同步请求:OkHttpCall.execute()
异步请求:OkHttpCall.enqueue()
(3.1)同步请求 OkHttpCall.execute()
Response response = call.execute();
包括网络请求三个步骤:
步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler进行解析,再根据ServiceMethod对象创建一个OkHttp的Request对象(ServiceMethod几乎保存了一个网络请求所需要的数据。发送网络请求时,OkHttpCall需要从ServiceMethod中获得一个Request对象)
步骤2:使用OkHttp的Request发送网络请求;
步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,最终得到一个Response对象
为了提高效率,Retrofit还会对解析过的请求ServiceMethod进行缓存,存放在Map
ServiceMethod> serviceMethodCache = new LinkedHashMap<>();对象中
(3.2)异步请求
call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { System.out.println(response.isSuccessful()); if (response.isSuccessful()) { response.body().show(); } else { try { System.out.println(response.errorBody().string()); } catch (IOException e) { e.printStackTrace(); } ; } }
发送请求过程包括4个步骤:
步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler进行解析,再根据ServiceMethod对象创建一个OkHttp的Request对象
步骤2:使用OkHttp的Request发送网络请求;
步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,最终得到一个Response对象
步骤4:进行线程切换从而在主线程处理返回的数据结果(若使用了RxJava,则直接回调到主线程)call是一个静态代理,使用静态代理的作用是:在okhttpCall发送网络请求的前后进行额外操作(这里的额外操作是:线程切换,即将子线程切换到主线程,从而在主线程对返回的数据结果进行处理)。
异步请求的过程跟同步请求类似,唯一不同之处在于:异步请求会将回调方法交给回调执行器在主线程中执行。
- 处理服务器返回的数据
(5.4.4)设计模式
(1)Builder 模式
(2)工厂模式
(3)适配器模式
(4)代理模式
(5)外观模式
(6)策略模式
(7)观察者模式
(5.4.5)总结
Retrofit 本质上是一个 RESTful 的HTTP 网络请求框架的封装,即通过 大量的设计模式 封装了 OkHttp ,使得简洁易用。具体过程如下:
- Retrofit 将 Http请求 抽象 成 Java接口
- 在接口里用 注解 描述和配置 网络请求参数
- 用动态代理 的方式,动态将网络请求接口的注解 解析 成HTTP请求
- 最后执行HTTP请求
即网络请求由OkHttp进行,而网络请求的接口封装由Retrofit进行:
- App应用程序通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数,之后由OkHttp完成后续的请求操作。
- 在服务端返回数据之后,OkHttp将原始的结果交给Retrofit,Retrofit根据用户的需求对结果进行解析。
- 完成数据的转化(converterFactory),适配(callAdapterFactory),通过设计模式进行各种扩展。
5.5 总结
(5.5.1)Retrofit使用7步骤
- 添加Retrofit依赖,网络权限
- 定义接收服务器返回数据的Bean
- 创建用于描述网络请求的接口,使用注解
- 通过builder模式创建Retrofit实例,并设置数据解析器(Converter)与网络请求适配器(CallAdapter)
- Retrofit通过动态代理创建网络请求接口实例,获取具体的网络请求对象Call
- 通过Call对象发送同步/异步网络请求(封装了数据转换,线程切换过程)
- 处理服务器返回的数据
(5.5.2)网络通信8步骤
- 创建Retrofit实例
- 定义网络请求接口,并为接口中的方法添加注解
- 通过动态代理生成网络请求对象
- 通过网络请求适配器将网络请求对象进行平台适配
- 通过网络请求执行器,发送网络请求(call)
- 通过数据解析器解析数据
- 通过回调执行器,切换线程
- 用户在主线程处理返回结果
更多相关文章
- android retrofit2.0框架的使用介绍
- Android中关于Volley的使用(三)认识Volley的架构
- android 4.0 "移动网络" 选项 不存在
- Android—网络编程
- Android的连接服务器
- 使用charles proxy for Mac来抓取手机App的网络包
- android使用HttpURLConnection上传文件同时提交参数
- Android,一个函数实现上传文件(单个,多文件)
- Android中ConnectivityManager监听网络状态