欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从源码角度分析Volley的请求流程

程序员文章站 2024-03-24 09:58:58
...

这两天断断续续的看了下Volley的源码。正所谓,纸上得来终觉浅 绝知此事要躬行。我得写个博客讲述下。巩固下,加深印象。所以,今天,咱们来分析一波Volley的整个请求流程。

简单的说下,平时我们使用Volley,就是创建各种Request,什么StringRequest或者JsonObjectRequest。把它们add进去Volley的RequestQueue里面,然后调用个RequestQueue.start方法即可开始网络请求,最后从传进去的回调里面拿到网络请求回来的数据。这就是咱们平时的一个使用volley的操作流程。

那今天,咱们就要了解下这个流程的具体实现。


CacheDispatcher

众所周知,调用RequestQueue的start方法后,就可以开始执行网络请求了。那这里面肯定就是个入口。咱们就直接看start方法的源码即可

RequestQueue.Java -141行

/**
 * Starts the dispatchers in this queue.
 */
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方法,然后接着new了个CacheDispatcher,传了一堆参数进去。然后把它start起来了,第一眼看到,start?有点像线程。的确,CacheDispatcher就是继承于线程,在run方法里面做了缓存请求的调度工作,就是走缓存不走网络,直接缓存里面拿数据。以下是传入的几个参数对像:

RequestQueue.Java 

/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

/** Cache interface for retrieving and storing responses. */
private final Cache mCache;

/** Response delivery mechanism. */
private final ResponseDelivery mDelivery;

mCacheQueue:存放走缓存请求的请求队列;

mNetworkQueue:存放走网络请求的请求队列;

mCache:实现缓存的类。Cache是个接口,这里Volley实现Cache接口的是DiskBasedCache类。把缓存数据存在硬盘里面。

提供get方法和put方法读取缓存数据。

mDelivery:用于分发请求后拿到的数据。ResponseDelivery也是个接口,具体实现类是ExecutorDelivery。提供postResponse和postError用于分发成功和错误的回调。

这时候,咱们看看CacheDispatcher的run方法,毕竟它是个线程,主要实现就是看run方法里面代码了。

@Override
public void run() {
    if (DEBUG) VolleyLog.v("start new dispatcher");
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // 初始化缓存,就是把磁盘的缓存数据加载到内存中去
    mCache.initialize();

    while (true) {
        try {
            //从队列
            final Request<?> request = mCacheQueue.take();
            request.addMarker("cache-queue-take");

            // 判断请求是否取消了,取消就不执行了。所以,如果在请求已经开始后,再去cancel那就没啥卵用了
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }

            // 通过这个请求的CacheKey从缓存里面找找有没有这个请求的缓存,getCacheKey的值默认是url
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // 如果没有缓存,那就把请求丢到网络请求队列mNetworkQueue里面去,让它直接请求网络加载数据
                mNetworkQueue.put(request);
                continue;
            }

            // 如果缓存过期,则直接请求网络加载数据。这里的判断过期是用的TTL,意思是缓存的过期时间
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }

            // 解析缓存的数据为response对象
            request.addMarker("cache-hit");
            Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
  
            if (!entry.refreshNeeded()) {
                // 判断是否需要刷新数据,false则直接把上面解析的缓存数据response分发下去,最终回调到success和error的监听里去
                mDelivery.postResponse(request, response);
            } else {
                // 一些基本操作                              
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);

                // Mark the response as intermediate.
                response.intermediate = true;

                // 如果缓存需要刷新,则把在把缓存数据分发下去后,
                // 再去执行一次网络请求用以刷新数据。
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mNetworkQueue.put(request);
                        } catch (InterruptedException e) {
                            // Not much we can do about this.
                        }
                    }
                });
            }

        } catch (InterruptedException e) {
            // 用于退出和结束线程
            if (mQuit) {
                return;
            }
            continue;
        }
    }
}

上面的注释也写的很明白了。CacheDispatcher做的事情也很简单。咱们再来描述一次,首先,从缓存请求队列里面拿到请求,先判断这个请求是否给取消了,如果给取消了,那就continue,继续拿去队列里面拿下一个请求。然后,通过请求的CacheKey去拿缓存里面的数据,CacheKey的值默认为url。拿到缓存的数据后,先判断下这个缓存的数据是否为null,如果为null,那就代表没有缓存,那就把这个请求丢到网络请求队列mNetworkQueue里面去轮询执行网络请求。如果缓存数据不为null,那就判断有没有过期,如果过期了,也一样,丢到mNetworkQueue里面去。最后,把这个缓存数据解析成Response对象,然后判断这个请求是否需要刷新数据了,如果不需要,那就直接把这个缓存的请求结果分发出去,最后也是跑到咱们请求的success和error回调里面去。如果需要刷新,还是老规矩,丢到mNetworkQueue里面去。

这样一来,这个CacheDispatcher的工作流程咱们就分析完了。接下来咱们看看另一个调度器。

NetworkDispatcher

顾名思义,这玩意就是网络调度器。跟上面的缓存调度器。差不多,也是个线程。

RequestQueue.java  148行

for (int i = 0; i < mDispatchers.length; i++) {
    NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
            mCache, mDelivery);
    mDispatchers[i] = networkDispatcher;
    networkDispatcher.start();
}

在RequestQueue的start方法中,这NetworkDispatcher 有着多个,具体多少个,看mDispatchers数组的长度,而这个mDispatchers就是个全是NetworkDispatcher 对象的线程池。默认个数是4个,这个可以通过RequestQueue构造方法来进行修改。代码如下:

/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
public RequestQueue(Cache cache, Network network) {
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }

public RequestQueue(Cache cache, Network network, int threadPoolSize,
        ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

也就是说,最多同时有4个请求可以同时请求,而多出来的,就要在队列里面排队了。既然NetworkDispatcher 也是个线程,那咱们就来看看run方法即可

@Override
public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        Request<?> request;
        try {
            // 从请求队列里面拿出请求
            request = mQueue.take();
        } catch (InterruptedException e) {
            // 用于结束线程
            if (mQuit) {
                return;
            }
            continue;
        }

        try {
            request.addMarker("network-queue-take");

            // 跟CacheDispatcher一样,一开始也是判断请求是否取消了
            // 如果是,那就不执行后面的代码,直接continue继续处理下一个请求
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }

            addTrafficStatsTag(request);

            // 直接通过mNetWork的performRequest去执行网络请求。
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

            // 判断返回的数据是否有变化和判断这个响应的数据是否分发过
            // 如果两者都是true,那就不分发了,因为数据没有改变,还是跟上次的一样,而且也分发过了。那就没有再次分发的必要了
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // 通过request的parseNetworkResponse方法来处理返回的数据,把它变成对应的格式,比如String,JsonObject
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // 判断是否需要缓存,如果需要,那就通过mCache写入缓存。shouldCache的返回值默认是true
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // 把request标记为已经分发过数据。并且把response通过mDelivery分发下去
            request.markDelivered();
            mDelivery.postResponse(request, response);
        } catch (VolleyError volleyError) {//分发error
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
        } catch (Exception e) {//分发error
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
        }
}
(可能你会问,为啥这里的代码是白的,上面的都是黑背景的。主要是这个csdn的文章编辑器的锅。。怪他怪他。我排版了半天,都搞不好,最后只能这样了。大家将就一下。)

上面就是NetworkDispatcher 处理请求的整个流程。代码比较清晰。还是总结一下流程。

首先队列里面拿到具体的某个请求,判断这个请求是否取消了,如果是,那就continue继续下一个请求。如果不是,那就调用mNetwork的performRequest方法去执行网络请求。mNetwork是Network接口,具体实现是BasicNetwork这个类。BasicNetwork主要根据返回的响应码做一些处理,还有处理超时的请求重试和提取响应头的信息。而具体的网络请求,是通过BasicNetwork类里面的mHttpStack来执行的。HttpStack也是个接口,具体的实现类有两个,HurlStack和HttpClientStack。前者是用HttpUrlConnection去执行具体的网络请求,而后者,看名字就知道是用HttpClient来执行的网络请求。

Volley.java-42行

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

在Volley的newRequestQueue方法里面可以看到。在sdk版本号为大于等于9时,就用HttpUrlConnection,而9以下的,就换成HttpClient来实现。因为HttpUrlConnection在2.2版本(sdk版本号为8)及以下安卓系统版本有个bug,调用close()函数会影响连接池,导致连接复用失效,使用HttpURLConnection需要关闭keepAlive。

不过现在都什么年代了。都很少存在4.4以下版本的手机了。基本都是5.0以上,这些就不用管了。

这一样一来,整个请求的发起,队列轮询请求,到最后执行请求并返回用于分发的Response对象。整个流程也就差不多了。还剩个分发的过程,咱们就把这整个请求流程至响应的流程分析完了。现在来看看分发是怎么分发的。

还记得我们看源码里面,分发都是一句代码

mDelivery.postResponse(request, response);
mDelivery.postError(request, volleyError);
也就是上面这两句。成功的分发和出错的分发。都是由mDelivery对象来处理。

/** For posting responses and errors. */
private final ResponseDelivery mDelivery;

而ResponseDelivery也是个接口,具体的实现类是ExecutorDelivery。看两眼ExecutorDelivery的代码

ExecutorDelivery

/**
 * Delivers responses and errors.
 */
public class ExecutorDelivery implements ResponseDelivery {
    /** 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);
            }
        };
    }

    /**
     * Creates a new response delivery interface, mockable version
     * for testing.
     * @param executor For running delivery tasks
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

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

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /**
     * A Runnable used for delivering network responses to a listener on the
     * main thread.
     */
    @SuppressWarnings("rawtypes")
    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();
            }
       }
    }
}

里面的postResponse和postError方法最后都会调用mResponsePoster对象的execute方法去执行一个ResponseDeliveryRunnable 。ResponseDeliveryRunnable 里就是直接调用了request的deliverResponse和deliverError去分发success和error。而deliverResponse和deliverError是request接口里面需要实现的一个抽象方法。我们随便看一下JsonRequest的实现。

JsonRequest.java-64行

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

而这里的mListener是什么呢,就是那个我们new一个request请求是传进去的成功回调。所以,最后是调用了我们传进去的成功监听事件,把具体的数据传回给我们。而error的情况也是一致的。

上面还有个东西漏掉了,就是mResponsePoster对象是个啥玩意。


ExecutorDelivery.java

private final Executor mResponsePoster;
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);
        }
    };
}

RequestQueue.java-123行

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

可以看到,这里就是简单的实现了一下Executor接口,实际上就是把传进来的runnable丢到主线程去处理。所以,volley的成功和出错回调都在主线程。

最后来一张图简洁的描述下

从源码角度分析Volley的请求流程

至此。整个volley的请求流程就完了。有什么问题或者哪里写错字了下面评论告诉我。谢谢大家,谢谢党和组织。山水有相逢,咱们来日再见。