一、Okhttp3
okhttp由square公司出品,目前主流的网络库,功能相对比较全面。
1.1总体框架设计
Okhttp 3.4之前有个类:HttpEngine,网络请求和响应的核心逻辑封装在HttpEngine中,3.4及其以后版本重构了,去掉了这个类,它的功能在getResponseWithInterceptorChain中拆分为CacheInterceptor、ConnectInterceptor、CallServerInterceptor 等几个拦截器来实现。
1.2请求流程
1.3 类图
1.4 异步请求时序图
基本流程:
1)Okhttp通过newCall创建RealCall发起一个网络请求任务,同时传入Request。
2)网络请求的调度交给Dispatcher通过一个受限制的Cached类型的线程池来处理。
3)3.4之前通过HttpEngine来处理网络请求,之后通过对应的几个固定拦截器来处理网络请求。
4)callback返回Response。
二、Volley
Volley 是 Google 1/0 2013 发布的Android异步网络请求框架和图片加载框架。适合数据量小,通信频繁的网络操作。
2.1总体框架设计
上层接受自定义Reuqest,请求放入RequestQueue中,通过两种Dispatch Thread不断从RequestQueue中取出请求,根据是否已缓存调用Cache或Network这两类数据获取接口之一,
从内存缓存或是服务器取得请求的数据,然后交由ResponseDelivery去做结果分发及回调处理。
2.2请求流程
2.3 类图
2.4 请求时序图
基本流程:
1)Volley创建一个RequestQueue,该Queue包含1个CacheDispatcher线程,4个NetworkDispatcher线程。
2)request设置能缓存则请求放入mCacheQueue,交由CacheDispatcher处理,获取缓存。不能缓存放入mNetworkQueue,走网络请求。
3)真正网络请求由BasicNetwork.performRequest执行。
4)最终解析response并返回callback。
三、Okhttp3 和 Volley几个点的对比
3.1 框架设计
这里只是对框架设计本身做对比,而非功能层面。
Okhttp3
优点:
- 首先设计上一个最大的直观感受是无处不在的建造者模式。不仅包括OkhttpClient、Request、Response这种核心组件,也包括CacheControl、Headers这种具体功能都使用的建造者模式,配置非常灵活。
- 通过各种Interceptor以责任链模式对Request和Response进行监听、参数获取或者调整某些参数信息。
缺点:
- Request个性配置支持不够,例如:为不同Request设置不同的超时时间,因为超时是由OkHttpClient统一设置的,当然也可以对个性化定制的Request去走OkHttpClient.newBuilder(),但是这里考虑到并发问题,并不是一个非常好的策略。
- Request中的配置信息基本上异常都是throw一个Exception,这也就要求初始化Request的时候必须做好try catch,不然线上环境很容易crash,例如:.url()设置请求url,如果url格式出现问题,会在HttpUrl.parse里直接throw异常,交给调用处去处理,这时候如果调用处疏忽没做处理,就会crash。
- callBack返回的异常情况需要自行封装,严格说也不能叫做是缺点吧,只是这点Volley做得更好。
Volley
优点:
- Request拓展性非常强。
-
callBack异常有封装VolleyError,同时也封装了各种异常子类,这部分handleError功能写起来比Okhttp3方便很多。
缺点:
BasicNetwork 中的statuCode状态码处理有点笼统,if (statusCode < 200 || statusCode > 299) 直接throw new IOException(); 另外还有volley 401 错误处理
3.2 重试
Okhttp3
通过拦截器RetryAndFollowUpInterceptor来做,因为拦截器是基于OkHttpClient来做的,如果是通用型重试还好,如果Request需要不同个性化重试策略,比如重试次数有差别,或者是否提供其他重试域名等等。这种情况下RetryAndFollowUpInterceptor就不太好做了,况且他还是final的,默认策略不满足还只能自己重新写。
Volley
Request.setRetryPolicy来实现,这对单一Request个性化定制来说比Okhttp3灵活,它接口为RetryPolicy,有个默认实现DefaultRetryPolicy,支持参数的动态配置,当然也可以自定义。相对比较灵活。
3.3 调度
Okhttp3
Dispatcher提供了在最大并发数64和每个主机最大请求数5的范围内的一个类似CacheThreadPool的线程池。这种类型线程池比较适用于:高并发但每个任务耗时较少的场景。
//准备队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//执行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//最大并发数。
private int maxRequests = 64;
//每个主机的最大请求数。
private int maxRequestsPerHost = 5;
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
那么来看看调度逻辑:
void enqueue(AsyncCall call) {
synchronized (this) {
//加入双端准备队列
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
将符合条件的calls从readyAsyncCalls移动到runningAsyncCalls里面,在executor service上执行它们。
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
//循环判断准备请求队列是否还有请求
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
//如果执行请求的队列数量大于等于最大请求数,中止循环
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
//小于最大主机请求限制
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
//从准备队列里移除自身。
i.remove();
executableCalls.add(asyncCall);
//添加到执行队列中。
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
//对新添加的任务尝试进行异步调用。
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
对应的AsyncCall 就是一个Runnable。
这里可以在源码的基础上做两个层面的自定义调度优先级:
1)线程层面的
可以通过自定义Request传入priority,在RealCall中获取对应Request的优先级并设置给AsyncCall
final class AsyncCall extends NamedRunnable {
...
@Override
public void run() {
super.run();
//originalRequest 就是传进来的Request
Thread.currentThread().setPriority(originalRequest.priority());
}
2)线程池Queue层面的
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new PriorityBlockingQueue<Runnable>(60,new AsycCallComparator<Runnable>()), Util.threadFactory( "OkHttp Dispatcher", false));
这里使用PriorityBlockingQueue,它是一个具有优先级的无界队列,同时需要实现一个有序的比较器:
public class AsycCallComparator<T> implements Comparator {
@Override
public int compare(Object o1, Object o2) {
if ((o1 instanceof RealCall.AsyncCall) && (o2 instanceof RealCall.AsyncCall)) {
RealCall.AsyncCall task1 = (RealCall.AsyncCall) o1;
RealCall.AsyncCall task2 = (RealCall.AsyncCall) o2;
int result = task2.priority() - task1.priority();
return result;
}
return 0;
}
}
Volley
Volley是1个Cache Thread,4个Network Thread。各自都有一个对应的PriorityBlockingQueue维护请求队列。
/**
* The cache triage queue.
*/
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<>();
/**
* The queue of requests that are actually going out to the network.
*/
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<>();
/**
* Number of network request dispatcher threads to start.
*/
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
//mDispatchers.length 即为DEFAULT_NETWORK_THREAD_POOL_SIZE
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
对于优先级调度来说:
Volley本身mCacheQueue 和 mNetworkQueue 就使用的PriorityBlockingQueue,而线程优先级就更方便了,重构一个RequestQueue的start方法,直接给NetworkDispatcher设置优先级。
public void start(int threadPriority) {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
try {
networkDispatcher.setPriority(threadPriority);
} catch (Exception e) {
e.printStackTrace();
}
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
最近完成了公司网络框架组件的重构,新增了Okhttp支持,在保证不动业务层调用的情况下,调整了网络库整体架构,支持Okhttp和Volley动态切换,并且从其他Module的耦合中剥离出来,单独做maven管理。从两个框架的使用情况来看,常规请求都是50-100ms量级的速度,两者不相上下。两者对get 、post 、multipart post支持都还不错。后续再进一步看看表现差异以及存在的问题。