안 드 로 이 드 의 볼 리 라 이브 러 리 기능 구 조 를 깊이 해독 하 다.

17717 단어 AndroidVolley
Volley 는 HTTP 라 이브 러 리 로 Android app 이 네트워크 작업 을 더욱 편리 하 게 수행 할 수 있 도록 도와 줄 수 있 습 니 다.가장 중요 한 것 은 더욱 빠 르 고 효율 적 입 니 다.우 리 는 개 원 된 AOSP 창 고 를 통 해 볼 리 를 얻 을 수 있다.
Volley 는 다음 과 같은 장점 이 있 습 니 다.
4.567917.자동 스케줄 링 네트워크 요청..고 병발 네트워크 연결
  • 표준 HTTP cache coherence(고속 캐 시 일치 성)캐 시 디스크 와 메모리 투명 응답 을 통 해.
  • 지 정 된 요청 의 우선 순 위 를 지원 합 니 다요청 API 를 취소 합 니 다.하나의 요청 을 취소 하거나 취소 요청 대기 열 에 있 는 영역 을 지정 할 수 있 습 니 다4.567917.프레임 워 크 는 맞 춤 형 으로 제작 되 기 쉽다.예 를 들 어 맞 춤 형 재 시도 또는 반환 기능 이다4.567917.강력 한 명령(Strong ordering)은 비동기 로 네트워크 데 이 터 를 불 러 오고 UI 에 정확하게 표시 하 는 작업 을 더욱 간단 하 게 할 수 있 습 니 다4.567917.디 버 깅 과 추적 도 구 를 포함 합 니 다Volley 는 검색 결 과 를 가 져 오 는 등 UI 를 표시 하 는 RPC 형식 작업 을 잘 수행 합 니 다.모든 프로 토 콜 을 쉽게 통합 하고 작업 결과 의 데 이 터 를 출력 합 니 다.원본 문자열 일 수도 있 고 그림 일 수도 있 고 JSON 일 수도 있 습 니 다.내 장 된 우리 가 사용 할 수 있 는 기능 을 제공 함으로써 볼 리 는 모델 코드 를 중복 작성 하지 않 고 app 의 기능 논리 에 관심 을 가 질 수 있 습 니 다.
    볼 리 는 큰 데이터 파일 을 다운로드 하 는 데 적합 하지 않다.볼 리 는 분석 과정 에서 모든 응답 을 유지 하기 때문이다.대량의 데 이 터 를 다운로드 하 는 작업 에 대해 서 는 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 에 가입 하여 신선 도 검증 을 해 야 합 니 다.
    위의 코드 논 리 는 사실 그리 복잡 하지 않 지만 묘사 하면 비교적 복잡 하 다.아래 의 이 그림 은 이해 하 는 데 도움 이 된다.
    201656145935002.jpg (800×600)
    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)를 호출 하여 응답 데 이 터 를 전달 합 니 다.다음 그림 은 이 방법의 절 차 를 보 여 준다.
    201656150017770.jpg (800×600)
    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 의 기본 적 인 작업 원 리 는 바로 이 렇 습 니 다.그림 으로 그의 운행 절 차 를 정리 합 니 다.
    201656150034469.jpg (800×600)

    좋은 웹페이지 즐겨찾기