浅读网络框架Volley源码

  对于安卓的网络开发,我们可能会用到谷歌自家的网络请求框架Volley,下面来看一下Volley是怎样进行网络请求的。所谓知其所然,并知所以然。于是用自己仅有的水平分析一下其源码,不对的地方欢迎提出。

  使用Volley框架进行网络开发,一般是三个步骤,首先是初始化一个 RequsetQueue:

RequestQueue requestQueue  = Volley.newRequestQueue(getApplicationContext());

  第二步是定义一个Request,这里以StringRequest为例:

StringRequest request = new StringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
               
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {

            }
        });

  最后一步是将Request添加到请求队列RequestQueue中:

requestQueue.add(request);

  然后在配置文件中加入网络请求的权限就可以实现网络请求了。

  接下来就看看它到底是怎么完成网络请求的:

  首先是 Volley.newRequestQueue(getApplicationContext()),查找一下源码可以看到最终调用该三参构造函数:

public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), "volley");
        String userAgent = "volley/0";

        try {
            String network = context.getPackageName();
            PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
            userAgent = network + "/" + queue.versionCode;
        } catch (NameNotFoundException var7) {
            ;
        }

        if(stack == null) {
            if(VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
        RequestQueue queue1;
        if(maxDiskCacheBytes <= -1) {
            queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
        } else {
            queue1 = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network1);
        }

        queue1.start();
        return queue1;
    }

  可以看到它首先是创建了一个文件,然后判断 HttpStack 是否为空,因为我们调用的是一参构造,所以默认 HttpStack 为NULL,于是new了一个 HurlStack,这里主要来看一下HurlStack是什么:

 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap map = new HashMap();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if(this.mUrlRewriter != null) {
            String parsedUrl = this.mUrlRewriter.rewriteUrl(url);
            if(parsedUrl == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }

            url = parsedUrl;
        }

        URL parsedUrl1 = new URL(url);
        HttpURLConnection connection = this.openConnection(parsedUrl1, request);
        Iterator responseCode = map.keySet().iterator();

        while(responseCode.hasNext()) {
            String protocolVersion = (String)responseCode.next();
            connection.addRequestProperty(protocolVersion, (String)map.get(protocolVersion));
        }

        setConnectionParametersForRequest(connection, request);
        ProtocolVersion protocolVersion1 = new ProtocolVersion("HTTP", 1, 1);
        int responseCode1 = connection.getResponseCode();
        if(responseCode1 == -1) {
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        } else {
            BasicStatusLine responseStatus = new BasicStatusLine(protocolVersion1, connection.getResponseCode(), connection.getResponseMessage());
            BasicHttpResponse response = new BasicHttpResponse(responseStatus);
            response.setEntity(entityFromConnection(connection));
            Iterator var12 = connection.getHeaderFields().entrySet().iterator();

            while(var12.hasNext()) {
                Entry header = (Entry)var12.next();
                if(header.getKey() != null) {
                    BasicHeader h = new BasicHeader((String)header.getKey(), (String)((List)header.getValue()).get(0));
                    response.addHeader(h);
                }
            }

            return response;
        }
    }

static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
    switch(request.getMethod()) {
    case -1:
        byte[] postBody = request.getPostBody();
        if(postBody != null) {
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.addRequestProperty("Content-Type", request.getPostBodyContentType());
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.write(postBody);
            out.close();
        }
        break;
    case 0:
        connection.setRequestMethod("GET");
        break;
    case 1:
        connection.setRequestMethod("POST");
        addBodyIfExists(connection, request);
        break;
    case 2:
        connection.setRequestMethod("PUT");
        addBodyIfExists(connection, request);
        break;
    case 3:
        connection.setRequestMethod("DELETE");
        break;
    case 4:
        connection.setRequestMethod("HEAD");
        break;
    case 5:
        connection.setRequestMethod("OPTIONS");
        break;
    case 6:
        connection.setRequestMethod("TRACE");
        break;
    case 7:
        connection.setRequestMethod("PATCH");
        addBodyIfExists(connection, request);
        break;
    default:
        throw new IllegalStateException("Unknown method type.");
    }

}

  可以看到其实就是对HttpUrlConnection的封装,包括了各种请求方法,并将请求结果封装成一个 Response返回。现在再回到前面。

  创建完 HurlStack 之后有创建了一个 BasicNetwork,来看一下这个对象的作用:

public BasicNetwork(HttpStack httpStack) {
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        this.mHttpStack = httpStack;
        this.mPool = pool;
    }

    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();

        while(true) {
            HttpResponse httpResponse = null;
            Object responseContents = null;
            Map responseHeaders = Collections.emptyMap();

            try {
                HashMap e = new HashMap();
                this.addCacheHeaders(e, request.getCacheEntry());
                httpResponse = this.mHttpStack.performRequest(request, e);
                StatusLine statusCode1 = httpResponse.getStatusLine();
                int networkResponse1 = statusCode1.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                if(networkResponse1 == 304) {
                    Entry requestLifetime2 = request.getCacheEntry();
                    if(requestLifetime2 == null) {
                        return new NetworkResponse(304, (byte[])null, responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                    }

                    requestLifetime2.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(304, requestLifetime2.data, requestLifetime2.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                }

                if(networkResponse1 == 301 || networkResponse1 == 302) {
                    String requestLifetime = (String)responseHeaders.get("Location");
                    request.setRedirectUrl(requestLifetime);
                }

                byte[] responseContents1;
                if(httpResponse.getEntity() != null) {
                    responseContents1 = this.entityToBytes(httpResponse.getEntity());
                } else {
                    responseContents1 = new byte[0];
                }

                long requestLifetime1 = SystemClock.elapsedRealtime() - requestStart;
                this.logSlowRequests(requestLifetime1, request, responseContents1, statusCode1);
                if(networkResponse1 >= 200 && networkResponse1 <= 299) {
                    return new NetworkResponse(networkResponse1, responseContents1, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                }

                throw new IOException();
            } catch (SocketTimeoutException var12) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException var13) {
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException var14) {
                throw new RuntimeException("Bad URL " + request.getUrl(), var14);
            } catch (IOException var15) {
                boolean statusCode = false;
                NetworkResponse networkResponse = null;
                if(httpResponse == null) {
                    throw new NoConnectionError(var15);
                }

                int statusCode2 = httpResponse.getStatusLine().getStatusCode();
                if(statusCode2 != 301 && statusCode2 != 302) {
                    VolleyLog.e("Unexpected response code %d for %s", new Object[]{Integer.valueOf(statusCode2), request.getUrl()});
                } else {
                    VolleyLog.e("Request at %s has been redirected to %s", new Object[]{request.getOriginUrl(), request.getUrl()});
                }

                if(responseContents == null) {
                    throw new NetworkError(networkResponse);
                }

                networkResponse = new NetworkResponse(statusCode2, (byte[])responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                if(statusCode2 != 401 && statusCode2 != 403) {
                    if(statusCode2 != 301 && statusCode2 != 302) {
                        throw new ServerError(networkResponse);
                    }

                    attemptRetryOnException("redirect", request, new AuthFailureError(networkResponse));
                } else {
                    attemptRetryOnException("auth", request, new AuthFailureError(networkResponse));
                }
            }
        }
    }

  可以看出其实是对请求结果的进一步分析和加工,其中二参构造中new了一个 ByteArrayPool(DEFAULT_POOL_SIZE),这个对象就是记录了最后一次缓存的内容和目前为止的缓存大小。DEFAULT_POOL_SIZE为一次缓存的上限,默认大小为4096,并将其保存到容器中。现在再回到开始。

  第一次的话,默认 maxDiskCacheBytes 为-1,所以执行 queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1),来看一下 DiskBasedCache 的作用:

public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        this.mEntries = new LinkedHashMap(16, 0.75F, true);
        this.mTotalSize = 0L;
        this.mRootDirectory = rootDirectory;
        this.mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, 5242880);
    }

可以看到初始化了缓存路径和缓存的总大小5M。将一些准备工作做好之后,然后new出RequestQueue对象:

public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
        this.mSequenceGenerator = new AtomicInteger();
        this.mWaitingRequests = new HashMap();
        this.mCurrentRequests = new HashSet();
        this.mCacheQueue = new PriorityBlockingQueue();
        this.mNetworkQueue = new PriorityBlockingQueue();
        this.mFinishedListeners = new ArrayList();
        this.mCache = cache;
        this.mNetwork = network;
        this.mDispatchers = new NetworkDispatcher[threadPoolSize];
        this.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, 4);
    }

可以看到RequestQueue初始化了一些容器,并采用原子方式,并默认线程的数量为4. 好了,第一步的分析基本就到这了。现在来看第二步:

第二步就是new了一个 Request对象,来看一下发生了什么:

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

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

可以看到谷歌提供的StringRequest默认使用的方法是0,也就是GET方法,接着看一下Request中发生了什么:

public Request(int method, String url, ErrorListener listener) {
        this.mEventLog = MarkerLog.ENABLED?new MarkerLog():null;
        this.mShouldCache = true;
        this.mCanceled = false;
        this.mResponseDelivered = false;
        this.mRequestBirthTime = 0L;
        this.mCacheEntry = null;
        this.mMethod = method;
        this.mUrl = url;
        this.mErrorListener = listener;
        this.setRetryPolicy(new DefaultRetryPolicy());
        this.mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

基本上是一些配置,默认缓存,DefaultRetryPolicy对象设置了请求的基本设置,findDefaultTrafficStatsTag方法返回了 url 的hashCode。

接下来看第三步:rquestQueue.add(request);

public <T> Request<T> add(Request<T> request) {
        request.setRequestQueue(this);
        Set var2 = this.mCurrentRequests;
        synchronized(this.mCurrentRequests) {
            this.mCurrentRequests.add(request);
        }

        request.setSequence(this.getSequenceNumber());
        request.addMarker("add-to-queue");
        if(!request.shouldCache()) {
            this.mNetworkQueue.add(request);
            return request;
        } else {
            Map var7 = this.mWaitingRequests;
            synchronized(this.mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                if(this.mWaitingRequests.containsKey(cacheKey)) {
                    Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
                    if(stagedRequests == null) {
                        stagedRequests = new LinkedList();
                    }

                    ((Queue)stagedRequests).add(request);
                    this.mWaitingRequests.put(cacheKey, stagedRequests);
                    if(VolleyLog.DEBUG) {
                        VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
                    }
                } else {
                    this.mWaitingRequests.put(cacheKey, (Object)null);
                    this.mCacheQueue.add(request);
                }

                return request;
            }
        }
    }

可以看到将请求request加入到set集合,保证没有重复请求,然后判断请求是否需要缓存,默认为true,判断等待队列中是否有该请求的缓存key,即url,若有则取出对应的Queue集合,将请求加入其中,若没有则为该请求创建一个新的Queue。并将之加入到缓存队列中去。

在回到第一步,new出来一个RequestQueue之后,其紧接着调用了对象方法start(),来看一下RequestQueue的start()方法做了什么:

public void start() {
        this.stop();
        this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
        this.mCacheDispatcher.start();

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

    }

首先调用stop(),代码如下:

 public void stop() {
        if(this.mCacheDispatcher != null) {
            this.mCacheDispatcher.quit();
        }

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

    }

可以看到以上两个方法都涉及到了CacheDispatcher和NetworkDispatcher,这两个类则真正的进行数据请求。首先看一下CacheDispatcher:

public void quit() {
        this.mQuit = true;
        this.interrupt();
    }

    public void run() {
        if(DEBUG) {
            VolleyLog.v("start new dispatcher", new Object[0]);
        }

        Process.setThreadPriority(10);
        this.mCache.initialize();

        while(true) {
            while(true) {
                while(true) {
                    while(true) {
                        try {
                            while(true) {
                                final Request e = (Request)this.mCacheQueue.take();
                                e.addMarker("cache-queue-take");
                                if(e.isCanceled()) {
                                    e.finish("cache-discard-canceled");
                                } else {
                                    Entry entry = this.mCache.get(e.getCacheKey());
                                    if(entry == null) {
                                        e.addMarker("cache-miss");
                                        this.mNetworkQueue.put(e);
                                    } else if(entry.isExpired()) {
                                        e.addMarker("cache-hit-expired");
                                        e.setCacheEntry(entry);
                                        this.mNetworkQueue.put(e);
                                    } else {
                                        e.addMarker("cache-hit");
                                        Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
                                        e.addMarker("cache-hit-parsed");
                                        if(entry.refreshNeeded()) {
                                            e.addMarker("cache-hit-refresh-needed");
                                            e.setCacheEntry(entry);
                                            response.intermediate = true;
                                            this.mDelivery.postResponse(e, response, new Runnable() {
                                                public void run() {
                                                    try {
                                                        CacheDispatcher.this.mNetworkQueue.put(e);
                                                    } catch (InterruptedException var2) {
                                                        ;
                                                    }

                                                }
                                            });
                                        } else {
                                            this.mDelivery.postResponse(e, response);
                                        }
                                    }
                                }
                            }
                        } catch (InterruptedException var4) {
                            if(this.mQuit) {
                                return;
                            }
                        }
                    }
                }
            }
        }
    }

它继承了Theard类,所以是个线程,首先执行quit,防止有线程在执行。接着调用start,即执行run()方法,首先从缓存队列中取出请求,思路很清晰,从缓存的内容中寻找该请求的url,如果找到了,则将内容取出,并返回。若失败,则加入网络的请求队列中。来看一下网络请求调度类NetworkDispatcher:

public void run() {
        Process.setThreadPriority(10);

        while(true) {
            long startTimeMs;
            Request request;
            while(true) {
                startTimeMs = SystemClock.elapsedRealtime();

                try {
                    request = (Request)this.mQueue.take();
                    break;
                } catch (InterruptedException var6) {
                    if(this.mQuit) {
                        return;
                    }
                }
            }

            try {
                request.addMarker("network-queue-take");
                if(request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                } else {
                    this.addTrafficStatsTag(request);
                    NetworkResponse e = this.mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");
                    if(e.notModified && request.hasHadResponseDelivered()) {
                        request.finish("not-modified");
                    } else {
                        Response volleyError1 = request.parseNetworkResponse(e);
                        request.addMarker("network-parse-complete");
                        if(request.shouldCache() && volleyError1.cacheEntry != null) {
                            this.mCache.put(request.getCacheKey(), volleyError1.cacheEntry);
                            request.addMarker("network-cache-written");
                        }

                        request.markDelivered();
                        this.mDelivery.postResponse(request, volleyError1);
                    }
                }
            } catch (VolleyError var7) {
                var7.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                this.parseAndDeliverNetworkError(request, var7);
            } catch (Exception var8) {
                VolleyLog.e(var8, "Unhandled exception %s", new Object[]{var8.toString()});
                VolleyError volleyError = new VolleyError(var8);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                this.mDelivery.postError(request, volleyError);
            }
        }
    }

它和缓存调度线程是一样的,默认是4个线程在工作。它用到了我们第一步时创建的HurlStack,通过HttpUrlConnection请求网络数据,并判断该请求是否缓存(默认为true),将请求的url作为key,将内存作为数据保存在缓存中。此外Volley提供了当缓存内容大于默认的缓存最大值时,自动清除最远最不常使用的缓存内容。

  至此,一个简单的网络请求就完成了,如有不正确的地方,欢迎提出,并及时改正。

posted on 2016-05-05 14:55  画家丶  阅读(148)  评论(0编辑  收藏  举报