搜尋
首頁Javajava教程Android開發—Volley原始碼的詳細分析

0. 前言  

#其實寫這篇文章只為一個目的,雖然Volley用起來很爽,但是面試官問你人家內部是怎麼實現呢,沒看過源碼的話,在面試官眼裡你和一個拿著一本Volley使用手冊的高中生沒啥差別。還是那句話說得好,會用一回事,深入理解又是另一回事了。

1.  Volley原始碼解析

1.1  Volley#入口

Volley首先取得到的是RequestQueue實例#。原始碼中則直接呼叫了newRequestQueue方法

public static RequestQueue newRequestQueue(Context context) {  
    return newRequestQueue(context, null);  
}  
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 {  
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));  
        }  
    }  
    Network network = new BasicNetwork(stack);  
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);  
    queue.start();  
    return queue;  
}


從上面原始碼可以看到,判斷傳入的stack為空,則根據系統版本去建立一個HttpStack物件,大於等於9的,則建立一個HurlStack的實例(內部使用HttpURLConnection#,否則就建立一個 HttpClientStack的實例(內部使用HttpClient。之所以這樣做是因為:

1HttpURLConnection在小於#9的版本中存在呼叫imputStream.close()會導致連線池失敗的BUG ,相對來說HTTPClient在小於9的系統版本中有較少的BUG

2)而在大於等於9的版本中,HttpURLConnection的實作有更多的優點,API更簡單,體積更小,有壓縮功能以及4.0以後加入的快取機制(如本機快取、304快取、伺服器快取).

創建好了HttpStack之後將其作為參數創建了一個Network對象,接著又根據Network對象創建了一個RequestQueue對象並呼叫它的start()方法進行啟動,然後將該實例傳回。


1.2 RequestQueue.start()

public void start() {  
    stop(); 
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);  
    mCacheDispatcher.start();  
for (int i = 0; i < mDispatchers.length; i++) { //默认循环4次 
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,  mCache, mDelivery);  
        mDispatchers[i] = networkDispatcher;  
        networkDispatcher.start();  
    }  
}

这里CacheDispatcherNetworkDispatcher都是继承自Thread的,也就是说当调用了Volley.newRequestQueue(context)之后,一个缓存线程和四个网络请求线程会一直在后台运行,并等待网络请求的到来。


1.3  RequestQueue.add()

既然上面已经迫不及待等待网络请求的到来了,那么是时候将我们的Request添加进队列了。


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&#39;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 &#39;null&#39; queue for this cacheKey, indicating there is now a request in  
            // flight.  
            mWaitingRequests.put(cacheKey, null);  
            mCacheQueue.add(request);  
        }  
        return request;  
    }  
}


在第11行的时候会判断当前的请求是否可以缓存,默认情况下是可以缓存的,除非主动调用了RequestsetShouldCache(false)方法来不允许其进行缓存。因此默认情况下会将该请求加入到缓存队列否则直接加入网络请求队列。下面看看缓存线程中的run方法。


1.4  CacheDispatcher中的run()


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();  
        while (true) {  
            try {  
                // Get a request from the cache triage queue, blocking until  
                // at least one is available.  
                final Request<?> request = mCacheQueue.take();  
                request.addMarker("cache-queue-take");  
                // If the request has been canceled, don&#39;t bother dispatching it.  
                if (request.isCanceled()) {  
                    request.finish("cache-discard-canceled");  
                    continue;  
                }  
                // Attempt to retrieve this item from cache.  
                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.  
                request.addMarker("cache-hit");  
                Response<?> response = request.parseNetworkResponse(  
                        new NetworkResponse(entry.data, entry.responseHeaders));  
                request.addMarker("cache-hit-parsed");  
                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.  
                    request.addMarker("cache-hit-refresh-needed");  
                    request.setCacheEntry(entry);  
                    // Mark the response as intermediate.  
                    response.intermediate = true;  
                    // Post the intermediate response back to the user and have  
                    // the delivery then forward the request along to the network.  
                    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) {  
                // We may have been interrupted because it was time to quit.  
                if (mQuit) {  
                    return;  
                }  
                continue;  
            }  
        }  
}


可以看到while(true)的循环说明缓存线程始终是在运行,接着会尝试从缓存当中取出响应结果,如何为空则把这条请求加入到网络请求队列中,否则判断该缓存是否已过期,如果已经过期那么肯定还是同上处理,没有过期就直接使用缓存中的数据

之后就是和网络请求队列请求到数据后一样的数据解析逻辑以及解析结果回调逻辑。


1.5  NetworkDispatcher中的run()

接下来就是分析正常情况下,没有缓存的情况下,网络请求是什么样子的。直接看网络请求线程中的run()

public void run() {  
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
        Request<?> request;  
        while (true) {  
            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&#39;re done -- don&#39;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) {  
                parseAndDeliverNetworkError(request, volleyError);  
            } catch (Exception e) {  
                VolleyLog.e(e, "Unhandled exception %s", e.toString());  
                mDelivery.postError(request, new VolleyError(e));  
            }  
        }  
    }  
}

while(true)循环说明网络请求线程也是在不断运行的。在第25行可以看到调用了NetworkperformRequest()方法发送网络请求,而Network是一个接口,这里具体的实现是BasicNetwork,我们来看下它的performRequest()方法,如下所示:

public NetworkResponse performRequest(Request<?> request) throws VolleyError {  
        long requestStart = SystemClock.elapsedRealtime();  
        while (true) {  
            HttpResponse httpResponse = null;  
            byte[] responseContents = null;  
            Map<String, String> responseHeaders = new HashMap<String, String>();  
            try {  
                // Gather headers.  
                Map<String, String> headers = new HashMap<String, String>();  
                addCacheHeaders(headers, request.getCacheEntry());  
                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) {  
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,  
                            request.getCacheEntry() == null ? null : request.getCacheEntry().data,  
                            responseHeaders, true);  
                }  
                // 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);  
            } catch (Exception e) {  
                ……  
            }  
        }  
}

里面除去一些网络请求的细节,看到在第11行调用了HttpStackperformRequest()方法,这里的HttpStack就是在一开始调用newRequestQueue()方法是创建的实例,之后会将服务器返回的数据组装成一个NetworkResponse对象进行返回。

收到了NetworkResponse这个返回值后会调用RequestparseNetworkResponse()方法来解析NetworkResponse中的数据,以及将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。在解析完了NetworkResponse中的数据之后,又会调用ExecutorDeliverypostResponse()方法来回调解析出的数据,代码如下所示:

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);
 }
};
}
# postResponse方法
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {  
    request.markDelivered();  
    request.addMarker("post-response");  
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}


在该类的构造函数中传入了一个主线程创建的handler,最后一行代码在mResponsePosterexecute()方法中传入一个ResponseDeliveryRunnable对象,这样就可以通过handler.postrunnable切换到了主线程。我们继续看下这个Runnable中的run()方法中的代码是什么样的:

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&#39;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&#39;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();  
        }  
   }  
}

其中在第22行调用了RequestdeliverResponse()方法,这个和parseNetworkResponse()方法一样都是我们在自定义Request时需要重写的方法,每一条网络请求的响应都是回调到这个方法中,再在这个方法中将响应的数据回调到ListeneronResponse()方法中,即我们创建Request时传入的那个listener。



以上是Android開發—Volley原始碼的詳細分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)