OKHttp 3 - dispatcher 분배 기 동기 화 비동기 요청 원본 분석 실현 [3]

Dispatcher
  • 시리즈
  • 회고
  • Dispatcher
  • 데이터 구조
  • 스 레 드 탱크
  • 관리 요청
  • 소스 코드
  • 계열
    OKHttp 3 – 상세 한 사용 및 소스 분석 시리즈 의 초보적인 소개 [1] OKHttp 3 – 프로 세 스 분석 핵심 류 소개 동기 비동기 요청 소스 분석 [2] OKHttp 3 – Dispatcher 배포 기 소스 분석 [3] OKHttp 3 – 호출 대상 RealCall 소스 분석 [4] OKHttp 3 – 차단기 체인 RealInterceptorChain 소스 분석 [5]OKHttp 3 – 리 셋 및 리 셋 차단기 Retry AndFollow UpInterceptor 원본 분석 [6] OKHttp 3 – 브리지 차단기 BridgeInterceptor 원본 분석 및 관련 http 요청 헤더 필드 분석 [7] OKHttp 3 – 캐 시 차단기 CacheInterceptor 원본 분석 [8] OKHttp 3 - HTTP 캐 시 메커니즘 분석 캐 시 처리 클래스 Cache 와 캐 시 정책 클래스 Cache Strategy 원본 분석[9]
    돌이켜보다
    이전 분석 OKHttp 동기 화 비동기 요청 글 에서 Dispatcher 라 는 종 류 를 대충 소개 했다. OKHttp 에서 의 위 치 는 매우 중요 하 므 로 그의 역할 에 대해 잘 이해 해 야 한다. 전체 프레임 워 크 의 임무 스케줄 러 로 서 우 리 는 의문 이 있 을 것 이다. 예 를 들 어
  • Dispatcher 는 도대체 무엇 입 니까? 무엇 에 쓰 입 니까?
     OKHttp Dispatcher                  (RealCall)     (AsyncCall),
         AsyncCall
    
  • OKHttp 는 어떻게 동기 화 및 비동기 요 구 를 실현 합 니까?
        ,   Dispatcher            
    
          ,      runningSyncCalls (           );
                  
    
          ,    AsyncCall              runningAsyncCalls  (           )
     readyAsyncCalls  (           ),    AsyncCall;      ,     ,
              
    
  • Dispatcher
    여기 서 우 리 는 먼저 소스 코드 를 배열 하지 않 고, 먼저 그것 이 어떤 특징 이 있 는 지 말 해서, 모두 에 게 전체적인 인식 을 가지 게 한다.
    데이터 구조
    Dispatcher 에서 세 개의 대기 열 을 유지 하고 있 습 니 다. 대기 열 유형 은 Array Deque 입 니 다. 이것 은 두 개의 단 대기 열 입 니 다. 즉, 대기 열 머리 와 꼬리 부분 에서 데이터 조작 을 할 수 있 고 FIFO 원칙 을 실현 할 수 있 습 니 다. 즉, 먼저 들 어 가 는 것 은 먼저 실행 할 수 있 지만 스 레 드 가 안전 한 실현 이 아니 라 다 중 스 레 드 환경 에서 잠 금 을 추가 해 야 합 니 다. AsyncTask 소스 코드 를 본 사람 은 이것 을 알 고 있 습 니 다. 구체 적 인 세부 사항 은 소스 코드 에서 참고 할 수 있분석 - android 데이터 구조의 양단 대기 열 Array Deque FIFO 와 LIFO 대기 열 실현
  • 첫 번 째 대기 열 은 runningsyncCalls 입 니 다. 실행 중인 동기 화 요청 대기 열 입 니 다. 우리 가 추가 한 동기 화 요청 은 모두 여기에 추 가 됩 니 다. 취 소 됐 지만 실행 되 지 않 은 요청 을 포함 합 니 다. 대기 열 범 위 는 RealCall 대상
  • 입 니 다.
  • 두 번 째 대기 열 은 running AsyncCalls 입 니 다. 실행 중인 비동기 요청 대기 열 입 니 다. 우리 가 추가 한 비동기 요청 은 모두 여기에 추 가 됩 니 다. 취 소 됐 지만 실행 되 지 않 은 요청 을 포함 합 니 다. 대기 열 범 위 는 AsyncCall 대상 으로 Runnable 인터페이스
  • 를 실현 합 니 다.
  • 세 번 째 대기 열 은 ready AsyncCalls 입 니 다. 실행 을 기다 리 는 비동기 요청 대기 열 입 니 다. 무슨 뜻 입 니까? OKHttp 에서 동시에 실행 할 수 있 는 비동기 요청 수량 은 64 개 이내 이 고 하나의 host 가 동시에 실행 할 수 있 는 최대 요청 수량 은 5 개 이내 이기 때문에 추가 한 비동기 요청 수가 초과 되면 이 요청 은 이 대기 열 에 추 가 됩 니 다. running AsyncCalls 등대기 열 에 남 은 위치 가 있 으 면 하나의 host 가 동시에 실행 하 는 최대 요청 수량 을 이해 하지 못 하 는 어린이 부츠 에 추가 합 니 다. 여기 서 host 는 호스트 이름, 즉 호스트 이름 을 말 합 니 다.같은 서버 에 동시에 보 내 는 요청 수량 이 5 개 를 초과 할 수 없다 고 이해 할 수 있 습 니 다. 그러나 OKHttp 는 URL 의 호스트 이름 으로 판단 되 기 때문에 같은 ip 주소 에 대한 동시 요청 은 5 개 를 초과 할 수 있 습 니 다. 여러 호스트 이름 이 같은 ip 주소 나 경로 (같은 HTTP 프 록 시)
  • 를 공유 할 수 있 기 때 문 입 니 다.
    어떤 사람들 은 이 대열 을 써 서 무슨 소 용이 있 습 니까? 좋 은 점 은 어디 에 있 습 니까?
    이 대기 열 을 통 해 OKHttp 는 동시 다발 요청 을 쉽게 실현 할 수 있 고 더 편리 한 유지보수 요청 수 와 이 요청 에 대한 후속 작업 (예 를 들 어 취소 요청) 을 알 아야 합 니 다.네트워크 요청 효율 을 크게 향상 시 키 는 동시에 요구 수 를 잘 관리 하고 동시에 실행 하 는 스 레 드 가 너무 많아 서 OOM 을 방지 하 는 동시에 같은 hostname 에서 의 요청 수 를 제한 하여 응용 이 점용 하 는 네트워크 자원 이 너무 많은 것 을 방지 하고 사용자 체험 을 최적화 할 수 있 습 니 다.
    스 레 드 탱크
    OKHttp 는 내부 에서 스 레 드 풀 을 유지 하고 비동기 요청 AsyncCall 을 수행 하 는데 사 용 됩 니 다. 그 구 조 는 다음 과 같 습 니 다.
    private ExecutorService executorService;
    public synchronized ExecutorService executorService() {
      if (executorService == null) {
        executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
            new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
      }
      return executorService;
    }
    

    저희 가 주목 해 야 할 것 은 Thread PoolExecutor 앞의 세 가지 인자 입 니 다.
  • 첫 번 째 는 0 이다. 이 스 레 드 탱크 는 핵심 스 레 드 가 없고 모든 스 레 드 는 작업 스 레 드 이다. 즉, 모든 스 레 드 가 일정한 여가 시간 을 초과 하면 회수 된다
  • .
  • 두 번 째 매개 변 수 는 Integer. MAX VALUE 입 니 다. 즉, 최대 스 레 드 수 입 니 다. 설정 값 이 이렇게 크 지만 성능 소모 에 큰 문 제 를 걱정 할 필요 가 없습니다. 대기 열 이 있어 서 요청 수
  • 를 유지 할 수 있 습 니 다.
  • 세 번 째 매개 변 수 는 60 이다. 즉, 작업 스 레 드 가 60s 남 으 면 회수 된다
  • .
    스 레 드 탱크 에 대한 더 많은 정 보 는 Android 개발 을 참고 할 수 있 습 니 다. - ExecutorService 를 통 해 앱 이 사용 하 는 전역 스 레 드 탱크 를 구축 합 니 다.
    요청 관리
    private int maxRequests = 64;
    private int maxRequestsPerHost = 5;
    

    클래스 에서 이 두 변 수 를 정 의 했 습 니 다. 기본 적 으로 지원 하 는 최대 병발 요청 수량 은 64 개 이 고 하나의 host 병발 요청 의 최대 수량 은 5 개 입 니 다. 이 두 값 은 후속 설정 을 통 해 변경 할 수 있 습 니 다. 또한 이 요 구 는 비동기 요청 에 만 적용 되 며 같은 단계 요청 수량 에 대해 제한 하지 않 습 니 다.
    비동기 요청 이 Dispatcher 에 들 어가 면 위의 두 가지 수량 요 구 를 만족 시 키 면 이 요청 은 running AsyncCalls 에 추 가 된 다음 에 실 행 됩 니 다. 만족 하지 않 으 면 ready AsyncCalls 에 추가 합 니 다. 비동기 요청 이 끝 날 때 ready AsyncCalls 열 을 옮 겨 다 니 며 조건 판단 을 한 다음 조건 에 맞 으 면 이 대기 열 에서 running AsyncCalls 팀 으로 요청 을 옮 깁 니 다.열 에 올 리 고 실행 합 니 다.
    동기 화 요청 이 든 비동기 요청 이 든 최종 적 으로 실행 되면 대기 열 에서 제 거 됩 니 다.
    추가 및 제거 외 에 도 OKHttp 는 사용자 가 추가 한 요청 을 취소 할 수 있 도록 지원 합 니 다. 모두 취소 할 수 있 고 대기 열 이 비 워 질 것 입 니 다. 요청 을 취소 할 수도 있 습 니 다.
    소스 코드
    마지막 으로 우 리 는 소스 코드 를 통 해 Dispatcher 를 알 게 되 었 다.
    public final class Dispatcher {
      //       
      private int maxRequests = 64;
      //           
      private int maxRequestsPerHost = 5;
      
      private Runnable idleCallback;
    
      /**   AsyncCall     */
      private ExecutorService executorService;
    
      /**            . */
      private final Deque readyAsyncCalls = new ArrayDeque<>();
    
      /**            ,              */
      private final Deque runningAsyncCalls = new ArrayDeque<>();
    
      /**            ,              */
      private final Deque runningSyncCalls = new ArrayDeque<>();
    
      //            
      public Dispatcher(ExecutorService executorService) {
        this.executorService = executorService;
      }
    
      public Dispatcher() {
      }
    
      //       
      public synchronized ExecutorService executorService() {
        if (executorService == null) {
          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
              new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
      }
    
      /**
       *             
       */
      public synchronized void setMaxRequests(int maxRequests) {
        if (maxRequests < 1) {
          throw new IllegalArgumentException("max < 1: " + maxRequests);
        }
        this.maxRequests = maxRequests;
        promoteCalls();
      }
    
      public synchronized int getMaxRequests() {
        return maxRequests;
      }
    
      /**
       *                 
       */
      public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
        if (maxRequestsPerHost < 1) {
          throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
        }
        this.maxRequestsPerHost = maxRequestsPerHost;
        promoteCalls();
      }
    
      public synchronized int getMaxRequestsPerHost() {
        return maxRequestsPerHost;
      }
    
      /**
       *            ,          ,    
       */
      public synchronized void setIdleCallback(Runnable idleCallback) {
        this.idleCallback = idleCallback;
      }
    
      /**
       *       
       *               64   host           5   ,      ,      
       *           
       */
      synchronized void enqueue(AsyncCall call) {
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
          runningAsyncCalls.add(call);
          executorService().execute(call);
        } else {
          readyAsyncCalls.add(call);
        }
      }
    
      /**
       *       
       */
      public synchronized void cancelAll() {
        for (AsyncCall call : readyAsyncCalls) {
          call.get().cancel();
        }
    
        for (AsyncCall call : runningAsyncCalls) {
          call.get().cancel();
        }
    
        for (RealCall call : runningSyncCalls) {
          call.cancel();
        }
      }
    
      //      ,                  
      private void promoteCalls() {
        if (runningAsyncCalls.size() >= maxRequests) return; //                ,       .
        if (readyAsyncCalls.isEmpty()) return; //          ,      
    
        //      
        for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
          AsyncCall call = i.next();
          //  host           5   ,       runningAsyncCalls     
          //           
          if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();
            runningAsyncCalls.add(call);
            executorService().execute(call);
          }
    
          if (runningAsyncCalls.size() >= maxRequests) return; //                ,     
        }
      }
    
      /**     host     */
      private int runningCallsForHost(AsyncCall call) {
        int result = 0;
        for (AsyncCall c : runningAsyncCalls) {
          if (c.host().equals(call.host())) result++;
        }
        return result;
      }
    
      /**       ,           */
      synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
      }
    
      /**           . */
      void finished(AsyncCall call) {
        finished(runningAsyncCalls, call, true);
      }
    
      /**           . */
      void finished(RealCall call) {
        finished(runningSyncCalls, call, false);
      }
    
      private  void finished(Deque calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
          //      
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          //         ,    
          if (promoteCalls) promoteCalls();
          //                  
          runningCallsCount = runningCallsCount();
          idleCallback = this.idleCallback;
        }
    
        if (runningCallsCount == 0 && idleCallback != null) {
          idleCallback.run();
        }
      }
    
      /**                   . */
      public synchronized List queuedCalls() {
        List result = new ArrayList<>();
        for (AsyncCall asyncCall : readyAsyncCalls) {
          result.add(asyncCall.get());
        }
        return Collections.unmodifiableList(result);
      }
    
      /**                 . */
      public synchronized List runningCalls() {
        List result = new ArrayList<>();
        result.addAll(runningSyncCalls);
        for (AsyncCall asyncCall : runningAsyncCalls) {
          result.add(asyncCall.get());
        }
        return Collections.unmodifiableList(result);
      }
    
      //             
      public synchronized int queuedCallsCount() {
        return readyAsyncCalls.size();
      }
    
      //           
      public synchronized int runningCallsCount() {
        return runningAsyncCalls.size() + runningSyncCalls.size();
      }
    }
    

    Dispatcher 의 소스 코드 는 비교적 간단 하고 이해 하기 어렵 지 않 으 며 관건 을 파악 해 야 한다 고 할 수 있 습 니 다. 그 역할 은 사용자 의 요청 을 관리 하고 저장 하 며 백업 하 는 것 입 니 다. 중점 은 스 레 드 탱크 를 사용 하여 비동기 요청 을 수행 하 는 것 입 니 다. 동기 화 요청 에 대해 거의 처리 하지 않 습 니 다.

    좋은 웹페이지 즐겨찾기