淺讀網絡框架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中:socket

requestQueue.add(request);

  而後在配置文件中加入網絡請求的權限就能夠實現網絡請求了。ide

  接下來就看看它究竟是怎麼完成網絡請求的:函數

  首先是 Volley.newRequestQueue(getApplicationContext()),查找一下源碼能夠看到最終調用該三參構造函數:oop

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是什麼:post

 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返回。如今再回到前面。ui

  建立完 HurlStack 以後有建立了一個 BasicNetwork,來看一下這個對象的做用:this

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提供了當緩存內容大於默認的緩存最大值時,自動清除最遠最不常使用的緩存內容。

  至此,一個簡單的網絡請求就完成了,若有不正確的地方,歡迎提出,並及時改正。

相關文章
相關標籤/搜索