Retrofitソースの詳細

12454 ワード

Retrofitの使用は古くから行われていたが、なぜRetrofitを使用するかは深く研究されておらず、1つのインタフェースを定義するとともに、インタフェースの方法と方法のパラメータに注釈を加えることでHttpの要求を完了することができ、パラメータと要求結果をどのようにパッケージ化するかも研究されていない.したがって、Retrofitを使用することは常に一知半解の状態であり、その内部の原理が分からないため、Retrofitのソースコードを見るのに少し時間がかかり、Retrofitの要求フロー全体に対して一定の理解が得られた.
Retrofitコアソース解読
1.Retrofitオブジェクトの作成Retrofitオブジェクトを作成するときに使用するのはBuilderモードで、Retrofitオブジェクトを作成するときにRetrofitbaseURLを設定して、自分のconverterFactoryを追加することができます
例:
Retrofit retrofit = new Retrofit.Builder()
           .baseUrl(GITHUB_API)
           .addConverterFactory(GsonConverterFactory.create())
           .build();

注意点:
  • baseUrlが設定されている場合とRetrofit 1.xのバージョンが異なるのは、url'/'でソースコード:
    
    // Retrofit.java#baseUrl method
    
    public Builder baseUrl(HttpUrl baseUrl) {
        ....
       // pathSegments()     url     List(      '/'      ,     split("/")    )
      List pathSegments = baseUrl.pathSegments();
      //   List         (""),           '/'      
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
        .... 
    }
    
    
  • で終わる必要があることです.
  • 独自の変換器を追加しない場合、戻り値はResponseBodyタイプにしかカプセル化できないか、戻り値のソースコードがない:
    // Retrofit.java#Builder
    
    Builder(Platform platform) {
           this.platform = platform;
           //      BuiltInConverter
           converterFactories.add(new BuiltInConverters());
        }
    
    // BuiltInConverters.java#responseBodyConverter method
    
     public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
     
         //       ResponseBody  
        if (type == ResponseBody.class) {
          return Utils.isAnnotationPresent(annotations, Streaming.class)
              ? StreamingResponseBodyConverter.INSTANCE
              : BufferingResponseBodyConverter.INSTANCE;
        }
        
        //         Void  
        if (type == Void.class) {
          return VoidResponseBodyConverter.INSTANCE;
        }
        
        //            
        return null;
      }
    
    
  • .
  • Retrofitオブジェクトを作成するときにBuilderモードを使用しますが、Builderモードを使用するメリットは、作成したオブジェクトが可変オブジェクトであることです(属性はprivateであり、setterメソッドがないため)、これにより、オブジェクト内の属性が変更されたか、またはオブジェクト内の属性が誤って変更されたかを心配する必要がなくなり、他の要求と同じRetrofitオブジェクトを共有できない要求がある場合、ほとんどの属性は同じで一部の属性が一致しないだけで、Retrofitオブジェクトをテンプレートとして使用して、Retrofitオブジェクトの新しいソースコードを作成することができる:
    
    // Retrofit.java#Builder
    
    Builder(Retrofit retrofit) {
        //  retrofit           Retrofit  
      this.platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;
      converterFactories.addAll(retrofit.converterFactories);
      adapterFactories.addAll(retrofit.adapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      adapterFactories.remove(adapterFactories.size() - 1);
      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }
    
  • ここでは追加します.Retrofitは複数のプラットフォーム(jvm, android, ios)をサポートしているので、Retrofitは現在参照されているプラットフォームをどのように見分けているのでしょうか.実際にRetrofitで使用する方法は、現在のclasspathに対応するプラットフォーム特有のclassが存在するかどうかを確認して、現在どのプラットフォームにあるかを判断することである.これとSpringの@ConditionalOnClass(ClassName.class)は、注釈修飾beanが異曲同工の味を持つキーコードを初期化するかどうかを決定する.
    2.送信要求のインタフェースの作成
    送信要求メソッドを含むインタフェースは、他のインタフェースを継承できないインタフェースでなければなりません.
    例:
    
    // Platform.java#findPlatform method 
    //          
    private static Platform findPlatform() {
        try {
          //     "android.os.Build"  android  
          Class.forName("android.os.Build");
          if (Build.VERSION.SDK_INT != 0) {
            return new Android();
          }
        } catch (ClassNotFoundException ignored) {
        }
        try {
          //     "java.util.Optional"  java8  
          Class.forName("java.util.Optional");
          return new Java8();
        } catch (ClassNotFoundException ignored) {
        }
        try {
          //     "org.robovm.apple.foundation.NSObject"  ios  
          Class.forName("org.robovm.apple.foundation.NSObject");
          return new IOS();
        } catch (ClassNotFoundException ignored) {
        }
        
        //     
        return new Platform();
      }
    

    注意点:
  • は、要求メソッドが1つのインタフェースである必要があり、他のインタフェースのソースコードを継承できない場合を含む:
        public interface GitApi {
            @GET("/users/{user}")
            Call getFeed(@Path("user") String user);
        }
    
  • .
  • なぜ1つのインタフェースと1つのリクエスト方法を決定すれば、RetrofitHttpリクエストを送信することができるのでしょうか.Retrofitはエージェントを使用しているため、インタフェースのオブジェクトを作成するときに返されるのはエージェントオブジェクトであり、このエージェントもRetrofitのコアである.ソース:
    static  void validateServiceInterface(Class service) {
    
         // class       
        if (!service.isInterface()) {
          throw new IllegalArgumentException("API declarations must be interfaces.");
        }
        
        //            
        if (service.getInterfaces().length > 0) {
          throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
        }
    }
    
  • では、Retrofitが実際にjdkProxy動的エージェント技術を使用していることが明らかになり、1つのインタフェースを定義し、注釈を加えてhttpの要求を完了することができる.Proxyを使用しているため、抽象クラスではなく1つのインタフェースとして定義しなければならない理由も明らかになった.jdkproxyはインタフェースのエージェントオブジェクトしか生成できないため、ここでcglibライブラリを使用すると抽象クラス
  • と定義することもできる.
    3.Httpリクエストの送信httpリクエストを送信するには、ServiceMethodクラスとOkHttpCallクラスが欠かせません.彼らはHttpリクエストを送信するコアクラスです.まず、Mapから現在のリクエストメソッドに関連付けられたServiceMethodを取得し、見つかったら(説明前にこのメソッドが呼び出された)見つかったServiceMethodを直接使用します.見つからなかったら、そして、新たなServiceMethodが作成する、現在の要求方法に関連付けられたputからMapへと、最終的には、OkHttpCall.executeを呼び出して要求を送信する.
    ソース:
        //          ,     GitApi       
       GitApi git = retrofit.create(GitApi.class);
       
       //     : create  
       public  T create(final Class service) {
       
            //   service                
            Utils.validateServiceInterface(service);
            
            //   validateEagerly==true,               serviceMethodCache ,
            //                 loadServiceMethod   ,         ,             ,
            //              ,                                  
            if (validateEagerly) {
              eagerlyValidateMethods(service);
            }
            
            //     ,
            //               ,           ,         ,     ,    retrofit                
            // service:     , proxy:     , method:      , args:     
            return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
                new InvocationHandler() {
                  private final Platform platform = Platform.get();
        
                  @Override public Object invoke(Object proxy, Method method, Object... args)
                      throws Throwable {
                    
                    //       Object              
                    if (method.getDeclaringClass() == Object.class) {
                      return method.invoke(this, args);
                    }
                    //                                  
                    if (platform.isDefaultMethod(method)) {
                      return platform.invokeDefaultMethod(method, service, proxy, args);
                    }
                    
                    //       ServiceMethod  
                    ServiceMethod serviceMethod =
                        (ServiceMethod) loadServiceMethod(method);
                   //   OkHttpCall              
                    OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                    return serviceMethod.callAdapter.adapt(okHttpCall);
                  }
                });
            }
    
  • 作成ServiceMethodBuildモードを用いる、buildメソッドにおいて要求されたメソッド上の注釈、メソッドパラメータ、戻り値を解析する
  • .
    ソース:
      //                 ServiceMethod
      
        ServiceMethod, ?> loadServiceMethod(Method method) {
            //     
            ServiceMethod, ?> result = serviceMethodCache.get(method);
            if (result != null) return result;
        
            //        (   )
            synchronized (serviceMethodCache) {
              //                   ,                       ,    key        
              result = serviceMethodCache.get(method);
              if (result == null) {
                result = new ServiceMethod.Builder<>(this, method).build();
                serviceMethodCache.put(method, result);
              }
            }
            return result;
        }
    
  • は、Retrofit.java#createproxyにおいて呼び出し要求メソッドが最終的に返すのがreturn serviceMethod.callAdapter.adapt(okHttpCall);であり、デフォルトではOkHttpCallを返すので、最終的に呼び出すのはOkHttpCall.execute()メソッドが要求を送信し、結果を解析する
  • である.
    キーコード:
    public ServiceMethod build() {
     //   callAdapter  (      DefaultCallAdapterFactory), callAdapter    .
     // callAdapter             CallAdapter           okhttp             (DefaultCallAdapterFactory   OkHttpCall.execute          ). 
     //             Call,     CallAdapter.Factory            
     callAdapter = createCallAdapter();
     
     ...
     other code
     ...
     
     //         (    BuiltInConverters),      Retrofit     addConverterFactory     converters
     responseConverter = createResponseConverter();
    
        //         
     for (Annotation annotation : methodAnnotations) {
       parseMethodAnnotation(annotation);
     }
    
     ...
     other code
     ...
     
     //     
     int parameterCount = parameterAnnotationsArray.length;
     parameterHandlers = new ParameterHandler>[parameterCount];
     for (int p = 0; p < parameterCount; p++) {
       Type parameterType = parameterTypes[p];
       if (Utils.hasUnresolvableType(parameterType)) {
         throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
             parameterType);
       }
    
       Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
       if (parameterAnnotations == null) {
         throw parameterError(p, "No Retrofit annotation found.");
       }
    
       parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
     }
    
     ...
              
     ...
    
     return new ServiceMethod<>(this);
    }
    

    まとめ
    これで、Retrofitが要求を送信するプロセス全体が説明され、実際にはプロセス全体のキーはいくつかしかありません.例えば、
  • Retrofit.java#createメソッド、このメソッドの役割はProxyを使用してユーザー定義のインタフェースの実装を作成することであり、使用時にインタフェースを作成し、メソッドを作成し、関連する注釈を追加する3つのステップでhttpリクエストのキー
  • を完了することができる.
  • ServiceMethod.java#buildメソッド、このメソッドの役割はリクエストメソッド上の注釈、メソッドのパラメータ、メソッドの戻り値を解析するとともにcallAdapterオブジェクトを作成することであり、これはRetrofitが残した「ソケット」であり、CallAdapter.Factoryを実現すれば、カスタムメソッド戻り値タイプを処理することができ、メソッドはCallタイプ
  • に戻る必要はない.
  • OkHttpCall.java#executeメソッド.このメソッドの役割は、最下位のokhttpを呼び出して真のhttpリクエストを送信し、戻り結果を解析することです.戻り結果を解析する方法は、RetrofitconverterFactoriesを巡り、戻り結果を解析できるオブジェクトを見つけることです.このRetrofitが残した別の「ソケット」です.Converter.Factoryを実装すれば、Callタイプの
  • のみであるとは限らないカスタム戻り結果を処理することができる.
    また、Retrofit
        @Override public Response execute() throws IOException {
        okhttp3.Call call;
        
        synchronized (this) {
          
          ...
            
          call = rawCall;
          if (call == null) {
            try {
              //   Okhttp call  
              call = rawCall = createRawCall();
            } catch (IOException | RuntimeException e) {
              ...
            }
          }
        }
            
         ....
        
         // call.execute:   okhttp       
         // parseResponse:           ,         retrofit      converterFactory,       converter       
        return parseResponse(call.execute());
      }
    

    この書き込みコンバータは、日常開発のニーズを満たすことができるはずだ.
    以上から分かるように、Retrofitは私たちに帰還タイプをカスタマイズすることを許可することができて、帰還結果は同時に下層のOkHttpの複雑さを遮断して、私たちに1つのインタフェースを定義するだけでHttpの要求の送信を完成させて、これは使用者にとってとても友好的です.