Volley源码浅析

上一篇我们介绍了Volley的基本使用,这篇我们来看看它的实现,直接看下面这段代码:

        RequestQueue rq = Volley.newRequestQueue(this);
        StringRequest sr = new StringRequest(URL_GET,
                new Response.Listener() {
                    @Override
                    public void onResponse(String response) {
                        Log.e("LHC", String.format("%s:%s", "volley_get_success:", response));
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.e("LHC", String.format("%s:%s", "volley_get_error:", error.getMessage()));
                    }
                });
        rq.add(sr);

上面这段代码是一个StringRequestGET实现,比较简单。我们现在来具体分析下,首先看第一行代码:

        RequestQueue rq = Volley.newRequestQueue(this);

用来生成了RequestQueue对象,我们进入Volley.newRequestQueue(this)进入源码来看看其内的实现:

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

这个方法很简单,只是实现了方法newRequestQueue的重载;那么在进入方法有两个参数的newRequestQueue中看看。

    public static RequestQueue newRequestQueue(Context context, HttpStack stack){
        return newRequestQueue(context, stack, -1);
    }

这里的第二个参数HttpStack用于网络默认可为NULL。它也是实现了方法newRequestQueue的重载,我们继续进入:

    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), 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) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);
        
        RequestQueue queue;
        if (maxDiskCacheBytes <= -1) {
            // No maximum size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }else{
            // Disk cache size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }

        queue.start();

        return queue;
    }

这个方法的第三个参数maxDiskCacheBytes表示磁盘缓存的最大值(单位为字节),默认值为-1。看看其中这段代码:

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

stack为空,Build.VERSION.SDK_INT大于9,通过HurlStackstack赋值;
stack为空,Build.VERSION.SDK_INT小于9,通过HttpClientStackstack赋值;
这个为什么要进行这个处理呢,我们进入这两个类中看看:

public class HurlStack implements HttpStack {
        ......
    @Override
    public HttpResponse performRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap map = new HashMap();
       ......
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        ......
       
        return response;
    }
}

public class HttpClientStack implements HttpStack {
    protected final HttpClient mClient;
    public HttpClientStack(HttpClient client) {
        mClient = client;
    }
        ......
    @Override
    public HttpResponse performRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {
        ......
        return mClient.execute(httpRequest);
    }
}

从上面代码可以看出,HurlStackHttpClientStack都是实现HttpStack接口,并在方法performRequest中完成了网络请求并返回HttpResponse对象;不同的在于HurlStack是使用HttpURLConnection进行网络通讯的,而HttpClientStack是使用HttpClient进行网络通讯的。

那么为什么要根据Build.VERSION.SDK_INT是否大于9来进行不同的网络请求实例呢?
HttpClient拥有众多的API,而且实现比较稳定,bug数量也很少;但是我们很难在不破坏兼容性的情况下对它进行升级和扩展。要注意的是在版本5.1之后废止了其相关API。
HttpURLConnection是一种多用途、轻量极的HTTP客户端,API提供的比较简单,但是我们可以更加容易地去使用和扩展它。
我们继续看newRequestQueue方法中的这段代码:

        RequestQueue queue;
        if (maxDiskCacheBytes <= -1) {
            // No maximum size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }else{
            // Disk cache size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);

        queue.start();
        }

如果没有设置磁盘缓存值,那么会默认设置磁盘缓存最大值为5M;

DiskBasedCache类,就是将要缓存的文件缓存到磁盘上指定目录的一种缓存实现。这块的缓存是在内存中分配的,虽然提高了请求的执行速度,但是不适合进行大数据的传输,如果进行大数据的下载,很容易早成分配的这块缓存溢出。

这段代码生成了RequestQueue对象并进行启动。最后将RequestQueue对象返回,这样就完成了RequestQueue rq = Volley.newRequestQueue(this);的实现。
我们继续看newRequestQueue方法中的这段代码:

Network network = new BasicNetwork(stack);

很简单就是生成了一个Network对象,具体来看看BasicNetwork中代码实现:

public class BasicNetwork implements Network {
......
    @Override
    public NetworkResponse performRequest(Request request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            ......
        }
......
}

这个类就是具体的来实现网络请求的,并返回了NetworkResponse对象。

来看看queue.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();
        }
    }

首先调用stop()方法将正在运行的调度程序全部停止。然后生成了一个CacheDispatcher对象,也就是缓存线程,并启动;最后在for循环中直接生成了mDispatchers.lengthNetworkDispatcher对相关,也就是网络线程,并启动。这样就直接启动了5个线程,在后台一直运行着,等待着网络请求的到来。其实mDispatchers.length值为4,看代码:

......
 /** The network dispatchers. */
    private NetworkDispatcher[] mDispatchers;
......
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }
......
public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }
......
/** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
......

所以,网络调度数组的大小也就是默认网络线程池的大小。
我们继续进入CacheDispatcher中,看看缓存调度线程的内部实现:

public class CacheDispatcher extends Thread {
......
    @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();

        Request request;
        while (true) {
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // Take a request from the queue.
                request = mCacheQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
                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.
                    final Request finalRequest = request;
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(finalRequest);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
            }
        }
    }
}

在这个线程的Run方法中,有个while(true)的死循环,它会再后台一直运行,直到有任务进来。通过Cache.Entry entry = mCache.get(request.getCacheKey());来取出缓存实体值,当为NULL时,将请求放入网络队列中;当entry.isExpired()true时,也就是数据完全过期了,我们需要将其放入网络队列中;否则我们直接使用缓存当中的数据,不需要进行网络请求。在通过entry.refreshNeeded()来判定是否需要来更新缓存数据。
进入NetworkDispatcher中,看看网络调度线程的内部实现:

public class NetworkDispatcher extends Thread {
......
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request request;
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            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);
            }
        }
    }
......
}

这个线程的Run方法中,有个while(true)的死循环,它会再后台一直运行,直到有任务进来。通过NetworkResponse networkResponse = mNetwork.performRequest(request);来执行网络请求并得到NetworkResponse对象;然后进行解析生成Response对象,在通过request.shouldCache() && response.cacheEntry != null来判定是否添加缓存。

完成RequestQueue生成后,构建具体的Request对象,将此对象加入RequestQueue中,这样就完成了网络请求操作。来看看rq.add(sr);中的代码实现:

    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);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

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

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // 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;
        }
    }

通过request.shouldCache()来判定用户是否需要缓存,如果不需要的话,直接将我们生成的Request对象加入到mNetworkQueue网络队列中;如果需要则加入到mCacheQueue缓存队列中。默认情况下是需要缓存的,除非你通过request.setsetShouldCache(false)设置为不需要缓存。

上一篇:Volley的基本使用
参考:
  • https://blog.csdn.net/guolin_blog/article/details/17656437
  • https://www.jianshu.com/p/b23c7952b30f

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