深入了解Volley如何执行一个Request的流程

Volley主要类构成

  • Volley: 框架同名类,负责创建RequestQueue对象
  • Request:代表一个网络请求的抽象类,具体实现子类有(StringRequest,JsonRequest,ImageRequest),需添加到RequestQueue中操作。包含了url,请求方法,请求Header,请求Body,请求的优先等级信息。
  • RequestQueue:核心类,负责维护所有的Request对象,完成请求操作。该类中有两个基于优先级的Request队列mCacheQueue,mNetWorkQueue(PriorityBlockingQueue),分别是缓存请求队列,网络请求队列。该数据结构支持对添加进来的Request进行优先级排序(所以可以将理财列表内的请求设置成优先级最高),还有一个mCurrentRequest(SetRequest类型,用来维护一个已经加入到Request中,并且还没有完成的请求;还有一个等待请求的集合,mWaitingQueue,如果一个请求正在处理并且可以被缓存,那么后续的相同的url的请求,将被放入到等待队列。
  • CacheDispatcher:缓存调度线程类,不停的从缓存队列中取出Request请求,进行处理。
  • NetworkDispatcher:网络调度线程类,不断的从网络请求队列中取出Request去处理。
  • Cache:缓存接口,代表了一个可以获取请求结果,存储请求结果的缓存。
  • DiskBasedCache:基于disk的缓存实现类。
  • BasicNetwork:Volley中默认的网络接口实现类
  • Response:封装了经过解析后的数据,用于传输
  • ByteArrayPool:byte[] 的回收池,用于byte[]的再回收利用,减少了内存的分配和回收,是Volley提高性能的优化之一。
  • RetryPolicy:重试策略接口类
  • ResponseDelivery:请求结果的传输接口,用于传递请求结果或请求错误。
  • ExcutorDelivery:请求结果传输接口具体实现类。利用Handler将缓存调度线程或网络调度线程中产生的请求结果和请求错误传输到主线程的回调函数中。

Volley一个请求的执行过程

volley官方文档给出的执行一个请求的步骤是:

RequestQueue mQueue = Volley.newRequestQueue(context);
mQueue.add(request);

为什么创建一个mQueue以后,直接放入一个request,就可以执行该request了呢?发起网络请求的代码在哪里呢?这里看下newRequestQueue()做了哪些操作:

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        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 {
                // 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 DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

可以看到创建RequestQueue后,调用了start()方法,再看下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;
            VolleyCompat.getInstance().setNetworkDispatcherPriorityMax(networkDispatcher);
            networkDispatcher.start();
        }
    }

通过源码可以查看到,CacheDispatcher,NetworkDispatcher都是继承了Thread类,这里相当于启动了一个缓存调度线程,四个网络调度线程,这里先不分析缓存,看网络调度线程做了些什么操作:

 @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            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);

                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);

                //volley扩展:(2015-11-10)当出现错误,如设备无网络,请求服务器出错,并且有缓存时,需要将缓存内容展示出来
                VolleyCompat.getInstance().showCacheWhenErrorOccur(mCache,request,mDelivery,volleyError,startTimeMs);

            } catch (Exception e) {
                e.printStackTrace();
//                    VolleyLog.e(e, "Unhandled exception %s", e.toString());
//                    VolleyError volleyError = new VolleyError(e);
//                    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
//                    mDelivery.postError(request, volleyError);

                //volley扩展:(2015-11-10)当出现错误,如设备无网络,请求服务器出错,并且有缓存时,需要将缓存内容展示出来
                VolleyError volleyError = new VolleyError(e);
                VolleyCompat.getInstance().showCacheWhenErrorOccur(mCache,request,mDelivery,volleyError,startTimeMs);
            }
        }
    }

很简单,网络调度线程先是从队列mQueue里取出一个request,然后判断该request是否已经取消,如果没有被用户取消,则调用mNetwork的performRequest(request)发起网络请求,这里的mNetWork是一个BasicNetWrok,看下performRequest()的实现,

@Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers);
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                    // A HTTP 304 response does not have all header fields. We
                    // have to use the header fields from the cache entry plus
                    // the new ones from the response.
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                    entry.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // Some responses such as 204s do not have content.  We must check.
                if (httpResponse.getEntity() != null) {
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  responseContents = new byte[0];
                }

                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(networkResponse);
                }
            }
        }
    }

首先通过调用mHttpStack.performRequest(request,headers) 发起请求,返回的首先是一个org.apache.http.HttpResponse对象,根据statusCode判断,如果是SC_NOT_MODIFIED,并且支持缓存,那么直接加载本地缓存,否则根据httpResponse.getEntity()方法来构建一个NetworkResponse的对象。

现在回到NetWorkDispatcher的run()方法里,当得到一个NetWorkResponse对象后,接着调用request.parseNetWorkResponse(networkResponse)来对结果数据进行解析,解析数据的过程是什么样的呢?查看下request的parseNetWorkResponse()方法。

 abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

是给抽象方法,再看Request,也是一个抽象类,说明要想发起请求,必须创建一个Request的子类,并且自己实现该方法,由该Requet来决定如何解析这个networkResponse。好,那先看下Volley自己提供的StringRequest中该方法是怎么实现的:

@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, HttpHeaderParser.parseCacheHeaders(response));
    }

对比下另外一个Volley提供的JsonObjectRequest的parseNetWorkResponse实现:

@Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }

其中Response.success()会new 出一个Response对象,该对象中的result变量也是泛型,并且该result的类型也是由新建Request类的子类的时候制定的,这样这个请求就得到了一个Response对象。

接着回到NetWorkDispatcher的run()方法,接着分析下边流程:

mDelivery.postResponse(request, response);

这个mDelivery又是什么东西呢?查看NetWorkDispatcher源码,其实是一个ResponseDelivery类型变量,而查看ResponseDelivery发现是一个接口,从这里可以看到,Volley中处处用到了面向接口的编程思想,使得使用者可以自己定义子类型,提供相关服务。那么这个mDelivery是在哪里制定的呢?其实是在RequestQueue的构造方法中指定,然后在start()方法中传递给NetWorkDispatcher的:

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
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;
            VolleyCompat.getInstance().setNetworkDispatcherPriorityMax(networkDispatcher);
            networkDispatcher.start();
        }
    }

好,接着看ExecutorDelivery的postResponse()方法:

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

调用了mResponsePoster的execute()方法,接着看下mResponsePoster:

/** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     * @param handler {@link Handler} to post responses on
     */
    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,想必熟悉handler机制的童鞋们就特别想知道这个handler是哪个线程的handler了吧?会不会是UI Thread的呢?接着看

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

可以看到,确实是UI 线程的,这样我们就可以将ResponseDeliveryRunnable放到UI线程里去执行啦。好了,那看下让UI线程执行什么操作呢?

 @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();
            }
       }

其中这个mRequest,就是我们这次网络请求发送的Request,Response就是我们这次请求返回的结果封装类,再次提醒,该Response里的result变量的类型,就是我们在新建Request子类时候指定的类型。好的,万里长城差最后一步了,再看下Request的deliverResponse方法()

/**
     * Subclasses must implement this to perform delivery of the parsed
     * response to their listeners.  The given response is guaranteed to
     * be non-null; responses that fail to parse are not delivered.
     * @param response The parsed response returned by
     * {@link #parseNetworkResponse(NetworkResponse)}
     */
    abstract protected void deliverResponse(T response);

也是一个抽象方法,那么我们还是看下JsonRequest类的该方法是如何实现的:

@Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

mListener是Response的一个内部接口:

public class Response<T> {

    /** Callback interface for delivering parsed responses. */
    public interface Listener<T> {
        /** Called when a response is received. */
        public void onResponse(T response);
    }

那mListener是在哪里赋值的呢?

public JsonRequest(int method, String url, String requestBody, Listener<T> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
        mRequestBody = requestBody;
    }

也就是在创建JsonRequest的时候,就要指定这个Response.Listener,这里也是面相接口编程,因为不知道将来谁会对这个Request请求的结果数据感兴趣,那么就在设计框架的时候,调用listener的onResponse()方法,这样以后谁对结果数据敢兴趣,那就实现接口,并且在创建相应Request的时候,传递进来就可以了。

有一个细节值得注意的就是,在Volley原生Request类的构造函数里,是只有Response.ErrorListener,并没有Response.Listener的:

/**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

也就是说请求如果发生错误信息了,那么必须要有能通知到的listener来处理,但是请求成功后,可以任何事情也不做。

正查数据请求过来的流程分析清楚后,最后再看下请求出错后的处理逻辑:

在ExecutorDelivery的run()方法里:

// Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

而在Request的deliverError()方法中:

/**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

也印证了上面的请求出错必须有处理的猜测。

至此,Volley一个请求的执行流程就分析完成了,总结下:

  1. Volley在主线程中创建一个request请求后,就会将该请求放到队列中,然后后台线程(NetWorkDispatcher)从队列中取出请求,调用网络相关类,执行请求,得到返回结果后,通过ExecutorDelivery将一个Runnable对象放到UI线程的消息池里去执行。
  2. 在创建Request的时候,需要制定Response.Listener,Response.ErroListener对象,因为在Request执行完后,Volley会通过handler机制,将在UI线程里调用这些listener的相关方法,从而使应用可以在这些listener的相关方法里,获得这些请求结果数据,从而实现刷新UI等操作。
时间: 2024-10-03 23:28:56

深入了解Volley如何执行一个Request的流程的相关文章

Volley使用技巧-----自定义Request

Volley使用技巧-–自定义Request 题外话 最近在和网络请求较劲,也初步接触了下volley,看了各路大神的各种理论分析,现在把自己使用volley的一点小经验拿出来和大家分享,特别是在cookie这个小问题上,由于人笨了,纠结了一段时间. Volley简介 Google I/O 2013上,Volley发布了.Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮.这是Volley名称的由来: a burst or emission of many thin

带您理解SQLSERVER是如何执行一个查询的

原文地址:http://www.cnblogs.com/lyhabc/p/3367274.html 看这篇文章之前,阁下可以先看一下下面的文章 SQLSERVER独特的任务调度算法"SQLOS" SQL Server SQLOS 的任务调度[转] 翻译自: http://rusanu.com/2013/08/01/understanding-how-sql-server-executes-a-query/ http://www.codeproject.com/Articles/6303

使用Volley来写一个List列表(Valley可以解决很大一部分android请求server的问题)

先上效果图: 先写一个Volley的请求的类: public void fetchData() { String url = "http://2.novelread.sinaapp.com/framework-sae/index.php"; // String body = ""; // try { // mEntity = new StringEntity(body); // } catch (UnsupportedEncodingException e1) {

springbank 开发日志 springbank是如何执行一个handler的requestMapping对应的方法的

占位 从dispatcher说起,方法doDispatch(Map request)的参数request是一个通过解析来报报文新城的map 要以request为参数执行一个handler,自然要先找到这个handler,这个工作是由getHandler(request)完成,其实找到的是handlerExecutionChain,是一个包含了拦截器链和真正的handler在内的执行链, 方法内的步骤建立在handlerMappings的基础上,

在Linux中定时执行一个程序的方法之at命令

/*********************************************************************  * Author  : Samson  * Date    : 04/29/2014  * Test platform:  *              3.11.0-12-generic #19-Ubuntu  *              GNU bash, version 4.2.45  * ****************************

如何在linux中执行一个脚本

---恢复内容开始--- 如何在LINUX中在系统启动时自动执行一个执行脚本 如果是开机马上执行的脚本,可以将脚本写到rc.local中: 如果是用户登录后自动执行脚本,可以将脚本写到相应的用户目录下"-/.bash_profile",若脚本"-/.bash_profile"不存在,可以直接拷贝"/etc/profile"命名为"-/.bash_profile": 如果是要任一用户登录后自动执行脚本,可以将脚本写到"

一定间隔时间下重复执行一个函数的几个方法

如果有个操作,我们需要过一会儿再做,或者每隔一段时间就要做一次.可以有很多种做法. 独立线程 是的,对.NET Framework本身一知半解的程序员才会使用这种方案.不过,现实中这个方案其实并不少见. public static void Repeat(this Action action, TimeSpan interval) { new Thread(new ThreadStart(() => { while(true) { Thread.Sleep(interval); action()

Ansible Tower系列 三(使用tower执行一个任务)【转】

创建playbook Tower playbook 项目默认存在 /var/lib/awx/projects/ su - awx cd projects/ mkdir ansible-for-devops && cd ansible-for-devops cat main.yml << EOF --- - hosts: all gather_facts: no tasks: - name: Check the date on the server. command: date

Ansible Tower系列 四(使用tower执行一个命令)【转】

在主机清单页面中,选择一个主机清单,进入后,选择hosts里的主机 Paste_Image.png 点击 RUN COMMANDS MODULE 选择 commandARGUMENTS 填写 ifconfig eth0MACHINE CREDENTIAL 选择 ssh登陆账号Verbosity 选择 3 (Debug) Paste_Image.png 点击 Launch,查看输出 转自 Ansible Tower系列 四(使用tower执行一个命令) - 简书http://www.jianshu