Volley源码流程解析

Volley的使用十分简单,我们从网络请求开始说起,你只需要做4件事。

1、新建RequestQueue

2、新建Request对象及数据回调

3、把Request对象添加进RequestQueue

4、RequestQueue.Start();

 

如果你已经有了一个RequestQueue直接做2、3步即可

 

RequestQueue mQueue = Volley.newRequestQueue(context);  

StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
 
mQueue.add(stringReqauest); 

这样就完成了一次网络请求并处理获取的数据

让我们来看看内部是怎么实现的

首先是获取请求队列RequestQueue

一般来说我们会使用Volley.newRequestQueue(context);

public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}
//同上
return newRequestQueue(context, stack, -1);


我们来看这一段:

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 {
            // 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;
    if (maxDiskCacheBytes <= -1)
    {
       // No maximum size specified
       queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    }
    else
    {
       // Disk cache size specified
       queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
    }

    queue.start();

    return queue;
}


这一段里面根据实际手机的Android版本创建了一个HttpStack,HttpUrlConnection在Android2.2以前有个bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive,详细的可以看上面注释里给出的链接。谷歌认为HttpUrlConnection更适合安卓,有兴趣可以看看这个:http://android-developers.blogspot.com/2011/09/androids-http-clients.html

然后创建一个network对象,并用network和stack对象作为参数生成RequestQueue返回给我们。

由于我们没有指定线程池的大小,默认会生成1个NetWorkDsispatcher(extendsThread

)数组,这个数组大小为4,同时文件缓存被指定为5MB大小。

这里Volley选择自己来管理线程池,而不是使用java提供的ThreadPool。

现在让我们来看看Request对象。

Volley里面有多种Request,StringRequest,JsonObjectRequest等,里面的处理逻辑只在parseNetworkResponse这个方法中有较大差别,在这里实现数据类型转换,也就是说,如果你有需要,也可以自己继承Request来写一个自己的转换,例如XML解析什么的。

在上面StringRequest对象生成中,我们调用了:

public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
    this(Method.GET, url, listener, errorListener);
}

这里额外指定了以GET方法进行网络请求

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

然后把参数交由父类处理,自己处理成功回调mListener

那我们来看看父类

public Request(int method, String url, Response.ErrorListener listener) {
    mMethod = method;
    mUrl = url;
    mIdentifier = createIdentifier(method, url);
    mErrorListener = listener;
    setRetryPolicy(new DefaultRetryPolicy());

    mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}

这里生成了一个唯一ID,并且设置了一个网络请求失败重试策略,这里默认不重新请求

 

现在让我们来看一下requestQueue.add()

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) {
        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.
    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;
    }
}


这个方法里面首先为这个request设置一个队列位置标识,然后判断是否需要缓存(默认为需要),若不需要则直接添加进网络请求队列并返回此request。

若此请求需要缓存,则根据此request的key从等待队列中查找是否已经有被缓存,如果之前已经请求过,就把它加进key对应的队列中。如果没有就把key加进等待队列,同时把request加进缓存队列。

 

我们来看最后一步RequestQueue.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;
        networkDispatcher.start();
    }
}

这里面做的第一件事是把mCacheDispatcher线程停掉,并把线程池全停掉

public void stop() {
    if (mCacheDispatcher != null) {
        mCacheDispatcher.quit();
    }
    for (int i = 0; i < mDispatchers.length; i++) {
        if (mDispatchers[i] != null) {
            mDispatchers[i].quit();
        }
    }
}


mDispatchers就是一开始初始化RequestQueue时生成的NetWorkDsispatcher数组,大小为4。

然后重新为mCacheDispatcher赋值,我们先来看看这个mCacheDispatcher都干了些什么:

CacheDispatcher继承了Thread,重写其run()方法:

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

    Request<?> request;
    while (true) {
        // release previous request object to avoid leaking request object when mQueue is drained.
        request = null;
        try {
            // Take a request from the queue.
// 这里使用了BlockingQueue实现的生产者消费者模式,request在RequestQueue.add()被调用时生产,//若Dispatchers线程已经被启动,而CacheQueue中已经没有request就会被阻塞
            request = mCacheQueue.take();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
            continue;
        }
        try {
            request.addMarker("cache-queue-take");

            // If the request has been canceled, don't bother dispatching it.
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }

            // Attempt to retrieve this item from cache.
// 这个mCache就是生成ReqiuestQueue时作为参数的DiskBasedCache,Volley只实现了文件一级缓存,//有需要的可以自己加一个内存缓存
//如果文件缓存不存在就把这个request加入网络请求队列,继续执行下一个request
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // Cache miss; send off to the network dispatcher.
                mNetworkQueue.put(request);
                continue;
            }

            // If it is completely expired, just send it to the network.
//缓存已经过期,进行网络请求
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }

            // We have a cache hit; parse its data for delivery back to the request.
<span style="white-space:pre">	</span>    //缓存可用
            request.addMarker("cache-hit");
            Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
<span style="white-space:pre">	</span>    //缓存无须刷新就直接发送
            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.
<span style="white-space:pre">		</span>//
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);

                // Mark the response as intermediate.
                response.intermediate = true;
<span style="white-space:pre">		</span>//如果需要刷新就发送请求结果给用户,并重新请求数据
                // Post the intermediate response back to the user and have
                // the delivery then forward the request along to the network.
                final Request<?> finalRequest = request;
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mNetworkQueue.put(finalRequest);
                        } catch (InterruptedException e) {
                            // Not much we can do about this.
                        }
                    }
                });
            }
        } catch (Exception e) {
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
        }
    }
}


总的来说在进行网络请求之前会先根据文件缓存进行判断,判断文件缓存是否已经过期,过期则直接进入网络请求队列,如果没过期但是已经不新鲜则先返回结果,然后进入网络请求队列刷新缓存。

关于文件缓存策略这一块还有有挺多可以说的,我们留到下一次再说。

然后我们再来看看网络请求这一块,也就是RequestQueue.start()余下的部分

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

这里分别为4个线程进行初始化,并传入工厂队列,在里面进行request的消费。

我们也来看一下NetworkDispatcher的实现。NetworkDispatcher继承了Thread类,重写run()方法:

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

可以看到这也是一个消费者,不停地从网络请求队列中取出reqeust进行网络请求,我们来看看mNetwork.performRequest(request);这里面做了什么

 

@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());
<span style="white-space:pre">	</span>//mHttpStack是之前判断Android版本后生成的HttpClientStack或者HurlStack
            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);
            }
            
            // Handle moved resources
            if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
               String newUrl = responseHeaders.get("Location");
               request.setRedirectUrl(newUrl);
            }

            // 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);
            }
            if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                  statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
               VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
            } else {
               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 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                         statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                    attemptRetryOnException("redirect",
                            request, new RedirectError(networkResponse));
                } else {
                    // TODO: Only throw ServerError for 5xx status codes.
                    throw new ServerError(networkResponse);
                }
            } else {
                throw new NetworkError(e);
            }
        }
    }
}

我们大概地讲一下这方法都做了什么

首先是添加header

然后获取响应状态及状态码,如果是304(所请求的资源未修改),则直接读取缓存并返回给用户。

判断是否是为301、302、204(可能不返回信息)

记录响应时间

判断200~299,即为服务器没有正常处理该请求,则抛出异常

如果上面没有这几种情况发生,那么返回新的请求中的内容+头部以及状态码

这个时候我们应该继续去看之前被我们忽略的networkResponse处理,在此之前,我们先看看HurlStack如何获取response。

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError {
    String url = request.getUrl();
    HashMap<String, String> map = new HashMap<String, String>();
    map.putAll(request.getHeaders());
    map.putAll(additionalHeaders);
    if (mUrlRewriter != null) {
//web技术,重写url,但是这里在创建HurlStack的时候传值为null,有需要可以自己传个进来
        String rewritten = mUrlRewriter.rewriteUrl(url);
        if (rewritten == null) {
            throw new IOException("URL blocked by rewriter: " + url);
        }
        url = rewritten;
    }
    URL parsedUrl = new URL(url);
//获取connection,如果mSslSocketFactory不为空还会为其设置SSLSocketFactory,网络安全用
    HttpURLConnection connection = openConnection(parsedUrl, request);
    for (String headerName : map.keySet()) {
        connection.addRequestProperty(headerName, map.get(headerName));
    }
    setConnectionParametersForRequest(connection, request);
    // Initialize HttpResponse with data from the HttpURLConnection.
    ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
    int responseCode = connection.getResponseCode();
    if (responseCode == -1) {
        // -1 is returned by getResponseCode() if the response code could not be retrieved.
        // Signal to the caller that something was wrong with the connection.
        throw new IOException("Could not retrieve response code from HttpUrlConnection.");
    }
//获取响应状态
    StatusLine responseStatus = new BasicStatusLine(protocolVersion,
            connection.getResponseCode(), connection.getResponseMessage());
    BasicHttpResponse response = new BasicHttpResponse(responseStatus);
    if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
//获取响应中的实体
        response.setEntity(entityFromConnection(connection));
    }
    for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
        if (header.getKey() != null) {
//为响应结果添加header,也要用在缓存中
            Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
            response.addHeader(h);
        }
    }
    return response;
}


这里要赞一下Volley这个框架,Http协议实践这一块写得超级棒,我们日常使用http连接时不会注意或使用的属性全都写上去,连协议版本都有,还保存、利用了header里缓存及其他各种属性,对于初学者来说是极好的示范。

我们继续来说一下这里面做了什么:

使用url打开连接,获取connection

设置connection的协议、超时时间、header

获取返回状态判断,返回包括header在内的数据

 

现在我们往回看一下在NetworkDispatcher中被我们忽略的剩余部分

@Override
public void run() {
<span style="white-space:pre">	</span>     //前略
            // 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()) {
//判断这个结果是否为304且响应已经被发送
//这里调用了finish之后,内部调用RequestQueue将request从队列中删除,并且根据cachekey将所有//相同请求全移除,加入到缓存队列中,这样就回到之前我们分析的缓存队列发现已经有缓存,则直接返//回缓存,不进行网络请求
                request.finish("not-modified");
                continue;
            }

            // Parse the response here on the worker thread.
//这里就是之前我们提到的进行数据处理的地方,如果自己想搞个xmlRequest就实现这个方法
            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是一个内含handler(主线程)的回调处理器,内部通过Executor发送线程,并在线程//内使用handler将回调发送到主线程
            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);
        }
    }
}


上面解释了mNetwork.performRequest(request)通过其内部的basicNetWork及basicNetWork内部的HttpStack完成了网络请求及返回状态处理,现在我们来解释一下:

拿到返回结果之后判断请求是否有更改,即请求如果是相同的那就交给deliver分发去,并且不再处理相同请求

如果有更改且允许缓存,那就写入缓存,然后通过deliver发送结果。

 

那么到这里Volley的流程解析就差不多完结了,我们最后来全局描述一下这个结构

 

RequestQueue将加入的请求加入到mCacheQueue有缓存的情况下处理缓存,没有再加入到mNetworkQueue,采用生产者消费者模式使用线程池对其进行请求,通过BasicNetWork中的HtppStack进行真正的网络请求,并将结果返回,再由NetWorkDispatcher使用Deliver返回数据。

没图,就这样。

做了一点微小的工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值