Volley源码分析

以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的网络请求部分没有具体分析。
如果有哪里不对的,希望指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • Volley源码分析之流程和缓存 前言 Android一开始提供了HttpURLConnection和HttpCl...
    大写ls阅读 620评论 0 6
  • 我的博客: Volley 源码分析 Volley 的使用流程分析 官网示例 创建一个请求队列 RequestQue...
    realxz阅读 2,040评论 1 11
  • 前言 本文是一篇日常学习总结性的文章,笔者通过分析经典网络框架Volley的源码,望以巩固Android网络框架中...
    Armstrong_Q阅读 574评论 0 7
  • 关闭了朋友圈,取关了一些公众号,删除了网易新闻,世界安宁祥和了许多……
    简单滴人生阅读 254评论 0 0
  • 今天的朋友圈再次被《小镇》刷屏,虽然这已经不是第一次了,从首演到全国巡演,到团帅姐夫的梅花奖,再到戏曲界的奥...
    心依87阅读 709评论 2 1