안 드 로 이 드 의 볼 리 라 이브 러 리 기능 구 조 를 깊이 해독 하 다.
Volley 는 다음 과 같은 장점 이 있 습 니 다.
4.567917.자동 스케줄 링 네트워크 요청..고 병발 네트워크 연결
볼 리 는 큰 데이터 파일 을 다운로드 하 는 데 적합 하지 않다.볼 리 는 분석 과정 에서 모든 응답 을 유지 하기 때문이다.대량의 데 이 터 를 다운로드 하 는 작업 에 대해 서 는 DownloadManager 를 사용 하 는 것 을 고려 하 십시오.
Volley 프레임 워 크 의 핵심 코드 는 AOSP 창고 의 frameworks/volley 에 맡 기 고 관련 도 구 는 toolbox 아래 에 두 는 것 입 니 다.프로젝트 에 Volley 를 추가 하 는 가장 간편 한 방법 은 Clone 창고 입 니 다.그리고 이 를 library procject 로 설정 합 니 다.
다음 명령 을 통 해 Clone 창고 로 이동:
git clone https://android.googlesource.com/platform/frameworks/volley
프로젝트 에 다운로드 한 소스 코드 를 Android library procject 로 가 져 옵 니 다.다음은 볼 리 의 자바 소스 코드 를 분석 해 보 겠 습 니 다.
RequestQueue
Volley 를 사용 할 때 RequestQueue 대상 을 먼저 획득 해 야 합 니 다.다양한 요청 작업 을 추가 하 는 데 사 용 됩 니 다.보통 Volly.newRequestQueue()방법 으로 기본 RequestQueue 를 가 져 옵 니 다.우 리 는 이 방법 부터 시작 해서 다음은 그것 의 소스 코드 이다.
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 {
// 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 = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
new RequestQueue(context)는 재 부팅 방법 new RequestQueue(context,null)를 호출 했 습 니 다.이 방법 에서 먼저 context 를 통 해 캐 시 디 렉 터 리 를 얻 고 userAgent 정 보 를 구축 했다.이 어 stack 이 비어 있 는 지 여 부 를 판단 합 니 다.위의 호출 을 통 해 알 수 있 습 니 다.기본 적 인 상황 에서 stack=null 이 므 로 stack 대상 을 새로 만 듭 니 다.시스템 버 전에 따라 버 전 번호 가 9 보다 클 때 stack 은 HurlStack 이 고 그렇지 않 으 면 HttpClient Stack 입 니 다.이들 의 차 이 는 HurlStack 은 HttpUrlConnection 을 사용 하여 네트워크 통신 을 하고 HttpClient Stack 은 HttpClient 를 사용 한 다 는 것 이다.stack 이 생기 면 Basic NetWork 대상 을 만 들 었 습 니 다.네트워크 요청 작업 을 처리 하 는 데 사용 되 는 것 으로 알 수 있 습 니 다.이 어 RequestQueue 를 새로 만 들 었 습 니 다.이것 도 결국 우리 에 게 돌아 온 요청 대기 열 입 니 다.이 RequestQueue 는 두 개의 인 자 를 받 아들 입 니 다.첫 번 째 는 DiskBasedCache 대상 입 니 다.이름 에서 알 수 있 듯 이 이것 은 하 드 디스크 캐 시 에 사용 되 는 것 이 고 캐 시 디 렉 터 리 는 방법 입 니 다.처음에 얻 은 cacheDir 입 니 다.두 번 째 인 자 는 방금 만 든 network 대상 입 니 다.마지막 으로 quue.start()시작 요청 대기 열 을 호출 합 니 다.start()를 분석 하기 전에 RequestQueue 의 관건 적 인 내부 변수 와 구조 방법 을 알 아 보 세 요.
//
private final Map<String, Queue<Request>> mWaitingRequests =
new HashMap<String, Queue<Request>>();
//
private final Set<Request> mCurrentRequests = new HashSet<Request>();
//
private final PriorityBlockingQueue<Request> mCacheQueue =
new PriorityBlockingQueue<Request>();
//
private final PriorityBlockingQueue<Request> mNetworkQueue =
new PriorityBlockingQueue<Request>();
//
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
//
private final Cache mCache;
//
private final Network mNetwork;
//
private final ResponseDelivery mDelivery;
//
private NetworkDispatcher[] mDispatchers;
//
private CacheDispatcher mCacheDispatcher;
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
RequestQueue 는 여러 가지 구조 방법 이 있 는데 최종 적 으로 마지막 을 호출 합 니 다.이 방법 에서 mCache 와 mNetWork 는 각각 new RequestQueue 에서 들 려 오 는 DiskBasedCache 와 Basic NetWork 로 설정 합 니 다.mDispatchers 네트워크 요청 스케줄 러 의 배열,기본 크기 4(DEFAULTNETWORK_THREAD_POOL_SIZE)。mDelivery 는 new Executor Delivery(new Handler(Looper.getMainLooper()))로 설정 되 어 있 으 며 데이터 전달 에 응 하 는 데 사 용 됩 니 다.나중에 구체 적 으로 소개 합 니 다.이 를 통 해 알 수 있 듯 이 우 리 는 기본 적 인 new RequestQueue 를 사용 하지 않 고 스스로 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();
}
}
코드 가 비교적 간단 해서 두 가지 일 을 했다.첫째,CacheDispatcher 를 만 들 고 시작 합 니 다.둘째,넷 워 크 디 스 패 치 를 만 들 고 시작 합 니 다.시작 요청 대기 열 이란 캐 시 스케줄 러 와 네트워크 요청 스케줄 러 에 작업 을 맡 기 는 것 입 니 다.여기에 또 문제 가 있 습 니 다.요청 작업 은 어떻게 요청 대기 열 에 가입 합 니까?사실은 add()방법 을 호출 한 것 이다.이제 내부 에서 어떻게 처 리 했 는 지 보 자.
public Request add(Request 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;
}
}
이 방법의 코드 는 약간 길 지만 논 리 는 복잡 하지 않다.우선 이 작업 을 mCurrentRequests 에 추가 한 다음 캐 시 필요 여 부 를 판단 하고 필요 하지 않 으 면 네트워크 요청 작업 대기 열 mNetworkQueue 에 직접 가입 하여 되 돌려 줍 니 다.기본 모든 작업 은 캐 시가 필요 합 니 다.set ShouldCache(boolean shouldCache)를 호출 하여 설정 을 변경 할 수 있 습 니 다.캐 시가 필요 한 모든 것 은 캐 시 작업 대기 열 mCacheQueue 에 추 가 됩 니 다.그러나 mWaiting Requests 가 이미 있 는 지 판단 하고 중복 되 는 요청 을 피해 야 합 니 다.Dispatcher
RequestQueue 가 start()를 호출 한 후 요청 작업 은 CacheDispatcher 와 NetworkDispatcher 에 맡 겨 졌 습 니 다.모두 Thread 에서 계승 되 었 습 니 다.사실은 배경 작업 스 레 드 입 니 다.각각 캐 시 와 네트워크 에서 데 이 터 를 가 져 옵 니 다.
CacheDispatcher
CacheDispatcher 는 mCacheQueue 에서 작업 처 리 를 계속 꺼 냅 니 다.다음은 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'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;
}
}
}
먼저 mCache.initialize()를 호출 하여 캐 시 를 초기 화 한 다음 while(true)의 순환 입 니 다.순환 중 캐 시 대기 열 을 꺼 내 는 작업 입 니 다.작업 이 취소 되 었 는 지 여 부 를 판단 하고 request.finish("cache-discard-canceled")를 실행 한 다음 아래 코드 를 건 너 뛰 고 다시 순환 을 시작 합 니 다.그렇지 않 으 면 캐 시 에서 이 작업 에 캐 시 데이터 가 있 는 지 찾 습 니 다.캐 시 데이터 가 존재 하지 않 으 면 네트워크 요청 대기 열 에 작업 을 추가 하고 아래 코드 를 건 너 뛰 어 순환 을 다시 시작 합 니 다.캐 시 를 찾 으 면 만 료 여 부 를 판단 합 니 다.만 료 된 것 은 네트워크 요청 대기 열 에 가입 해 야 합 니 다.그렇지 않 으 면 request 의 parseNetworkResponse 분석 응답 데 이 터 를 호출 합 니 다.마지막 단 계 는 캐 시 데이터 의 신선 도 를 판단 하 는 것 입 니 다.신선 도 를 새로 고 칠 필요 가 없 는 mDelivery.postResponse(request,response)가 응답 데 이 터 를 전달 합 니 다.그렇지 않 으 면 mNetworkQueue 에 가입 하여 신선 도 검증 을 해 야 합 니 다.위의 코드 논 리 는 사실 그리 복잡 하지 않 지만 묘사 하면 비교적 복잡 하 다.아래 의 이 그림 은 이해 하 는 데 도움 이 된다.
NetworkDispatcher
CacheDispatcher 는 캐 시 에서 작업 의 응답 데 이 터 를 찾 습 니 다.캐 시가 없 거나 캐 시가 효력 을 잃 으 면 NetworkDispatcher 에 맡 겨 야 합 니 다.네트워크 요청 작업 대기 열 에서 작업 을 계속 꺼 내 수행 합 니 다.다음은 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;
}
// Tag the request (if API >= 14)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
}
// 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));
}
}
}
run()방법 은 여전히 무한 순환 임 을 알 수 있다.대기 열 에서 작업 하 나 를 꺼 내 서 작업 이 취소 되 었 는 지 여 부 를 판단 합 니 다.취소 하지 않 으 면 mNetwork.performRequest(request)를 호출 하여 응답 데 이 터 를 가 져 옵 니 다.데이터 가 304 응답 이 고 이 작업 의 데이터 전달 이 있 으 면 CacheDispatcher 에서 신선 도 를 검증 하 라 는 요청 이 며 신선 도 를 새로 고 칠 필요 가 없 기 때문에 아래 코드 를 건 너 뛰 고 다시 순환 을 시작 합 니 다.그렇지 않 으 면 다음 단 계 를 계속 하고 응답 데 이 터 를 분석 하여 데이터 가 캐 시 되 어야 하 는 지 확인 하 십시오.마지막 으로 mDelivery.postResponse(request,response)를 호출 하여 응답 데 이 터 를 전달 합 니 다.다음 그림 은 이 방법의 절 차 를 보 여 준다.Delivery
CacheDispatcher 와 NetworkDispatcher 에서 작업 의 데 이 터 를 얻 은 후 mDelivery.post Response(request,response)를 통 해 데 이 터 를 전달 합 니 다.우 리 는 Dispatcher 가 다른 스 레 드 라 는 것 을 알 고 있 기 때문에 그들 이 얻 은 데 이 터 를 어떤 방법 으로 메 인 스 레 드 에 전달 하여 Deliver 가 어떻게 하 는 지 봐 야 한다.
mDelivery 의 유형 은 Executor Delivery 입 니 다.다음은 post Response 방법 원본 입 니 다.
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
위의 코드 를 통 해 알 수 있 듯 이 최종 적 으로 mResponse Poster.execute(new Response Delivery Runnable(request,response,runnable)를 호출 하여 데 이 터 를 전달 합 니 다.여기 mResponse Poster 는 Executor 대상 입 니 다.
private final Executor mResponsePoster;
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);
}
};
Executor 는 스 레 드 탱크 프레임 인터페이스 로 그 안에 execute()방법 만 있 습 니 다.mResponse Poster 의 이 방법 은 handler 로 Runnable 대상 을 전달 하 는 것 입 니 다.한편,post Response 방법 에서 request 와 response 는 Response Delivery Runnable 로 봉 인 됩 니 다.이것 은 바로 Runnable 대상 입 니 다.그래서 응답 데 이 터 는 handler 를 통 해 전달 되 었 습 니 다.그러면 이 handler 는 어디에서 왔 습 니까?사실 RequestQueue 를 소개 할 때 언급 했 습 니 다.mDelivery 는 new Executor Delivery(new Handler(Looper.getMainLooper())로 설정 되 어 있 습 니 다.이 handler 는 new Handler(Looper.getMainLooper()입 니 다.메 인 스 레 드 의 메시지 순환 과 연결 되 어 있 습 니 다.그러면 데 이 터 는 메 인 스 레 드 에 성공 적 으로 전 달 됩 니 다.총결산
Volley 의 기본 적 인 작업 원 리 는 바로 이 렇 습 니 다.그림 으로 그의 운행 절 차 를 정리 합 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.