Volley源码分析

volley-request.png

Volley的工作原理

  1. 线程管理
  2. 缓存的管理
  3. 发送网络请求过程
    • 在主线程把请求加入请求队列
    • 缓存线程查询请求是否有缓存,如果有缓存,则从缓存中获取数据解析返回给主线程,如果没有缓存,把请求分发给网络线程
    • 网络线程发送请求,从服务器获取数据,解析后返回给主线程

Volley源码分析

​ 首先,从创建请求队列开始看

Volley.newRequestQueue(context);

​ 点击进去跳到Volley类,然后就会跳到这个方法里面.

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);  //这里创建了一个缓存目录,具体路径在data/data/包名/cache/DEFAULT_CACHE_DIR所指向的文件名里面

    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) { //如果当前的构建版本大于SDK9 就用HurlStack为HttpStack赋值,HttpStack内部用HttpUrlConnection来创建网络连接
            stack = new HurlStack();
        } else { //否则就用HttpClientStack来为HttpStack赋值,HttpClientStack里面使用HttpClient来创建网络连接
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }

    Network network = new BasicNetwork(stack);

    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

    return queue;
} 

​ 我们进去看一下HttpStack类

public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
     *
     * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
     * and the Content-Type header is set to request.getPostBodyContentType().</p>
     *
     * @param request the request to perform
     * @param additionalHeaders additional headers to be sent together with
     *         {@link Request#getHeaders()}
     * @return the HTTP response
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

​ 发现这是一个接口,那就看一下他的实现类HurlStack,查看该类的实现方法

public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request); //使用HttpURLConnection来打开网络连接
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

​ 我们发现这个方法主要是在构建网络连接,里面是使用HttpURLConnection来网络连接,然后返回HttpResponse响应.

继续回到newRequestQueue方法里面

    Network network = new BasicNetwork(stack);

    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

​ 代码执行到这里了,我们看一下这行代码,这里传入了两个参数,一个是基于磁盘的缓存,一个是网络连接.进去DiskBasedCache类看一下

 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
    }

​ 可以看出这里设置了缓存的目录以及默认使用缓存的大小

 private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

​ 默认缓存的大小为5M.

我们最后进入RequestQueue里面看一下构造方法做了一些什么事情

public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

继续点击进去,

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

再次点击进去,最终是调用这个构造方法

 public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache; //根据上面封装的构造方法,这里传进来的是new DiskBasedCache(cacheDir)
        mNetwork = network;//这里传进来的是new BasicNetwork(stack)
        mDispatchers = new NetworkDispatcher[threadPoolSize];//threadPoolSize传进来的是 DEFAULT_NETWORK_THREAD_POOL_SIZE,根据查看源码得知这个值是为4
        //private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
        mDelivery = delivery; 
   //这里传进来的是
   //new ExecutorDelivery(new Handler(Looper.getMainLooper())));
   //这里做的是,将子线程切换到主线程
    }

我们最终是初始化了以上这些类,我们点击NetworkDispatcher进去看一下,里面做了一些什么事情

发现NetworkDispatcher是继承了Thread类的,就说明,上面初始化的时候,初始化了4个线程.

 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

可以看出RequestQueue初始化做了一下事情:

  • 初始化磁盘缓存
  • 初始化了网络连接
  • 创建了4个网络线程
  • 初始化了一个将执行结果发送到主线程的传递器

继续点击进去看start方法做了什么事情

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.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

通过这个方法我们可以知道,首先创建了缓存分发器.首先检查缓存中是否有数据,我们进去CacheDispatcher类查看

这是一个继承了Thread的类,

 public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();  //这里不断地往缓存队列中拿数据
                request.addMarker("cache-queue-take");     //并且为请求添加标记

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {  //判断请求是否已经取消
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
              //内存中找不到请求,就从磁盘中查找缓存
                if (entry == null) { //如果找不到缓存
                    request.addMarker("cache-miss"); //则为这个请求打上"缓存丢失的标记"
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);  //找不到缓存,就会往网络队列中寻找缓存
                    continue;
                }

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) { //判断缓存是否过期
                    request.addMarker("cache-hit-expired"); //过期了就,为缓存打上"缓存过期" 的标记
                    request.setCacheEntry(entry); //并重新设置缓存
                    mNetworkQueue.put(request); //向网络请求队列中加入缓存
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");  //如果有缓存,就打上"找到缓存"的标记
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
              //读取缓存并解析缓存
                request.addMarker("cache-hit-parsed"); //为请求打上"缓存被解析"的标记

                if (!entry.refreshNeeded()) {//判断数据是否需要刷新
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response); //传递器将响应从子线程传递到主线程
                } else {
                  //如果数据需要重新刷新,就需要重新加入网络请求队列中,发送网络请求
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

看完这里,可以知道,首先在内存中寻找缓存,如果没有就到磁盘中寻找缓存,如果没有,再发送网络请求获取数据.

volley-request.png

和上面这个图的工作原理一样.数据加入网络请求队列之后,继续往下看

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

这里添加了四个线程给网络分发器,对应上图.

我们进去NetworkDispatcher里面看

 public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response); //发送响应到主线程
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

工作原理和上面的缓存队列工作原理相似.

  mDelivery.postResponse(request, response); //发送响应到主线程

继续往下查看

@Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

这里执行了ResponseDeliveryRunnable,继续查看

 public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) { //响应成功
                mRequest.deliverResponse(mResponse.result);
            } else { //响应失败
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

响应成功或者失败都会回调监听

响应成功:

  @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }

响应失败:

public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

因为本人这里是使用JsonRequest,所以这里会响应JsonRequest的请求,监听器就有数据过来了

private NetWorkListener<NewsListBean> mListener=new NetWorkListener<NewsListBean>()
    {
        @Override
        public void onResponse(NewsListBean response) {
            //保存新闻列表数据
            mNews = response.getData().getNews();
            //保存轮播图数据
            mTopNews = response.getData().getTopnews();
            //刷新新闻列表
            mAdapter.notifyDataSetChanged();
            //保存more字段,加载更多
            mMore = response.getData().getMore();
            List<String> urls = new ArrayList<String>();
            mTopNews = response.getData().getTopnews();
            //遍历轮播图的数据,构建图片url集合
            for (int i = 0; i <mTopNews.size() ; i++) {
                urls.add(mTopNews.get(i).getTopimage());
            }
            //刷新轮播图
            mFunBanner.setImageUrls(urls);//内部使用glide加载图片
        }
    };

NetWorkListener是我自己写的,实现了Response.Listener,Response.ErrorListener接口

public class NetWorkListener<T> implements Response.Listener<T>,Response.ErrorListener{
    @Override
    public void onErrorResponse(VolleyError error) {

    }

    @Override
    public void onResponse(T response) {

    }
}

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

推荐阅读更多精彩内容

  • 我的博客: Volley 源码分析 Volley 的使用流程分析 官网示例 创建一个请求队列 RequestQue...
    realxz阅读 2,040评论 1 11
  • 前言 本文是一篇日常学习总结性的文章,笔者通过分析经典网络框架Volley的源码,望以巩固Android网络框架中...
    Armstrong_Q阅读 570评论 0 7
  • Volley源码分析之流程和缓存 前言 Android一开始提供了HttpURLConnection和HttpCl...
    大写ls阅读 618评论 0 6
  • 以StringRequest为例子,分析Volley是怎样执行一个网络请求的。 先看实现Request抽象类的St...
    stefanJi阅读 218评论 0 0
  • kidII阅读 101评论 0 0