Volley源码分析

作者: stefanJi | 来源:发表于2017-08-09 22:01 被阅读0次

    以StringRequest为例子,分析Volley是怎样执行一个网络请求的。

    先看实现Request抽象类的StringRequest

    public class StringRequest extends Request<String> {
        private Listener<String> mListener;
    
        /**
         * Creates a new request with the given method.
         *
         * @param method the request {@link Method} to use
         * @param url URL to fetch the string at
         * @param listener Listener to receive the String response
         * @param errorListener Error listener, or null to ignore errors
         */
        public StringRequest(int method, String url, Listener<String> listener,
                ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
        }
    }
    

    Request有3个参数:

    • method 对应请求的方式
    • url 请求的地址
    • errorListener 发生错误时回调

    StringRequest多了一个自己的listener作为成功请求的回调接口。
    StringRequest实现了parseNetworkResponse方法。这个方法会传入一个NetworkResponse对象,包装了请求的响应结果。

    然后根据响应结结果header里的编码格式构造一个String对象,最后使用封装了最终请求的Response类构造一个代表成功的response返回。

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            //根据编码格式构造字符串
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            //如果格式不支持编码,就构造一个默认的UTF-8编码的字符串
            parsed = new String(response.data);
        }
    
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
    

    NetworkResponse封装了具体的请求内容:

    public class NetworkResponse implements Serializable{
        /**
         * @param statusCode the HTTP status code
         * @param data Response body
         * @param headers Headers returned with this response, or null for none
         * @param notModified True if the server returned a 304 and the data was already in cache
         * @param networkTimeMs Round-trip network time to receive network response
         */
        public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
                boolean notModified, long networkTimeMs) {
            this.statusCode = statusCode;
            this.data = data;
            this.headers = headers;
            this.notModified = notModified;
            this.networkTimeMs = networkTimeMs;
        }
    }
    
    • StringReqeust 负责封装请求
    • NetworkResponse 负责封装从服务器返回的请求
    • Response 负责构造最终的结果。
    一个Volley RequestQueue的创建到运行

    RequestQueue的创建

    Volley.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){
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }
        else{  
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }
        queue.start();
        return queue;
    }
    

    其中执行了一些对象的创建工作:

    1. 创建缓存文件,文件名默认为volley
    2. 创建一个UserAgent字符串,代表HTTP头里的客户端身份。默认为包名+APPb版本号。
    3. 创建执行网络请求的工具。HurlStack(Android 2.3及以上)或HttpClientStack(Android 2.3以下)。Stack负责真正的HTTP请求。HurlStack使用的是HttpURLConnection;HttpClientStack使用的是HttpClient
    4. 创建一个Netwoker对象。Netwoker通过调用Stack进行网络访问,并将执行结果封装为NetworkResponse对象。
    5. 创建一个RequestQueue对象,同时创建一个DiskBasedCache缓存对象,作为本地缓存。
    6. RequestQueue创建完毕之后,就调用queue.start()开始不断执行添加到RequestQueue中的请求。

    RequestQueue的创建:

    RequestQueue有3个构造函数:

    //最终调用
    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, int threadPoolSize) {
        //会调用最终的构造函数
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
    
    public RequestQueue(Cache cache, Network network) {
        //会调用第二个
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }
    

    三个构造函数最终都是调用最上面那个。第二个构造函数,则是创建了一个ExecutorDelivery对象,并在创建时传入了拥有UI线程的handler
    可见ExecutorDelivery是与主线程打交道的工具。

    最后一个构造函数,则创建了:

    • 一个NetworkDispatcher数组,数组大小为threadPoolSize,默认为4。

    NetworkDispatcher

    NetworkDispatcher继承Thread。RequestQueue在创建时,创建了一个NetworkDispatcher数组,实际就是创建了一个线程数组。

    public class NetworkDispatcher extends Thread {
        /** The queue of requests to service. */
        private final BlockingQueue<Request<?>> mQueue;
        /** The network interface for processing requests. */
        private final Network mNetwork;
        /** The cache to write to. */
        private final Cache mCache;
        /** For posting responses and errors. */
        private final ResponseDelivery mDelivery;
        /** Used for telling us to die. */
        private volatile boolean mQuit = false;
    }
    

    NetworkDispatcher拥有:

    • BlockingQueue<Request<?>> 一个保存者Request的阻塞队列
    • Network 执行网络访问,并返回结果
    • Cache 本地缓存
    • ResponseDelivery 负责与UI线程打交道。ReqeustQueue在创建时,创建的ExecutorDelivery就是一个实现了ResponseDelivery接口的类。
    • volatile boolen mQuit 一个多线程可以安全访问的布尔,负责结束线程

    NetworkDispatcher既然继承自Thread,那么就实现了run方法:

    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);
            }
        }
    }
    
    1. 设置当前线程的优先级为后台线程
    2. 从阻塞队列中获取一个request请求。这里使用的take方法,这个方法会阻塞线程,直到线程从队列中拿到了东西。
    3. 给request添加network-queue-take标记
    4. 调用netwoke的performRequest方法,并传入requset获取请求的结果networkResponse
    5. 给request添加network-http-complete标记
    6. 通过response判断是否是304状态码,如果是就调用request.finish(),并跳过下面步骤。否则继续下面的步骤。
    7. 使用request.parseNetworkResponse(networkResponse);创建一个Response对象response
    8. 给request添加network-parse-complete标记
    9. 将请求requset和结果response写入缓存。
    10. 调用request.markDelivered();表明,当前请求已被解决
    11. 调用mDelivery.postResponse(request, response);将请求和结果传递到UI线程。

    这就是一个NetworkDispatcher线程执行一个Request的大致流程。

    ResponseDelivery->ExecutorDelivery

    NetworkDispatcher线程中,最终结果是通过mDeliery这个ResponseDelivery对象传递到UI线程的。在创建NetworkDispatcher时,mDeliery被赋予的实际是ExecutorDelivery的实例。ExecutorDeliveryResponseDelivery接口的实现类。

    ExecutorDelivery类的postResponse方法:

    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }
    
    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }
    

    postResponse 方法调用了 mResponsePosterexecute()方法,并传入了一个Runnable对象。

    mResponsePoster 对象是一个 Executor对象,并在 ExecutorDelivery 并创建时就创建。它的execute方法,就是调用 RequestQueue在创建 ExecutorDelivery 传入的拥有UI线程的Looper的handlerpost(Runnable)方法。

    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }
    

    handler post 的runnable对象是一个内部类:在 run 方法里调用了Request对象的deliverResponse deliverError finish方法。

    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
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }
            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }
            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }
            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
    }
    

    经过这样的转化,相当于Request的几个方法就是在UI线程执行了:

    //Requset自己实现的deliverError
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }
    
    //StringRequest实现的Request的抽象方法deliverResponse
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }
    

    Requset的deliver方法实际就是调用的在创建Request的时候,传入的Listener接口的方法。


    Volley每创建一个消息队列,就创建了4个这样的NetworkDispatcher线程一直从请求队列中获取请求,然后执行,最后post到UI线程。4个线程都去拿请求,不会发生冲突是因为请求放在了BlockingQueue中,保证了每次take获取操作只有一个线程能获取。而且Volley的BlockingQueue使用的是PriorityBlockingQueue,这个队列在拥有BlockingQueue功能的同时,还对队列中的请求进行了排序。


    add请求操作

    RequestQueue中的线程们一直在跑着,它们不断的有序的从消息阻塞队列中拿请求,执行请求,传递到UI线程。

    ReqeustQueue的add操作就是将请求添加到请求队列中。

    // 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;
    }
    
    • 如果加入的请求没有被缓存过,就直接加入到消息队列。直接加入不用获取消息队列的锁。因为消息队列是个BlockingQueue,本就支持并发操作。而且即使add操作是在UI线程,也不会阻塞UI线程,因为
      mNetworkQueue.add(request);内部是调用BlockingQueueoffer操作,offer入队操作不会阻塞线程,如果入队失败,它会返回false

    以上只是我分析的Volley的RequestQueue的大概执行过程。其中还有CacheQueue WaitRequests CurrentRequests等一些细节和HurlStack和HttpClientStack的网络请求部分没有具体分析。
    如果有哪里不对的,希望指正。

    相关文章

      网友评论

        本文标题:Volley源码分析

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