volley(3)——源码分析

本文详细解释了Volley库中的RequestQueue如何管理网络请求和缓存,涉及RequestQueue的结构、CacheDispatcher和NetworkDispatcher的工作流程,以及关键方法如`put`,`take`,和`parseNetworkResponse`的作用。
摘要由CSDN通过智能技术生成
        if (stagedRequests == null) {

            stagedRequests = new LinkedList<Request<?>>();

        }

        stagedRequests.add(request);

        mWaitingRequests.put(cacheKey, stagedRequests);

        if (VolleyLog.DEBUG) {

            VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);

        }

    } else {

        // Insert 'null' queue for this cacheKey, indicating there is now a request in

        // flight.

        mWaitingRequests.put(cacheKey, null);

        mCacheQueue.add(request);

    }

    return request;

}

}




可以看到注释所示,添加一个Request到派发队列。Request是所有请求的基类,是一个抽象类。`request.setRequestQueue(this);`的作用就是将请求Request关联到当前RequestQueue。然后同步操作将当前Request添加到RequestQueue对象的mCurrentRequests HashSet中做记录。通过`request.setSequence(getSequenceNumber());`得到当前RequestQueue中请求的个数,然后关联到当前Request。`request.addMarker("add-to-queue");`添加调试的Debug标记。`if (!request.shouldCache())`判断当前的请求是否可以缓存,如果不能缓存则直接通过`mNetworkQueue.add(request);`将这条请求加入网络请求队列,然后返回request;如果可以缓存的话则在通过同步操作将这条请求加入缓存队列。在默认情况下,每条请求都是可以缓存的,当然我们也可以调用Request的setShouldCache(false)方法来改变这一默认行为。OK,那么既然默认每条请求都是可以缓存的(shouldCache返回为true),自然就被添加到了缓存队列中,于是一直在后台等待的缓存线程就要开始运行起来了。现在来看下CacheDispatcher中的run()方法,代码如下所示:



@Override

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;

    }

}

}




首先通过Process.setThreadPriority(Process.THREAD\_PRIORITY\_BACKGROUND);设置线程优先级,然后通过mCache.initialize(); 初始化缓存块,其中mCache是由Volley.java中的`newRequestQueue(Context context, HttpStack stack)`方法中实例化传入的,其Cache接口的实现为new DiskBasedCache(cacheDir),其中cacheDir默认在Volley.java中不设置为/data/data/app-package/cache/volley/。



接下来由while (true)可以发现缓存线程是一直在执行,其中通过mQuit标记进制是否结束线程的操作。mCacheQueue.take()从阻塞队列获取最前面的一个request,没有request就阻塞等待。接着通过mCache.get(request.getCacheKey());尝试从缓存中取出响应结果,如何为空的话则把这条请求加入到网络请求队列中,如果不为空的话再判断该缓存是否已过期,如果已经过期了则同样把这条请求加入到网络请求队列中,否则就认为不需要重发网络请求,直接使用缓存中的数据即可。在这个过程中调运了parseNetworkResponse()方法来对数据进行解析,再往后就是将解析出来的数据进行回调了。现在先来看下Request抽象基类的这部分代码:



/**

  • Subclasses must implement this to parse the raw network response

  • and return an appropriate response type. This method will be

  • called from a worker thread. The response will not be delivered

  • if you return null.

  • @param response Response from the network

  • @return The parsed response, or null in the case of an error

*/

abstract protected Response parseNetworkResponse(NetworkResponse response);




通过注释可以看到他就是一个解析模块的功能。



上面说了,当调用了Volley.newRequestQueue(context)之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,其中一个CacheDispatcher是缓存线程,四个NetworkDispatcher是网络请求线程。CacheDispatcher的run方法刚才已经大致分析了,解析来看下NetworkDispatcher中是怎么处理网络请求队列的,具体代码如下所示:



@Override

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);

    }

}

}




和CacheDispatcher差不多,如上可以看见一个类似的while(true)循环,说明网络请求线程也是在不断运行的。



如上通过mNetwork.performRequest(request);代码来发送网络请求,而Network是一个接口,这里具体的实现之前已经分析是BasicNetwork,所以先看下它的performRequest()方法,如下所示:



NetWork接口的代码:



public interface Network {

/**

 * Performs the specified request.

 * @param request Request to process

 * @return A {@link NetworkResponse} with data and caching metadata; will never be null

 * @throws VolleyError on errors

 */

public NetworkResponse performRequest(Request<?> request) throws VolleyError;

}




上面说了,就是执行指定的请求。他的BasicNetwork实现子类如下:



@Override

public NetworkResponse performRequest(Request<?> request) throws VolleyError {

long requestStart = SystemClock.elapsedRealtime();

while (true) {

    HttpResponse httpResponse = null;

    byte[] responseContents = null;

    Map<String, String> responseHeaders = Collections.emptyMap();

    try {

        // Gather headers.

        Map<String, String> headers = new HashMap<String, String>();

        addCacheHeaders(headers, request.getCacheEntry());

        httpResponse = mHttpStack.performRequest(request, headers);

        StatusLine statusLine = httpResponse.getStatusLine();

        int statusCode = statusLine.getStatusCode();



        responseHeaders = convertHeaders(httpResponse.getAllHeaders());

        // Handle cache validation.

        if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

            Entry entry = request.getCacheEntry();

            if (entry == null) {

                return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,

                        responseHeaders, true,

                        SystemClock.elapsedRealtime() - requestStart);

            }

            // A HTTP 304 response does not have all header fields. We

            // have to use the header fields from the cache entry plus

            // the new ones from the response.

            // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5

            entry.responseHeaders.putAll(responseHeaders);

            return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,

                    entry.responseHeaders, true,

                    SystemClock.elapsedRealtime() - requestStart);

        }

        // Some responses such as 204s do not have content.  We must check.

        if (httpResponse.getEntity() != null) {

            responseContents = entityToBytes(httpResponse.getEntity());

        } else {

            // Add 0 byte response as a way of honestly representing a

            // no-content request.

            responseContents = new byte[0];

        }

        // if the request is slow, log it.

        long requestLifetime = SystemClock.elapsedRealtime() - requestStart;

        logSlowRequests(requestLifetime, request, responseContents, statusLine);

        if (statusCode < 200 || statusCode > 299) {

            throw new IOException();

        }

        return new NetworkResponse(statusCode, responseContents, responseHeaders, false,

                SystemClock.elapsedRealtime() - requestStart);

    } catch (SocketTimeoutException e) {

        attemptRetryOnException("socket", request, new TimeoutError());

    } catch (ConnectTimeoutException e) {

        attemptRetryOnException("connection", request, new TimeoutError());

    } catch (MalformedURLException e) {

        throw new RuntimeException("Bad URL " + request.getUrl(), e);

    } catch (IOException e) {

        int statusCode = 0;

        NetworkResponse networkResponse = null;

        if (httpResponse != null) {

            statusCode = httpResponse.getStatusLine().getStatusCode();

        } else {

            throw new NoConnectionError(e);

        }

        VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());

        if (responseContents != null) {

            networkResponse = new NetworkResponse(statusCode, responseContents,

                    responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);

            if (statusCode == HttpStatus.SC_UNAUTHORIZED ||

                    statusCode == HttpStatus.SC_FORBIDDEN) {

                attemptRetryOnException("auth",

                        request, new AuthFailureError(networkResponse));

            } else {

                // TODO: Only throw ServerError for 5xx status codes.

                throw new ServerError(networkResponse);

            }

        } else {

            throw new NetworkError(networkResponse);

        }

    }

}

}




这个方法是网络请求的具体实现,也是一个大while循环,其中mHttpStack.performRequest(request, headers);代码中的mHttpStack是Volley的newRequestQueue()方法中创建的实例,前面已经说过,这两个对象的内部实际就是分别使用HttpURLConnection和HttpClient来发送网络请求的,然后把服务器返回的数据组装成一个NetworkResponse对象进行返回。在NetworkDispatcher中收到了NetworkResponse这个返回值后又会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,同时将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。



前面你可以看到在NetWorkDispatcher的run中最后执行了`mDelivery.postResponse(request, response);`,也就是说在解析完了NetworkResponse中的数据之后,又会调用ExecutorDelivery(ResponseDelivery接口的实现类)的postResponse()方法来回调解析出的数据,具体代码如下所示:



@Override

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {

request.markDelivered();

request.addMarker("post-response");

mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));

}




这里可以看见在mResponsePoster的execute()方法中传入了一个ResponseDeliveryRunnable对象,就可以保证该对象中的run()方法就是在主线程当中运行的了,我们看下run()方法中的代码是什么样的:



/**

  • A Runnable used for delivering network responses to a listener on the

  • main thread.

*/

@SuppressWarnings(“rawtypes”)

private class ResponseDeliveryRunnable implements Runnable {

private final Request mRequest;

private final Response mResponse;

private final Runnable mRunnable;

public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {

    mRequest = request;

    mResponse = response;

    mRunnable = runnable;

}

@SuppressWarnings("unchecked")

@Override

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。

[外链图片转存中…(img-Ee19sa52-1714519216200)]

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值