美文网首页
Volley源码浅析

Volley源码浅析

作者: 小和尚恋红尘 | 来源:发表于2018-08-04 13:29 被阅读0次

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

            RequestQueue rq = Volley.newRequestQueue(this);
            StringRequest sr = new StringRequest(URL_GET,
                    new Response.Listener<String>() {
                        @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<String, String> additionalHeaders)
                throws IOException, AuthFailureError {
            String url = request.getUrl();
            HashMap<String, String> map = new HashMap<String, String>();
           ......
            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<String, String> 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 <T> Request<T> add(Request<T> 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<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                    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.shouldCache()来判定用户是否需要缓存,如果不需要的话,直接将我们生成的Request对象加入到mNetworkQueue网络队列中;如果需要则加入到mCacheQueue缓存队列中。默认情况下是需要缓存的,除非你通过request.setsetShouldCache(false)设置为不需要缓存。

    上一篇:Volley的基本使用
    参考:

    相关文章

      网友评论

          本文标题:Volley源码浅析

          本文链接:https://www.haomeiwen.com/subject/vadrvftx.html