前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Volley 源码解析(一),网络请求的执行流程

Android Volley 源码解析(一),网络请求的执行流程

作者头像
developerHaoz
发布2018-08-20 15:32:36
1.3K0
发布2018-08-20 15:32:36
举报
文章被收录于专栏:developerHaoz 的安卓之旅

前言

花了好几天,重新研究了 Volley 的源码实现,比起之前又有了一番新的体会,啃源码真的是一件让人纠结的事情,阅读优秀的源码,特别是难度相对较大的源码,一旦陷入代码细节或者情绪一烦躁,很容易让人奔溃,但是真正的啃下来,收获真的很大。从优秀的代码中学习优秀的编程思想以及良好的代码设计和代码风格是一个非常好的方法,这次通读了 Volley 的源码之后,对于 Volley 的代码质量和拓展性深感佩服,为了更好的记录这次的源码研究之旅,写几篇博客记录一下。

一、Volley 简介


Volley 是 Google 在 2013 年的 I/O 大会上推出的 「Android 异步网络请求框架和图片加载框架」,它的设计目标就是去进行 数据量不大,但 通信频繁 的网络操作,而对于大数据量的网络操作,比如下载文件等,Volley 的表现就会非常糟糕。

Volley 的使用方法

在进行源码分析之前,先让我们来看下平时是怎样使用 Volley 的

代码语言:javascript
复制
   RequestQueue requestQueue = Volley.newRequestQueue(context);
   StringRequest stringRequest = new StringRequest(url
           , new Response.Listener<String>() {
       @Override
       public void onResponse(String s) {
           // TODO:
       }
   }, new Response.ErrorListener() {
       @Override
       public void onErrorResponse(VolleyError error) {
          // TODO:
       }
   });
   requestQueue.add(stringRequest);

1、通过 Volley.newRequestQueue(Context) 获取一个 RequestQueue

2、传入 URL 构建 Request,并实现相应的回调

3、将 Request 加入到 RequestQueue 中

Volley 中比较重要的类

在这先把 Volley 中比较重要的类说一下,到时候看源码能更加明白:

类名

作用

Volley

对外暴露的 API,主要作用是构建 RequestQueue

Request

所有网络请求的抽象类,StringRequest、JsonRequest、ImageRequest 都是它的子类

RequestQueue

存放请求的队列,里面包括 CacheDispatcher、NetworkDispatcher 和 ResponseDelivery

Response

封装一个解析后的结果以便分发

CacheDispatcher

用于执行缓存队列请求的线程

NetworkDispatcher

用户执行网络队列请求的线程

Cache

缓存请求结果,Volley 默认使用的是基于 sdcard 的 DiskBaseCache

HttpStack

处理 Http 请求,并返回请求结果

Network

调用 HttpStack 处理请求,并将结果转换成可被 ResponseDelivery 处理的 NetworkResponse

ResponseDelivery

返回结果的分发接口

二、请求的执行流程


我们从 Volley 的使用方法入手,一步一步探究底层的源码实现,我们的入手点就是

Volley.newRequestQueue(context)

代码语言:javascript
复制
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }

这个方法只有一行代码,只是调用了 newRequestQueue() 的方法重载,并给第二个参数传入 null,那我们看下带有两个参数的 newRequestQueue 方法中的代码

代码语言:javascript
复制
    public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
        BasicNetwork network;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
                    userAgent = packageName + "/" + info.versionCode;
                } catch (NameNotFoundException e) {
                }

                network = new BasicNetwork(
                        new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
            }
        } else {
            network = new BasicNetwork(stack);
        }

        return newRequestQueue(context, network);
    }

    private static RequestQueue newRequestQueue(Context context, Network network) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }

可以看到,这个方法中先判断 stack 是否为 null,如果是的话,这里会根据 Android 手机的系统版本号来进行相应的处理,当 SDK >= 9,则创建一个 HurlStack 实例,否则创建一个 HttpClientStack 实例,实际上 HurlStack 内部使用的是 HttpURLConnction 进行网络请求,而 HttpClientStack 则是使用 HttpClient 进行网络请求,这里之所以要这么处理,主要是因为在 Android 2.3(SDK = 9)之前,HttpURLConnection 存在一个很严重的问题,所以这时候用 HttpClient 来进行网络请求会比较合适,具体的原因可以看下这篇文章:Android 一起来看看 HttpURLConnection 和 HttpClient 的区别

不过由于现在的 Android 手机基本都是 4.0 以上的,而且 HttpClient 已经由于某些原因被弃用了,所以现在只要了解 HttpURLConnection 相关的知识就够了。思路拉回来,我们继续看代码,拿到 Stack 的实例之后将其构建成一个 Network 对象,它是用于根据传入的 Stack 对象来处理网络请求的,紧接着构建出一个 RequestQueue 对象,并调用 start() 方法。

我们接着看 start() 方法究竟做了什么:

代码语言:javascript
复制
    public void start() {
        stop();
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (final NetworkDispatcher mDispatcher : mDispatchers) {
            if (mDispatcher != null) {
                mDispatcher.quit();
            }
        }
    }

先调用 stop() 方法将当前正在进行 Dispatcher 都停掉,然后创建了一个 CacheDispatcher 实例,并调用了它的 start() 方法,接着在一个循环里去创建 NetworkDispatcher 的实例,分别调用它们的 start() 方法,这里的 CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread 的,默认情况下 for 循环会执行四次,也就是说当调用了 Volley.newRequestQueue(context) 之后,就会有五个线程在后台运行,等待网络请求的到来,其中 CacheDispatcher 是缓存线程,NetworkDispatcher 是网络请求线程。

得到 RequestQueue 之后,构建相应的 Request,然后调用 add() 方法将其加入到请求队列中

代码语言:javascript
复制
    public <T> Request<T> add(Request<T> request) {
        // 将 Request 标记为属于此队列,并将其放入 mCurrentRequests 中
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // 让 Request 按照他们被添加的顺序执行
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        //如果请求不需要被缓存,就跳过缓存,直接进行网络请求
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        mCacheQueue.add(request);
        return request;
     }

可以看到,传入 Request 之后,会先判断该 Request 是否需要进行缓存,如果不需要就直接将其加入到网络请求队列,需要缓存则加入缓存队列。默认情况下,每条请求都是应该缓存的,当然我们也可以调用 Request 的 setShouldCache() 方法来进行设置。

Request 被添加到缓存队列中后,在后台等待的缓存线程就要开始运行起来了,我们看下 CacheDispatcher 的 run() 方法究竟是怎么实现的。

代码语言:javascript
复制
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // 初始化 Cache
        mCache.initialize();

        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
            }
        }
    }

    private void processRequest() throws InterruptedException {
        final Request<?> request = mCacheQueue.take();
        request.addMarker("cache-queue-take");

        // 如果请求已经取消了,我们直接结束该请求
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // 从 Cache 中取出包含请求缓存数据的 Entry
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // 如果缓存的请求过期了,就将其添加到网络请求队列中
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // 缓存的数据封装成 NetworkResponse
        Response<?> response = request.parseNetworkResponse(
                new NetworkResponse(entry.data, entry.responseHeaders));

        if (!entry.refreshNeeded()) {
            // 如果缓存没有过期就直接进行分发
            mDelivery.postResponse(request, response);
        } else {
            // 重置该请求的 Entry
            request.setCacheEntry(entry);
            response.intermediate = true;

            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mNetworkQueue.put(request);
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                });
            } else {
                mDelivery.postResponse(request, response);
            }
        }
    }

代码相对比较长,我在关键的地方已经打上注释了,在这里总结一下,可以看到在初始化了 Cache 之后,有一个 while(true) 循环,说明缓存线程是始终执行的,接着会在缓存中取出响应结果,如果为 null 的话,就将其加入到网络请求队列中,如果不为空的话,再判断该缓存是否已过期,已经过期则同样把这条请求加入到网络请求队列中,否则直接使用缓存中的数据。最后将数据进行解析,并进行分发。

看完 CacheDispathcer 的 run() 方法,我们接着看 NetworkDispatcher 的 run() 方法

代码语言:javascript
复制
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
            }
        }
    }

    private void processRequest() throws InterruptedException {
        Request<?> request = mQueue.take();

        long startTimeMs = SystemClock.elapsedRealtime();
        try {
            request.addMarker("network-queue-take");

            // 如果 Request 已经取消了,那就不执行网络请求
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }

            addTrafficStatsTag(request);

            // 执行网络请求
            NetworkResponse networkResponse = mNetwork.performRequest(request);

            // 如果服务器返回 304,而且我们已经分发过该 Request 的结果,那就不用进行第二次分发了
            //(这里补充一下,304 代表服务器上的结果跟上次访问的结果是一样的,也就是说数据没有变化)
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // 在子线程解析返回的结果
            Response<?> response = request.parseNetworkResponse(networkResponse);

            // 如果需要的话,就将返回结果写入缓存
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);            }

            // 分发响应结果
            request.markDelivered();
            mDelivery.postResponse(request, response);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            request.notifyListenerResponseNotUsable();
        } catch (Exception e) {
            request.notifyListenerResponseNotUsable();
        }
    }

在 NetworkDispatcher 同样使用了 while(true),说明网络请求线程也是不断运行的。然后从网络队列里面取出 Request,再调用 Network 的 performRequest() 方法去发送网络请求。Network 其实是一个接口,这里具体的实现是 BasicNetwork,我们来看下它的 performRequest() 方法实现:

代码语言:javascript
复制
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());
                 // 该注意的地方:调用 Stack 的 executeRequest 进行网络请求
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                int statusCode = httpResponse.getStatusCode();

                responseHeaders = httpResponse.getHeaders();
                if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null, true,
                                SystemClock.elapsedRealtime() - requestStart, responseHeaders);
                    }
                    List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                    return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,
                            true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders);
                }

                // 有些返回结果是没有内容的,如:204,所以我们必须进行检查
                InputStream inputStream = httpResponse.getContent();
                if (inputStream != null) {
                  responseContents =
                          inputStreamToBytes(inputStream, httpResponse.getContentLength());
                } else {
                  responseContents = new byte[0];
                }
                return new NetworkResponse(statusCode, responseContents, false,
                        SystemClock.elapsedRealtime() - requestStart, responseHeaders);
            } catch (Exception e) {
                // ...
            }
        }
    }

这个方法里面,基本上都是网络请求方面处理的细节,我们这篇文章,主要是梳理整体的流程,对细节方面先不深入。需要注意的是在我标注的第一个地方,调用了 Stack 的 executeRequest() 方法,这里的 Stack 就是之前调用 Volley.newRequestQueue() 所创建的实例,前面也说过了这个对象的内部是使用了 HttpURLConnection 或 HttpClient(已弃用)来进行网络请求。网络请求结束后将返回的数据封装成一个 NetworkResponse 对象进行返回。

在 NetworkDispatcher 接收到了这个 NetworkResponse 对象之后,又会调用 Request 的 parseNetworkResponse() 方法来对结果进行解析,然后将数据写入到缓存,最后调用 ExecutorDelivery 的 postResponse() 方法来回调解析后的数据,如下所示:

代码语言:javascript
复制
    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

在 mResponsePoster(一个 Executor 的实例对象) 的 execute() 方法中传入了一个 ResponseDeliveryRunnable 对象,execute() 方法默认是在主线程中执行的,这样就保证了 ResponseDeliveryRunnable 的 run() 方法也是在主线程当中运行的,我们看下 run() 方法里面的逻辑:

代码语言:javascript
复制
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // 如果 Request 被取消了,调用 finish() 方法,结束该请求,不进行传递
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // 根据响应的结果来进行不同的分发
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // 如果传入的 mRunnable 不为 null,则运行
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

可以看到当 Response.isSuccess() 为 true 的话,调用 Resquest 的 deliverResponse() 方法,对结果进行回调,deliverResponse() 方法是每一个具体的 Request 子类都必须实现的抽象类,来看下我们最熟悉的 StringRequest 中的 deliverResponse() 方法

代码语言:javascript
复制
    @Override
    protected void deliverResponse(String response) {
        Response.Listener<String> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {
            listener.onResponse(response);
        }
    }

看到这里应该就很明白了,在 deliverResponse() 方法中,调用 listener.onResponse() 方法进行回调,这个 listener 正是我们构建 StringRequest 时传入的 Listener,也就是说将返回的结果回调到我们在外部调用的地方。

代码语言:javascript
复制
   StringRequest stringRequest = new StringRequest(url
           , new Response.Listener<String>() {
       @Override
       public void onResponse(String s) {
           // TODO:
       }
   }, new Response.ErrorListener() {
       @Override
       public void onErrorResponse(VolleyError error) {
          // TODO:
       }
   });

到这里,终于把 Volley 的完整执行流程全部都梳理了一遍,最后我们来看一下 Volley 官方提供的流程图:


参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.02.02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、Volley 简介
    • Volley 的使用方法
      • Volley 中比较重要的类
        • 二、请求的执行流程
          • 参考
          相关产品与服务
          云服务器
          云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档