美文网首页
Volley源码解析

Volley源码解析

作者: bruce1990 | 来源:发表于2020-04-11 11:39 被阅读0次

    前言

    这段时间入职新公司,发现网络请求库使用的是Volley,由于对Volley还不是很熟悉,于是有了今天这篇文章。

    Volley的基本使用

            //Volley的回调是在主线程的
            Log.e("TAG", "volley: -----------"+ Thread.currentThread().getName());
            //1.创建请求队列
            RequestQueue requestQueue = Volley.newRequestQueue(this);
            String url = "https://api.apiopen.top/musicBroadcasting";
            //2.创建请求
            StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    Log.e("TAG", "onResponse: ---" + Thread.currentThread().getName());
                    Log.e("TAG", "onResponse: ---" + response);
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e("TAG", "onErrorResponse: ---" + Thread.currentThread().getName());
                }
            });
            //3.将创建的请求添加到队列中
            requestQueue.add(request);
    
    • 注意: Volley的回调是在主线程的所以可以直接在回调中进行UI操作

    分析

    创建请求队列 Volley.newRequestQueue(this);做了些什么

        public static RequestQueue newRequestQueue(Context context) {
            return newRequestQueue(context, (BaseHttpStack) null);
        }
        //调用重载方法 BaseHttpStack 为null
    
        public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
            BasicNetwork network;
            if (stack == null) {
                //安卓2.3及以上使用HttpURLConnection
                if (Build.VERSION.SDK_INT >= 9) {
                    network = new BasicNetwork(new HurlStack());
                } else {
                    //下面这段话的意思是安卓2.3以前HttpURLConnection不可靠,所以使用的Apache的HttpClient,后面如果不再兼容2.3以下会考虑把Apache的Http移除
                    // Prior to Gingerbread, HttpUrlConnection was unreliable.
                    // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                    // At some point in the future we'll move our minSdkVersion past Froyo and can
                    // delete this fallback (along with all Apache HTTP code).
                    String userAgent = "volley/0";
                    try {
                        String packageName = context.getPackageName();
                        PackageInfo info =
                                context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
                        userAgent = packageName + "/" + info.versionCode;
                    } catch (NameNotFoundException e) {
                    }
    
                    //采用HttpClient
                    network =
                            new BasicNetwork(
                                    new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
                }
            } else {
                network = new BasicNetwork(stack);
            }
    
            return newRequestQueue(context, network);
        }
    
        private static RequestQueue newRequestQueue(Context context, Network network) {
            //创建缓存路径
            File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
            // 创建请求队列
            RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
            queue.start();
            return queue;
        }
    
        public void start() {
            //一个Queue里面有一个缓存线程和默认4个网络请求线程。也就是说内部其实会启动5个线程
            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();
            }
        }
    

    Request

    • Request就是封装了一些请求相关的东西,这里暂时略过

    requestQueue.add(request);

        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) {//添加到请求集合里面 set集合
                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.
            //判断是否需要缓存responses 如果不需要就跳过缓存队列  直接到网络请求队列  值得注意的是Volley默认是做缓存的
            if (!request.shouldCache()) {
                mNetworkQueue.add(request);
                return request;
            }
            mCacheQueue.add(request);
            return request;
        }
    
    • 接下来就是看这几个线程NetworkDispatcher和CacheDispatcher做了些什么
      直接看他们的run方法
    
    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 {
                    processRequest();
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                    VolleyLog.e(
                            "Ignoring spurious interrupt of CacheDispatcher thread; "
                                    + "use quit() to terminate it");
                }
            }
        }
        //从队列里面拿出请求然后处理
        private void processRequest() throws InterruptedException {
            // Get a request from the cache triage queue, blocking until
            // at least one is available.
            final Request<?> request = mCacheQueue.take();
            processRequest(request);
        }
    
        @VisibleForTesting
        void processRequest(final Request<?> request) throws InterruptedException {
            request.addMarker("cache-queue-take");
    
            // If the request has been canceled, don't bother dispatching it.
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                return;
            }
    
            // 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.
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
    
            // If it is completely expired, just send it to the network.
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
    
            // 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;
    
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    // 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) {
                                        // Restore the interrupted status
                                        Thread.currentThread().interrupt();
                                    }
                                }
                            });
                } else {
                    // request has been added to list of waiting requests
                    // to receive the network response from the first request once it returns.
                    mDelivery.postResponse(request, response);
                }
            }
        }
    
    
        void processRequest(Request<?> request) {
            long startTimeMs = SystemClock.elapsedRealtime();
            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");
                    request.notifyListenerResponseNotUsable();
                    return;
                }
    
                addTrafficStatsTag(request);
                //执行网络请求的地方   通过HttpUrlConnection 或者HttpClient来请求数据
                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");
                    request.notifyListenerResponseNotUsable();
                    return;
                }
    
                // 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);
                request.notifyListenerResponseReceived(response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
                request.notifyListenerResponseNotUsable();
            } 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);
                request.notifyListenerResponseNotUsable();
            }
        }
        
    

    缓存处理

    • Volley判断是否需要刷新缓存是使用服务端设置的,会考虑服务端返回header里的Cache-Control的Expires。但是有时候接口并不返回这些东西,这种情况下,volley设置的缓存ttl就是0,也就是相当于没有缓存,每次都会从网络请求
    • 如果需要缓存,应该怎么设置?
    • 重写请求头信息原理就是增加过期时间
    public class CacheControlHttpHeaderParser extends HttpHeaderParser {
    
        /**
         * @param response
         * @param cacheTime 设置缓存时间
         * @return
         */
        public static Cache.Entry parseCacheHeaders(NetworkResponse response, long cacheTime) {
            long now = System.currentTimeMillis();
            Map<String, String> headers = response.headers;
            long serverDate = 0;
            long lastModified = 0;
            String serverEtag = null;
            String headerValue;
    
            headerValue = headers.get("Date");
            if (headerValue != null) {
                serverDate = parseDateAsEpoch(headerValue);
            }
            headerValue = headers.get("Last-Modified");
            if (headerValue != null) {
                lastModified = parseDateAsEpoch(headerValue);
            }
            serverEtag = headers.get("ETag");
            Cache.Entry entry = new Cache.Entry();
            entry.data = response.data;
            entry.etag = serverEtag;
            entry.softTtl = now + cacheTime;
            entry.ttl = now + cacheTime;
            entry.serverDate = serverDate;
            entry.lastModified = lastModified;
            entry.responseHeaders = headers;
            entry.allResponseHeaders = response.allHeaders;
    
            return entry;
        }
    }
    
    • 在Request里重写parseNetworkResponse
    public class StringCacheRequest extends StringRequest {
        private static final long CACHE_TIME = 1000 * 60 * 5;//5分钟
    
        public StringCacheRequest(int method, String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
            super(method, url, listener, errorListener);
        }
    
        public StringCacheRequest(String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
            super(url, listener, errorListener);
        }
    
        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String parsed;
            try {
                parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            } catch (UnsupportedEncodingException e) {
                parsed = new String(response.data);
            }
            return Response.success(parsed, CacheControlHttpHeaderParser.parseCacheHeaders(response, CACHE_TIME));
        }
    }
    

    总结

    Volley的源码对于开发者还是比较友好的,没什么地方绕弯子。看起来也是行云流水,酣畅淋漓。对于安卓开发者从长远看还是推荐使用OkHttp,OkHttp效率更高,设计更为合理。并且支持Http2、SPDY。

    相关文章

      网友评论

          本文标题:Volley源码解析

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