0. 머리말
사실 이 글은 단 하나의 목적으로만 작성되었습니다. Volley사용하기는 좋은데, 내부적으로 어떻게 구현하는지 면접관이 묻는다. 책을 들고 있는 것과 같다발리고등학생이 매뉴얼을 사용하는 것과 별 차이가 없다. 속담처럼, 그것을 사용하는 방법을 아는 것과 그것을 깊이 이해하는 것은 다른 것입니다.
1. 발리소스코드 분석
1.1 발리 입구
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; }
위의 소스 코드에서 볼 수 있듯이 들어오는 스택<🎜이라고 판단됩니다. >가 비어 있으면 은 시스템 버전에 따라 <보다 크거나 같은 HttpStack 객체 를 생성합니다. 🎜>9 그런 다음 HurlStack의 인스턴스를 만듭니다(내부적으로 HttpURLConnection 사용) , 그렇지 않으면 HttpClientStack의 인스턴스(내부적으로 HttpClient 사용) 를 만듭니다. 그 이유는 다음과 같습니다: (
1)HttpURLConnection 버그 🎜>9 버전에서 imputStream.close()를 호출하면 연결 풀이 발생합니다. 상대적으로 말하면 HTTPClient는 9 미만의 시스템 버전에서 버그가 더 적습니다. .
(2) 및 9 이상 버전, HttpURLConnection 구현에는 더 많은 장점이 있으며, API는 더 간단하고 크기가 더 작으며 압축 기능이 있습니다 및 4.0나중에 추가된 캐싱 메커니즘 (예: 로컬 캐시, 304 캐시, 서버 캐시 ). HttpStack을 생성한 후 이를 파라미터로 사용하여 Network 객체를 생성하고, 그 후 Network 객체를 기반으로
RequestQueue 객체<를 생성합니다. 🎜> 🎜>, 및 start() 메서드 를 호출하여 시작한 다음 인스턴스를 반환합니다.
1.2 RequestQueue.start()
这里CacheDispatcher和NetworkDispatcher都是继承自Thread的,也就是说当调用了Volley.newRequestQueue(context)之后,一个缓存线程和四个网络请求线程会一直在后台运行,并等待网络请求的到来。 1.3 RequestQueue.add() 既然上面已经迫不及待等待网络请求的到来了,那么是时候将我们的Request添加进队列了。 在第11行的时候会判断当前的请求是否可以缓存,默认情况下是可以缓存的,除非主动调用了Request的setShouldCache(false)方法来不允许其进行缓存。因此默认情况下会将该请求加入到缓存队列,否则直接加入网络请求队列。下面看看缓存线程中的run方法。 1.4 CacheDispatcher中的run() 可以看到while(true)的循环说明缓存线程始终是在运行,接着会尝试从缓存当中取出响应结果,如何为空则把这条请求加入到网络请求队列中,否则判断该缓存是否已过期,如果已经过期那么肯定还是同上处理,没有过期就直接使用缓存中的数据。 之后就是和网络请求队列请求到数据后一样的数据解析逻辑以及解析结果回调逻辑。 1.5 NetworkDispatcher中的run() 接下来就是分析正常情况下,没有缓存的情况下,网络请求是什么样子的。直接看网络请求线程中的run()。 while(true)循环说明网络请求线程也是在不断运行的。在第25行可以看到调用了Network的performRequest()方法发送网络请求,而Network是一个接口,这里具体的实现是BasicNetwork,我们来看下它的performRequest()方法,如下所示: 里面除去一些网络请求的细节,看到在第11行调用了HttpStack的performRequest()方法,这里的HttpStack就是在一开始调用newRequestQueue()方法是创建的实例,之后会将服务器返回的数据组装成一个NetworkResponse对象进行返回。 收到了NetworkResponse这个返回值后会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,以及将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。在解析完了NetworkResponse中的数据之后,又会调用ExecutorDelivery的postResponse()方法来回调解析出的数据,代码如下所示: 在该类的构造函数中传入了一个主线程创建的handler,最后一行代码在mResponsePoster的execute()方法中传入一个ResponseDeliveryRunnable对象,这样就可以通过handler.post(runnable)切换到了主线程。我们继续看下这个Runnable中的run()方法中的代码是什么样的: 其中在第22行调用了Request的deliverResponse()方法,这个和parseNetworkResponse()方法一样都是我们在自定义Request时需要重写的方法,每一条网络请求的响应都是回调到这个方法中,再在这个方法中将响应的数据回调到Listener的onResponse()方法中,即我们创建Request时传入的那个listener。 위 내용은 Android 개발 - Volley 소스 코드에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!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();
}
}
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;
}
}
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'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;
}
}
}
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'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) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
}
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) {
……
}
}
}
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));
}
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();
}
}
}