网络框架- Okhttp3与Volley整体架构分析

一、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支持都还不错。后续再进一步看看表现差异以及存在的问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358