OkHttp 是一个网络请求框架
Retrofit 是采用注解的形式对 OkHttp 的封装
关键字
OkHttp、Retrofit、Okio
同步、异步、阻塞、非阻塞、BIO、NIO、AIO
拦截器
SPDY、连接池、Gzip、Http
目录
- 基本网络框架
- OkHttp
- Retrofit
- BIO / NIO / AIO / Okio
1. 基本网络框架
一个完整的网络请求流程
- 构建 request 请求
- 切换子线程请求 Http,Executor或线程
- enqueue 入队列请求
- 请求服务器数据完成之后进行解析 json -> 对象
- 切回主线程,回调上层
2. OkHttp
KEY: execute、enqueue、RealCall、Interceptor
2.1 总述
OkHttp 有两种请求方式同步和异步
真正的执行者是RealCall
通过 拦截链的设计 (拦截及回传),优先 自定义 的拦截器(eg:Log、Header),之后是 默认 有 5 个拦截器。
5个默认拦截器依次为:
- RetryAndFollowUpInterceptor 重试/重定向拦截器
- BridgeInterceptor 桥拦截器:添加移除Header
- CacheInterceptor 缓存拦截器
- ConnectInterceptor 连接池拦截器,连接池复用
- CallServerInterceptor 真正请求网络拦截器
2.2 同步 / 异步请求
同步方法:execute
异步方法:enqueue
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder().url("http://").build();
// 同步请求方式
Response response1 = okHttpClient.newCall(request).execute();
// 异步请求方式
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
}
});
2.3 拦截链模式
RealCall 请求真正的执行者
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
execute() / enqueue() 最终都会调用 getResponseWithInterceptorChain()
方法
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
...
)
...
val response = chain.proceed(originalRequest)
...
return response
...
}
- RealInterceptorChain 负责管理拦截链的类
- 每个拦截器调用
chain.process(request)
,就会走到下一个拦截器的intercept
方法 - 最后一个拦截器是
CallServerInterceptor
,用Okio 请求网络返回 Response - 然后 response 进行回传,
response = chain.proceed(request);
通过拦截链的设计,每个拦截器各司其职,我们可以自定义拦截器。
2.4 基本使用
2.4.1 GET
- 同步 execute
- 异步 enqueue
2.4.2 POST
- 提交 String RequestBody
- 提交 流 RequestBody
- 提交 文件 RequestBody
- 提交 表单 FormBody
- 提交 分块请求 MultipartBody( 复杂的请求体,与HTML文件上传形式兼容)
2.4.3 Header
自定义拦截器添加
-
每个请求单独添加(区分 header / addHeader)
Request request = new Request.Builder() .url("https://api.github.com/repos/square/okhttp/issues") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build();
2.4.4 响应缓存
配置请求缓存策略:
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(10, TimeUnit.MILLISECONDS)
.maxStale(10, TimeUnit.MILLISECONDS)
.onlyIfCached()
.noCache()
.build();
new Request.Builder()
.cacheControl(cacheControl)
.build();
配置单个请求缓存设置:私有缓存目录 和 缓存大小的限制
newClient = okHttpClient.newBuilder() // 单个 OkHttp 配置
.cache(new Cache(new File(""), 100 * 1024 * 1024L))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new Interceptor() {
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
return null;
}
})
.build();
2.4.5 取消一个Call
-
取消单个:
Call call = okHttpClient.newCall(request); call.cancel();
取消全部:
dispatcher.cancelAll();
(待验证)
2.4.6 处理验证
-
HTTP AUTH
- 配置了 http auth
- 检查 header 有没有 "Authorization"
- 有则检验,没有则401
-
OkHttp 认证
自动重试未验证的请求
401 Not Authorized
Authenticator authenticator = new Authenticator() { @Nullable @Override public Request authenticate(@Nullable Route route, @NotNull Response response) throws IOException { String credential = Credentials.basic("user", "pwd"); if (credential.equals(response.request().header("Authorization"))) { return null; // If we already failed with these credentials, don't retry. } return response.request().newBuilder() .header("Authorization", credential) .build(); } }; new OkHttpClient.Builder() .authenticator(authenticator) .build();
2.5 优缺点
优点:
- 异步
- 进度条回调
- 支持 session 保持
- 支持取消请求
- 支持SPDY(多个连接共享一个socket,Http协议的增强)、连接池(复用)、Gzip(减少传输内容大小)、缓存(避免重复请求)
缺点:
缓存失效问题,解决:过滤可变参数;手动保存;
参考:
(面试必备-源码系列)OkHttp3
OkHttp使用完全教程
3. Retrofit
3.1 总述
Retrofit 底层网络框架默认是 OkHttp
Retrofit 适合 RESTful 格式的请求
与其他网络框架的不同:更多使用运行时注解的方式提供功能
3.2 使用步骤
-
创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .build();
注意:baseUrl 必须以 "/" 结束
-
定义Api接口
public interface BlogService { @GET("blog/{id}") Call
getBlog(@Path("id") int id); } -
请求调用
BlogService service = retrofit.create(BlogService.class); // 和 OkHttp 一致 service.getBlog(2).execute();
3.3 Retrofit 22 个注解
22个注解分三类:请求方式、标记类、参数类
3.3.1 HTTP请求方式 8个
7个:
GET、POST、PUT、DELETE、PATCH、OPTION;
1个:
HTTP(method=;path=;hasBody=); 可以替换上面7个方法
3.3.2 标记类 3个
表单请求:
FormUrlEncoded、Multipart
标记:
Streaming
3.3.3 参数类 11个
作用于方法:
Headers
作用于方法参数:
Header
Body
Filed / FiledMap
Part / PartMap
Path
Query / QueryMap
Url
参考:
你真的会用Retrofit2吗?Retrofit2完全教程
3.4 优缺点
优点:
- 使用注解超级解偶
- 配置不同的网络框架,OkHttp/HttpClient
- 配置不同 CallAdapter
- 配置不同的转换器解析工具
缺点:
- 不能接触序列化实体和响应数据
- 使用转换器低效
- 只能支持自定义参数类型
参考:
OkHttp和Retrofit
3.5 Retrofit 设计模式
-
外观模式(门面模式)
外部方便使用,内部闭门造车
eg: retrofit.create() / Glide.with()
-
装饰模式
Decorator,实际上叫 Wrapper/Source 更直观些
和静态代理很像,同 Proxy/Delegate,proxy不提供delegate没有的public方法,以免被认出来
使用的目的:希望在 Source 操作时执行一些额外的操作,这里是线程切换到主线程
static final class ExecutorCallbackCall
implements Call { final Executor callbackExecutor; final Call delegate; ExecutorCallbackCall(Executor callbackExecutor, Call delegate) { this.callbackExecutor = callbackExecutor; this.delegate = delegate; } @Override public void enqueue(final Callback callback) { delegate.enqueue(new Callback () {...} } ... -
动态代理
API:Proxy / InvocationHandler,依赖接口实现
和上面的静态代理使用场景类似,都做额外的操作。
但,静态代理的不足是需要额外写很多代码
所以,引入动态代理设置 delegate
public
validateServiceInterface(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
new InvocationHandler() {
...
@Override public @Nullable Object invoke(Object proxy, Method method,
...
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
```
Android AOP:Aspect Oriented Programming,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,实现业务解偶
- 适配器模式
Retrofit 底层使用 OkHttp 进行网络请求最终返回 OkHttpCall
implement Call
,要被不同的标准平台调用,比如 rxjava、java8、guava等。
于是为了适配兼容支持各平台,设计了接口 CallAdapter
```
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
...
.build()
```
```
public interface CallAdapter {
Type responseType();
T adapt(Call call);
abstract class Factory {
public abstract @Nullable CallAdapter, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
protected static Class> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
```
其他:RecyclerView.Adapter
-
Builder模式
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();
-
工厂模式
public interface CallAdapter
{ Type responseType(); T adapt(Call call); abstract class Factory { public abstract @Nullable CallAdapter, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit); protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } protected static Class> getRawType(Type type) { return Utils.getRawType(type); } } }
参考:
Retrofit分析-漂亮的解耦套路
3. BIO / NIO / AIO / Okio
KEY: 同步/异步、阻塞/非阻塞、多路复用、BIO、NIO、AIO
对比
名称 | 模式 | 接口 | 备注 |
---|---|---|---|
BIO Blocking I/O 传统IO |
同步阻塞I/O模式 | InputStream OutputStream Reader Writer |
装饰器模式 |
NIO New I/O No-Blocking I/O |
同步非阻塞I/O模式 | Buffer Channel Selector |
jdk1.4引入 实现多路复用模型 |
AIO Asynchronous I/O |
异步非阻塞I/O模式 | AsynchronousChannel CompletionHandler |
|
Okio | 对Java原生流(而不是Channel)做了封装 设计了一套非阻塞调用的机制(看门狗) |
Sink Source Segment SegmentPool |
享元模式-池技术 |
参考:
用轻和快定义优雅,Okio框架解析
Java NIO Tutorial
Java核心(五)深入理解BIO、NIO、AIO
10个最高频的Java NIO面试题剖析!
漫画:一文学会面试中常问的 IO 问题!
参考资料
如文中列出