Volley源码简析

RequestQueueManager

这个类是Volley源码分析的入口,在这里创建RequestQueue,并且为之配置参数。这个类代码很少,请看:

/**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @param stack An {@link HttpStack} to use for the network, or null for default.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = 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) {
                }
                
                // 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 DiskCache(), network);
        queue.start();

        return queue;
    }

根据系统的版本,2.3以及以上的版本,使用HurlStack,也就是使用HttpUrlConnection。低于2.3的,使用HttpClient。

这里最重要的工作是新建了RequestQueue,为它配置了Cache(DiskCache,处理磁盘缓存)和Network(BasicNetwork,用来实现真正的网络数据请求)。RequestQueue是一个Request分发管理器,直接在这里启动它内部的线程池。

RequestQueue

它用来分发请求,内部管理着一个线程池,默认用1个线程来分发缓存的请求;用4个线程来分发网络的请求。

先看一下它的几个关键的成员变量,基本上体现出它的功能。

/**
 * A request dispatch queue with a thread pool of dispatchers.
 *
 * Calling {@link #add(Request)} will enqueue the given Request for dispatch,
 * resolving from either cache or network on a worker thread, and then delivering
 * a parsed response on the main thread.
 */
public class RequestQueue {

    // 缓存后的Request再次请求的话,先在这里等待
    /** Staging area for requests that already have a duplicate request in flight.*/
    private final Map>> mWaitingRequests =
            new HashMap>>();
    
    // 当前所有等待被处理和正在处理的Request
    /** The set of all requests currently being processed by this RequestQueue. A Request will be in this set if it is waiting in any queue or currently being processed by any dispatcher. */
    private final Set> mCurrentRequests = new HashSet>();

    // 所有的Request先进这个队列,被CacheDispatcher分发,也就是看有没有效的缓存数据,有的话直接返回数据,请求结束;没有的话再放到NetwrokQueue里,等待联网。
    /** The cache triage queue. */
    private final PriorityBlockingQueue> mCacheQueue =
        new PriorityBlockingQueue>();

    // 等待联网的Request都在这个队列里。进到这个队列,就等着被分发给线程池里的线程去请求数据
    /** The queue of requests that are actually going out to the network. */
    private final PriorityBlockingQueue> mNetworkQueue =
        new PriorityBlockingQueue>();

    // 缓存管理工具接口,提供缓存的存取操作,默认用DiskCache。
    /** Cache interface for retrieving and storing responses. */
    private final Cache mCache;

    // 网络管理工具接口,提供网络数据的获取操作,默认用BasicNetwork
    /** Network interface for performing requests. */
    private final Network mNetwork;

    // 向界面传递响应数据
    /** Response delivery mechanism. */
    private final ResponseDelivery mDelivery;

    /** The network dispatchers. */
    private NetworkDispatcher[] mDispatchers;

    /** The cache dispatcher. */
    private CacheDispatcher mCacheDispatcher;

    /** Callback interface for completed requests. */
    public static interface RequestFinishedListener {
        /** Called when a request has finished processing. */
        public void onRequestFinished(Request request);
    }

    private List mFinishedListeners =
            new ArrayList();

上面的几个关键成员变量大概说明了工作机制。具体的先从add()方法入手:加入一个Request。

public  Request add(Request request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);    // Request里要用this去取消自己
        synchronized (mCurrentRequests) { // mCurrentRequests记录所有的Request
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // 不需要缓存的直接跳过CacheQueue,进入NetworkQueue.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // 以下是需要缓存的Request的处理。涉及mWaitingRequests和mCacheQueue
        // 一个需要缓存的Request,如果第一次请求,那么先去mCacheQueue,然后到mWaitingRequests登记一下,但是没有进去。一旦请求过再请求,那就不进mCacheQueue,而是进入mWaitingRequests等待。
        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList>();
                }
                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;
        }
    }

mCurrentRequests用来登记当前所有的Request。add()的时候加入,finish()的时候移除,cancelAll()的时候用来结束所有。

从数据链路的角度来说,所有的Request(除了不需要缓存的)都先进mCacheQueue队列,看有没有效的缓存数据,有的话直接返回数据,请求结束;没有的话再放到NetwrokQueue里,等待联网。

mWaitingRequests是个比较有意思的设计,一开始没理解,多看几遍代码后才明白。明确的一点是它用于需要缓存的Request。看下面这种场景:

RequestA是一个需要缓存的请求。它第一次被发起,因为没有本地缓存数据,所以被放到NetworkQueue去联网请求数据。但是网络很差,20秒后才会有数据返回。就在这时,RequestA又被快速的连续发了10次。也就是说,在第一次的请求数据还没回来前,同一个请求又被发了10次。

如果不用WaitingRequests,那后面的10次请求都会先进到CacheQueue然后因为没有本地缓存数据而进到了NetworkQueue。一旦进到了NQ,那就没有反悔的机会,10个同样的请求都会发出去。很明显,这10个请求都是浪费!

WaitingRequests就是用来解决这个问题的。现在,这10次请求,都会因为第一次的请求在mWaitingRequests里留下记录而先暂时被存在mWaitingRequests里,而不会马上到达CacheQueue和NetworkQueue。等到第一次请求完成后,Request会调用RequestQueue的finish方法,把自己finish掉。在finish的时候,就把暂存在mWaitingRequests里的自己的10个兄弟们释放到CacheQueue里。之后就看造化了,如果第一次请求成功并且缓存数据有效,那么后10个就直接用本地缓存,不需要浪费10次联网请求。

     /** Called from {@link Request#finish(String)}, indicating that processing of the given request has finished.*/
     void finish(Request request) {
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {
          for (RequestFinishedListener listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }

        // 关键代码在这里!
        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    // 全取出来放到CacheQueue里
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

CacheQueue和NetworkQueue的两套分发机制,相对比较简单,容易看懂。RequestQueue被创建后马上就被start()

public void start() {
        // Make sure any currently running dispatchers are stopped.
        stop();  

        // 缓存队列派发
        // 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();
        }
    }

Dispatcher里放入了相应的Queue,也就是Dispatcher分发Queue里的Request。注意到CacheDispatcher里也放入了NetworkQueue。因为CacheQueue里的有可能缓存数据失效了,或者不用缓存,所以会放到NetworkQueue里去联网。

RequestQueue主要是靠CacheDispatcher和NetWorkDispatcher两类分发器开展主要工作。它们都是线程。RequestQueue一启动,就创建了分发线程池,里面有一个缓存分发线程,4个网络分发线程。后面就靠这5个线程进行请求分发。

CacheDispatcher

缓存型Request分发器,准确地说是所有的Request都会先进到这个队列,这个参考上面RequestQueue。它实质上是一个线程,一创建就被启动,有请求到来(CacheQueue上有Request)就进行分发,没有请求就阻塞在CacheQueue上。CacheQueue是一个BlockingQueue。先看下它的成员变量:

public CacheDispatcher(
            BlockingQueue> cacheQueue, BlockingQueue> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

它的工作原理是作为一个线程循环地执行:从mCacheQueue中取出Request,判断是否有缓存,没有缓存的直接放到NetworeQueue里。有缓存,判断ttl和softttl这两种时效。ttl失效则放到NetworkQueue里,softttl失效先向界面通知数据然后在放到NetworkQueue里再取一遍数据。

Volley源码简析_第1张图片
Paste_Image.png

缓存管理以及ttl和softttl,由Cache接口以及接口实现类DiskCache来完成。

Cache和DiskCache

Cache定义了缓存操作接口。

public Entry get(String key);

public void put(String key, Entry entry);

public void initialize();

public void invalidate(String key, boolean fullExpire);

public void remove(String key);

public void clear();

Cache的内部类Entry,封装了缓存的数据结构以及基本操作。

public static class Entry {
    /** The data returned from cache. */
    public byte[] data;

    /** ETag for cache coherency. */
    public String etag;

    /** Date of this response as reported by the server. */
    public long serverDate;

    /** The last modified date for the requested object. */
    public long lastModified;

    /** TTL for this record. */
    public long ttl;

    /** Soft TTL for this record. */
    public long softTtl;

    /** Immutable response headers as received from server; must be non-null. */
    public Map responseHeaders = Collections.emptyMap();

    // 判断缓存是否有效
    public boolean isExpired() {
        return this.ttl < System.currentTimeMillis();
    }

    // 判断是否还需要再从服务器刷新数据
    public boolean refreshNeeded() {
        return this.softTtl < System.currentTimeMillis();
    }
}

ttl和softttl本来是由服务器配置的,决定缓存的时效。ttl记录的是缓存的过期的日期。在这个日期内缓存视为有效,可以直接使用。softttl记录的是软过期的日期。意思是这个日期内缓存可用,但是需要从服务器再刷新一遍数据。刷新数据的时候,会把从缓存里取出的老数据Entry放到Request的cacheEntry里再向服务器请求。

DiskCache具体实现了Cache接口,完成缓存的初始化、存储、获取和废弃等操作。所有的缓存配置,例如缓存位置、缓存大小都可以在这里配置。关于Cache实现,需要单独再写一篇分析文章。

NetworkDispatcher

网络请求分发器,它的参数和CacheDispatcher差不多,少了一个CacheQueue。作为一个线程,它主要的代码都在run()里。用伪码的话,大概是这样的:

while (true) {
    request = mNetworkQueue.take();
    if (request.isCanceled()) {
        request.finish();
        continue;
    }
    
    // 用Network接口的实现类(BasicNetwork)来执行联网请求,阻塞并获取数据
    NetworkResponse networkResponse = mNetwork.performRequest(request);
    
    // 304处理
    if (networkResponse.notModified && request.hasHadResponseDelivered()) {
        request.finish();
        continue;
    }
    
    // 解析响应数据
    Response response = request.parseNetworkResponse(networkResponse);
    // 该缓存的缓存起来
    if (request.shouldCache() && response.cacheEntry != null) {
        mCache.put(request.getCacheKey(), response.cacheEntry);
    }
    
    // 向上通知数据
    mDelivery.postResponse(request, response);
}

它有两个阻塞点,一个是NetworkQueue,如果队列里没有数据,那么线程会阻塞在取数据上;一个是performRequest(),连接服务器等待数据响应。注意,响应数据的解析,是由Request里的方法来实现的。 Request其实封装了很多实现。这样Volley使用起来有更大的定制空间。

Network接口和BasicNetwork实现

Network接口只有一个方法,用来执行网络请求。

public NetworkResponse performRequest(Request request) throws VolleyError;

BasicNetwork的主要工作是实现了performRequest(),通过HttpStack完成具体的联网请求,然后处理各种状态下的响应数据。下面用伪码来说明performRequest的流程:

while (true) {
    // 缓存数据的header,主要是etag,配合服务器的304
    addCacheHeaders(headers, request.getCacheEntry());
    httpResponse = mHttpStack.performRequest(request, headers);

    // 处理304
    if (statusCode == 304) {
        Entry entry = request.getCacheEntry();
        return new NetworkResponse(entry.data, entry.responseHeaders);
    }
                
    // 处理redirect
    if (statusCode == 301 || statusCode == 302) {
        String newUrl = responseHeaders.get("Location");
        request.setRedirectUrl(newUrl);
    }

    // 处理204没内容的情况
    if (httpResponse.getEntity() != null) {
        responseContents = entityToBytes(httpResponse.getEntity());
    } 
    else {
        // 没内容
        responseContents = new byte[0];
    }

    // 网络错误
    if (statusCode < 200 || statusCode > 299) {
        throw new IOException();
    }
    
    return new NetworkResponse(responseContents, responseHeaders, );
} 
catch (SocketTimeoutException e) {
    // 各种错误处理
}

HurlStack和HttpClientStack

HurlStack和HttpClientStack这两个实现了HttpStack接口的类,其实是系统的HttpURLConnection和HttpClient的对应封装。HttpStack接口和Network接口一样,只提供一个接口:

public HttpResponse performRequest(Request request, Map additionalHeaders)

ResponseDelivery接口和ExecutorDelivery实现

ResponseDelivery提供了成功和出错两种数据的传递接口。成功接口里还有一个可以带Runnable的。比如缓存数据softttl需要刷新的,传递了数据后再执行一个加入到NetworkQueue的接口。

public interface ResponseDelivery {
    // 传递成功数据
    public void postResponse(Request request, Response response);

    // 传递成功数据,并在传递完成后执行一个runnable
    public void postResponse(Request request, Response response, Runnable runnable);

    // 传递出错数据
    public void postError(Request request, VolleyError error);
}

ExecutorDelivery通过Executor和一个来自UI的Handler来传递数据。Executor调用Handler来向界面传递一个Runnable。Delivery运行在线程池的线程上,和UI线程之间是跨线程。来自UI的Handler是两个线程之间的桥梁。

public ExecutorDelivery(final Handler handler) {
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

post出的command这个Runnable已经是在UI线程上执行了。这个Runnable调用Request里的传递方法来执行操作。几个关键节点,比如之前的网络数据解析,这里的数据传递,具体的都是在Request里完成的。

public void run() {
    // 每一阶段都判断一下Request有没被取消
    if (mRequest.isCanceled()) {
        mRequest.finish();
        return;
    }

    // 这里的代码已经是在UI线程空间里执行了,传递操作还是在Request里定义
    if (mResponse.isSuccess()) {
        mRequest.deliverResponse(mResponse.result);
    } 
    else {
        mRequest.deliverError(mResponse.error);
    }

    // 再执行一下附带的任务
    if (mRunnable != null) {
        mRunnable.run();
    }
}

你可能感兴趣的:(Volley源码简析)