Okhttp 3 소스 코드 분석

okhttp 3 소스 코드 분석 은 okhttp 3.10.0 을 바탕 으로 합 니 다.
okhttp 의 특징 및 관련 기능 에 대한 소 개 는 홈 페이지 의 소 개 를 볼 수 있 습 니 다.
  • http://square.github.io/okhttp/
  • https://github.com/square/okhttp/wiki

  • 기본 사용
    okhttp 를 사용 하여 네트워크 요청 을 시작 합 니 다. 다음 절차 만 있 으 면 됩 니 다.
  • OkHttpClient 만 들 기
  • 요청 대상 생 성
  • 요청 에 사용 할 콜 대상 을 만 듭 니 다
  • 네트워크 요청 (동기 요청 execute; 비동기 요청 enqueue)
  • 코드 데모:
    public void request()  {
        
        // 1、  OkHttpClient
        OkHttpClient okHttpClient = new OkHttpClient();
        
        // 2、  Request  
        Request request = new Request.Builder()
                .url("https://blog.csdn.net/kaifa1321")
                .build();
        
        // 3、    Call  ,      
        Call call = okHttpClient.newCall(request);
    
        // 4、      
        
        // 4.1、        ,  Response
        Response response = call.execute();
    
        // 4.2、        ,  Callback  Response
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {}
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //     
                String string = response.body().string();
            }
        });
    }
    
    

    소스 코드 분석
    앞에서 언급 한 okhttp 의 사용 절 차 를 대상 으로 이 절차 의 소스 코드 를 하나씩 분석 할 것 입 니 다.
    OkHttpClient 인 스 턴 스 생 성
    OkHttpClient 대상 의 생 성, okhttp 에서 두 가지 방식 을 제공 합 니 다.
  • 매개 변수 없 는 구조 방법 으로 OkHttpClient 만 들 기
  • OkHttpClient 내부 클래스 Builder 를 통 해 작성 자 모드 로 OkHttpClient 인 스 턴 스 만 들 기
  • // 1、      OkHttpClient
    OkHttpClient okHttpClient = new OkHttpClient();
    
    // 2、  Builder  OkHttpClient
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(null)
            .cache(null)
            .build();
    

    OkHttpClient 실례 화 과정
    OkHttpClient 의 구조 방법 을 살 펴 보 겠 습 니 다.
    public OkHttpClient() {
        this(new OkHttpClient());
    }
    
    OkHttpClient(Builder builder) {
        //            ,      
        this.dispatcher = builder.dispatcher;
        this.interceptors = Util.immutableList(builder.interceptors);
        this.cache = builder.cache;
        this.internalCache = builder.internalCache;
        
        this.connectTimeout = builder.connectTimeout;
        this.readTimeout = builder.readTimeout;
        this.writeTimeout = builder.writeTimeout;
    
    }
    

    위의 소스 코드 를 통 해 알 수 있 듯 이 OkHttpClient 의 무 참 구조 방법 은 마지막 으로 Builder 대상 을 만 들 고 OkHttpClient 의 실례 화 를 완성 합 니 다.사실 무 참 구조 방법 은 OkHttpClient 를 실례 화 하 는 것 과 Builder. build () 방법 을 통 해 실례 화 하 는 원리 가 똑 같 습 니 다. 모두 Builder 대상 을 만들어 OkHttpClient 를 초기 화 하 는 것 입 니 다. 유일 하 게 다른 것 은 Builder 대상 파라미터 (시스템 기본 값 과 개발 자 자체 설정) 입 니 다.
    Builder. build () 방법 을 살 펴 보 겠 습 니 다.
    public OkHttpClient build() {
       //   this  Builder    
      return new OkHttpClient(this);
    }
    
    

    OkHttpClient 는 작성 자 모드 를 통 해 실례 화 를 완 료 했 습 니 다. 이 생 성 모드 는 okhttp 에서 매우 많이 사 용 됩 니 다. 예 를 들 어 Request, Response 의 생 성 은 모두 이 모드 를 사 용 했 습 니 다.
    맞 춤 형 OkHttpClient 인 스 턴 스
    okhttp 홈 페이지 에서 개발 자 에 게 okhttp 을 사용 할 때 OkHttpClient 의 인 스 턴 스 는 단일 인 스 턴 스 를 통 해 okhttp 의 연결 풀 과 스 레 드 풀 을 잘 관리 하 는 것 이 좋 습 니 다.그러나 일반적으로 응용 개발 에서 차단 기 를 추가 하거나 캐 시 를 추가 하 는 등 특별한 요청 을 받 을 수 있 습 니 다.단일 인 스 턴 스 의 OkHttpClient 를 직접 수정 하면 다른 요청 에 영향 을 줄 수 있 습 니 다. okhttp 도 이러한 상황 을 고려 하여 OkHttpClient 인 스 턴 스 를 맞 춤 형 으로 만 드 는 방법 new Buidler 를 제공 합 니 다.
      OkHttpClient client = new OkHttpClient();
      client.newBuilder()
              .readTimeout(10, TimeUnit.SECONDS)
              .writeTimeout(10, TimeUnit.SECONDS)
              .build();
    

    여기 뉴 빌 더 방법 을 보 겠 습 니 다.
      public Builder newBuilder() {
        // this  OkHttpClient    
        return new Builder(this);
      }
    
    

    기 존의 OkHttpClient 인 스 턴 스 를 통 해 새로운 Builder 대상 을 만 듭 니 다. 이 Builder 대상 은 기본적으로 원래 OkHttpClient 인 스 턴 스 의 속성 을 가지 고 있 습 니 다. 그러면 존재 하 는 OkHttpClient 의 관련 설정 을 완전히 재 활용 할 수 있 습 니 다. 특정한 속성 만 수정 하면 됩 니 다.예 를 들 어 readTimeout, writeTimeout 등 이다.
    요청 대상 만 들 기
    Request 대상 의 주요 역할 은 Http 요청 의 주요 매개 변 수 를 봉인 하 는 것 입 니 다. 이 매개 변 수 는 다음 과 같 습 니 다.
  • url
  • http 요청 방식: GET, POST 등
  • 요청 체 RequestBody, POST 등 요청 방식 시 필요
  • http 요청 헤더
  • Request 대상 은 OkHttpClient 와 마찬가지 로 작성 자 모드 를 통 해 만 들 어 졌 습 니 다.
    요청 대상 생 성:
        Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")
            .addHeader("Accept", "application/json; q=0.5")
            .addHeader("Accept", "application/vnd.github.v3+json")
            .post(RequestBody.create(MediaType, File))
            .build();
    

    Request 의 생 성 절 차 는 상대 적 으로 간단 합 니 다. 위 에 서 는 기본적으로 한눈 에 알 수 있 습 니 다. 여 기 는 너무 많은 소 개 를 하지 않 습 니 다.
    위의 코드 예제 에서 주의해 야 할 것 은 Request 에 Header 요청 헤드 를 추가 하 는 방법 header () 와 addHeader () 입 니 다.
    Request 의 원본 코드 에서 도 두 가지 방법 에 대해 구체 적 으로 설명 했다.
  • header (): 유일한 요청 헤더 키 쌍 을 설정 합 니 다. 같은 name 의 요청 헤더 가 존재 하면 value 는 덮어 씁 니 다.
  • addHeader (): 같은 name 의 요청 헤더 에 여러 개의 Value
  • 를 설정 할 수 있 습 니 다.
    RequestBody
    Request 를 만 드 는 과정 에서 이 Request 가 POST 요청 이 라면 이 Request 에 요청 체 RequestBody 를 만들어 야 합 니 다. RequestBody 의 생 성 은 정적 방법 create () 를 호출 해 야 합 니 다. RequestBody 에는 여러 개의 create () 방법 이 있 습 니 다. 주로 요청 체 유형 에 따라 요청 체 인 스 턴 스 를 만 듭 니 다.
    여기 서 RequestBody 류 의 구체 적 인 내용 을 살 펴 보 겠 습 니 다.
        public abstract class RequestBody {
            //      
            public abstract @Nullable MediaType contentType();
    
            //      
            public long contentLength() throws IOException {
                return -1;
            }
    
           
            //         sink,sink okio   ,        
            public abstract void writeTo(BufferedSink sink) throws IOException;
    
            /**
             *        ,
             */
            public static RequestBody create(@Nullable MediaType contentType, String content) {
                //   UTF-8  
                Charset charset = Util.UTF_8;
                if (contentType != null) {
                    charset = contentType.charset();
                    if (charset == null) {
                        charset = Util.UTF_8;
                        contentType = MediaType.parse(contentType + "; charset=utf-8");
                    }
                }
                byte[] bytes = content.getBytes(charset);
                return create(contentType, bytes);
            }
    
            /**
             *      
             */
            public static RequestBody create(
                    final @Nullable MediaType contentType, final ByteString content) {
                return new RequestBody() {
                    @Override public @Nullable MediaType contentType() {
                        return contentType;
                    }
    
                    @Override public long contentLength() throws IOException {
                        return content.size();
                    }
    
                    @Override public void writeTo(BufferedSink sink) throws IOException {
                        sink.write(content);
                    }
                };
            }
    
            /**
             *      
             */
            public static RequestBody create(final @Nullable MediaType contentType, final byte[] content) {
                return create(contentType, content, 0, content.length);
            }
    
           
            /**
             *      
             */
            public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,
                                             final int offset, final int byteCount) {
                if (content == null) throw new NullPointerException("content == null");
                Util.checkOffsetAndCount(content.length, offset, byteCount);
                return new RequestBody() {
                    @Override public @Nullable MediaType contentType() {
                        return contentType;
                    }
    
                    @Override public long contentLength() {
                        return byteCount;
                    }
    
                    @Override public void writeTo(BufferedSink sink) throws IOException {
                        //          sink
                        sink.write(content, offset, byteCount);
                    }
                };
            }
    
            /**
             *        ,      file,         
             */
            public static RequestBody create(final @Nullable MediaType contentType, final File file) {
                if (file == null) throw new NullPointerException("content == null");
    
                return new RequestBody() {
                    @Override public @Nullable MediaType contentType() {
                        return contentType;
                    }
    
                    @Override public long contentLength() {
                        return file.length();
                    }
    
                    @Override public void writeTo(BufferedSink sink) throws IOException {
                        Source source = null;
                        try {
                            source = Okio.source(file);
                            sink.writeAll(source);
                        } finally {
                            Util.closeQuietly(source);
                        }
                    }
                };
            }
        }
    

    여기 핵심 코드 를 드 리 겠 습 니 다.
    
    public static class Builder {
    
        Headers.Builder headers;
    
        public Builder header(String name, String value) {
          headers.set(name, value);
          return this;
        }
        
        public Builder addHeader(String name, String value) {
          headers.add(name, value);
          return this;
        }
    }
    
    
    public final class Headers {
    
        final List namesAndValues = new ArrayList<>(20);
    
        //        
        public Builder add(String name, String value) {
          checkNameAndValue(name, value);
          return addLenient(name, value);
        }
    
    
        //        
        public Builder set(String name, String value) {
          checkNameAndValue(name, value);
          //     ,  
          removeAll(name);
          addLenient(name, value);
          return this;
        }
        
        //    list   
        Builder addLenient(String name, String value) {
          namesAndValues.add(name);
          namesAndValues.add(value.trim());
          return this;
        }
    }
    

    호출 대상 만 들 기
    // 3、    Call    
    Call call = okHttpClient.newCall(request);
    

    okHttpClient. newCall () 방법 에서 RealCall. newRealCall () 방법 으로 Call (RealCall) 인 스 턴 스 를 가 져 옵 니 다.
    @Override 
    public Call newCall(Request request) {
        //     Call(RealCall)  
        return RealCall.newRealCall(this, request, false /* for web socket */);
    }
    
    

    네트워크 요청 시작
    다음 절 참조: Call 인터페이스 및 구현 클래스 RealCall
    Call 인터페이스 및 구현 클래스 RealCall
    Call
    Call 인터페이스, 주로 네트워크 요청 호출 을 실현 합 니 다.
  • execute 방법 을 통 해 동기 화 요청 을 실현 하여 요청 응답 을 가 져 옵 니 다.
  • enqueue 방법 을 통 해 비동기 요청 을 실현 하고 CallBack 리 셋 을 통 해 요청 응답 을 가 져 옵 니 다.

  • 콜 인터페이스 소스 코드
    public interface Call extends Cloneable {
    
      //         
      Request request();
    
      //     
      Response execute() throws IOException;
    
      //     
      void enqueue(Callback responseCallback);
    
      //     
      void cancel();
    
      //       
      boolean isExecuted();
    
      //     
      boolean isCanceled();
    
      Call clone();
    
      interface Factory {
        Call newCall(Request request);
      }
    }
    
    

    RealCall
    RealCall 은 Call 인터페이스의 유일한 실현 클래스 이기 때문에 okhttp 의 네트워크 요청 은 모두 RealCall 클래스 에서 이 루어 집 니 다.그래서 여기 서 리 얼 콜 에 대한 상세 한 분석 을 할 겁 니 다.
    RealCall 인 스 턴 스 생 성
    위의 글 에서 RealCall 대상 은 okHttpClient. newCall () 방법 에서 RealCall. newRealCall 방법 을 호출 하여 만 들 었 습 니 다.
    RealCall. newRealCall () 방법 을 살 펴 보 겠 습 니 다.
    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        // Safely publish the Call instance to the EventListener.
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.eventListener = client.eventListenerFactory().create(call);
        return call;
    }
    

    이 방법 에 서 는 주로 구조 적 방법 을 통 해 RealCall 대상 의 생 성 을 완성 하고 이 RealCall 대상 은 OkHttpClient 와 Requst 인 스 턴 스 를 가지 고 있 습 니 다.
    RealCall 대상 이 생 성 되면 정식 네트워크 요청 을 시작 할 수 있 습 니 다. 여 기 는 동기 화 요청 과 비동기 요청 으로 나 뉜 다.
    
    //         ,  Response
    try {
        Response response = call.execute();
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    
    //         ,  Callback  Response
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {}
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            //     
            String string = response.body().string();
        }
    });
    
    

    여기 서 이 두 가지 요구 방식 에 대해 일일이 분석 하 다.
    execute 동기 화 요청
    RealCall.execute()
      public Response execute() throws IOException {
    
        // 1、     Call    
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
    
    
        try {
    
          // 2、  OkHttpClient  dispatcher executed  
          client.dispatcher().executed(this);
    
          // 3、        http  Response
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } catch (IOException e) {
          eventListener.callFailed(this, e);
          throw e;
        } finally {
    
          // 4、    ,           Call
          client.dispatcher().finished(this);
        }
      }
    

    execute () 방법 에서 주로 다음 과 같은 기능 을 완성 했다.
  • 우선, 이 요청 이 실 행 됐 는 지 여 부 를 판단 하고, 하나의 Call 스마트 가 한 번
  • 그 다음 에 OkHttpClient. dispatcher 대상 의 executed 방법 을 호출 하여 동기 화 요청 대기 열 에 요청 을 넣 습 니 다.
  • 그리고 okhttp 의 차단기 체인 을 통 해 http 응답 을 가 져 옵 니 다
  • 마지막 으로 OkHttpClient. dispatcher 대상 을 호출 하 는 finished () 방법 으로 요청 을 끝 냅 니 다 (이것 은 다음 디 스 패 치 배포 기 에서 구체 적 으로 분석 합 니 다)
  • 동기 화 요청 에서 최종 적 으로 RealCall. getResponse With Interceptor Chain () 방법 으로 응답 을 얻 을 수 있 습 니 다. 이 방법의 이름 을 통 해 이 방법의 역할 을 알 수 있 습 니 다. 차단기 체인 을 통 해 응답 을 얻 을 수 있 습 니 다.
    핵심 기능: Dispatcher 배포 기.이 는 후속 장 에서 구체 적 인 분석 을 할 것 입 니 다. 동기 화 비동기 요청 이 최종 적 으로 Dispatcher 요청 배포 기 에서 이 루어 졌 다 는 것 만 알 면 됩 니 다.
    핵심 기능: Interceptor 차단기.이것 은 후속 장 에서 구체 적 인 분석 을 할 것 입 니 다. 여 기 는 Http 응답 을 얻 는 것 이 Intercepter 차단 기 를 통 해 이 루어 진 것 임 만 알 면 됩 니 다.
    enqueue 비동기 요청
    RealCall.enqueue(Callback)
      @Override 
      public void enqueue(Callback responseCallback) {
    
            // 1、     Call    
            synchronized (this) {
              if (executed) throw new IllegalStateException("Already Executed");
              executed = true;
            }
            captureCallStackTrace();
            eventListener.callStart(this);
    
            //     AsyncCall  ,           
            client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    

    enqueue () 방법 에서 주로 다음 과 같은 기능 을 완성 하 였 습 니 다.
  • 우선, 이 요청 이 실행 되 었 는 지 여 부 를 판단 하고, 하나의 콜 은 한 번 만 실행 할 수 있다
  • 그리고 OkHttpClient. dispatcher 대상 의 enqueue () 방법 을 호출 하여 비동기 요청 대기 열 에 요청 을 추가 합 니 다.

  • 위의 enqueue 방법 에서 마지막 으로 client. dispatcher (). enqueue (new AsyncCall (responseCallback) 를 실행 할 때 AsyncCall 대상 을 만 들 었 습 니 다.
    이 AsyncCall 에 대한 추가 분석 이 필요 합 니 다.
    AsyncCall
    AsyncCall 은 RealCall 의 내부 클래스 로 AsyncCall 은 NamedRunnable 에서 계승 되 고 NamedRunnable 은 Runnable 인 터 페 이 스 를 실현 하기 때문에 AsyncCall 의 주요 역할 은 Runnable 인 터 페 이 스 를 실현 하 는 것 이다.
    NamedRunnable
    public abstract class NamedRunnable implements Runnable {
      protected final String name;
    
      public NamedRunnable(String format, Object... args) {
        this.name = Util.format(format, args);
      }
    
      @Override public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
          execute();
        } finally {
          Thread.currentThread().setName(oldName);
        }
      }
    
      protected abstract void execute();
    }
    

    NamedRunnable 은 추상 적 인 유형 으로 Runnable 인 터 페 이 스 를 실현 했다. 이 유형 에서 추상 적 인 방법 execute () 를 정 의 했 는데 주로 구체 적 인 비동기 작업 을 실현 하 는 것 이다. 여기 서 이런 유형 에 대해 지나치게 설명 하지 않 는 다.
    다음은 NamedRunnable 의 하위 클래스 AsyncCall 을 살 펴 보 겠 습 니 다.
    AsyncCall
      final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
    
        AsyncCall(Callback responseCallback) {
          super("OkHttp %s", redactedUrl());
          this.responseCallback = responseCallback;
        }
    
        String host() {
          return originalRequest.url().host();
        }
    
        Request request() {
          return originalRequest;
        }
    
        RealCall get() {
          return RealCall.this;
        }
    
        @Override protected void execute() {
          boolean signalledCallback = false;
          try {
    
            //               
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
              signalledCallback = true;
              responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
              signalledCallback = true;
              responseCallback.onResponse(RealCall.this, response);
            }
          } catch (IOException e) {
            if (signalledCallback) {
              // Do not signal the callback twice!
              Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              eventListener.callFailed(RealCall.this, e);
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            //       ,              
            client.dispatcher().finished(this);
          }
        }
      }
    
    

    AsyncCall 의 execute 방법 에서 주요 역할 은 두 가지 입 니 다.
  • RealCall. getResponse With Interceptor Chain () 방법 으로 요청 응답 을 얻 습 니 다. 이것 은 동기 화 요청 방법 과 같은 방법 으로 호출 됩 니 다.
  • 네트워크 응답 을 가 져 온 후 client. dispatcher (). finished (this) 를 통 해 이 요청 을 비동기 요청 대기 열 에서 제거 합 니 다. 이 요청 이 처리 되 었 음 을 표시 하고 스 레 드 풀 에 다른 비동기 대기 열 에서 요청 을 수행 하도록 합 니 다.

  • 이 를 보면 알 수 있 을 것 입 니 다. 비동기 처리 프로 세 스 는 위의 RealCall. execute () 동기 화 요청 과 똑 같 습 니 다. 유일한 차이 점 은 메 인 스 레 드 에서 이 루어 진 것 입 니 다. 하 나 는 하위 스 레 드 (AsyncCall) 에서 이 루어 진 것 입 니 다.
    Dispatcher 배포 기와 Interceptor 차단기 에 대해 서 는 다음 장 에서 구체 적 으로 소개 합 니 다.

    좋은 웹페이지 즐겨찾기