Retrofitソースのhttp情報の綴り込み(二)

14721 ワード

前にRetrofitの詳細http情報の綴り込み(一)では、Retrofitは方法レベルの注釈情報をどのように処理しているかを分析し、パラメータレベルに対する注釈解析は各注釈ごとにParameterHandlerを生成していることを明らかにしました。ここに記録されているのは全て注解の情報です。後から入ってきたので、ServiceMethodにはこれらの情報が存在しないので、ParameterHandlerは注釈の対応する値を記録することなく、注釈情報のみを記録する。本論文ではパラメータレベルの注釈情報を1つずつ分析して、ParameterHandlerをどのように生成しますか?
生成プロセスはパーパーパーパルムの方法で行われ、先にコードを付けます。
private ParameterHandler> parseParameter(
        int p, Type parameterType, Annotation[] annotations) {
      ParameterHandler> result = null;
      for (Annotation annotation : annotations) {
        ParameterHandler> annotationAction = parseParameterAnnotation(
            p, parameterType, annotations, annotation);

        if (annotationAction == null) {
          continue;
        }

        if (result != null) {
          throw parameterError(p, "Multiple Retrofit annotations found, only one allowed.");
        }

        result = annotationAction;
      }

      if (result == null) {
        throw parameterError(p, "No Retrofit annotation found.");
      }

      return result;
    }
この方法の核心はパーパーパルエムAnnotation方法で、残りはいくつかの検査だけです。
    private ParameterHandler> parseParameterAnnotation(
        int p, Type type, Annotation[] annotations, Annotation annotation) {
      if (annotation instanceof Url) {
            //……
      } else if (annotation instanceof Path) {
            //……
      } else if (annotation instanceof Query) {
            //……
      } else if (annotation instanceof QueryName) {
            //……
      } else if (annotation instanceof QueryMap) {
            //……
      } else if (annotation instanceof Header) {
            //…… 
      } else if (annotation instanceof HeaderMap) {
            //…… 
      } else if (annotation instanceof Field) {
            //……
      } else if (annotation instanceof FieldMap) {
            //……
      } else if (annotation instanceof Part) {
            //…… 

      } else if (annotation instanceof PartMap) {
            //……
      } else if (annotation instanceof Body) {
            //……
      }

      return null; // Not a Retrofit annotation.
    }

この方法は非常に長いです。大体の構造は注釈の種類を判断して、それから対応するParameterHandlerを生成します。一つ一つの注釈を分析するParameeter Handlerはどのように生成されますか?
1.@Url
if (annotation instanceof Url) {
        if (gotUrl) {
          throw parameterError(p, "Multiple @Url method annotations found.");
        }
        if (gotPath) {
          throw parameterError(p, "@Path parameters may not be used with @Url.");
        }
        if (gotQuery) {
          throw parameterError(p, "A @Url parameter must not come after a @Query");
        }
        if (relativeUrl != null) {
          throw parameterError(p, "@Url cannot be used with @%s URL", httpMethod);
        }

        gotUrl = true;

        if (type == HttpUrl.class
            || type == String.class
            || type == URI.class
            || (type instanceof Class && "android.net.Uri".equals(((Class>) type).getName()))) {
          return new ParameterHandler.RelativeUrl();
        } else {
          throw parameterError(p,
              "@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
        }

      }
この注釈は動的urlを表示するために使われています。処理は簡単です。いくつかの検査をする以外に、直接にParameterHandler.RelativeUrlを生成しました。このhanderは何をすることができますか?そのアプリを見てみましょう。
 @Override void apply(RequestBuilder builder, @Nullable Object value) {
      checkNotNull(value, "@Url parameter is null.");
      builder.setRelativeUrl(value);
    }
このhandlerは主にRequest BuilderのrelativeUrlにvalueが設置されています。
2.@Path
まず、@Path注解の役割を振り返って、urlのダイナミックなプレースホルダを置換します。
@POST("/{a}") Call postRequestBody(@Path("a") Object a);
複数のプレースホルダがある可能性があるので、ここの注釈にはnameパラメータ「a」が付加されています。有名な本パラメータはどのプレースホルダを置き換えますか?ソースコードを見ます
else if (annotation instanceof Path) {
        if (gotQuery) {
          throw parameterError(p, "A @Path parameter must not come after a @Query.");
        }
        if (gotUrl) {
          throw parameterError(p, "@Path parameters may not be used with @Url.");
        }
        if (relativeUrl == null) {
          throw parameterError(p, "@Path can only be used with relative url on @%s", httpMethod);
        }
        gotPath = true;

        Path path = (Path) annotation;
        String name = path.value();
        validatePathName(p, name);

        Converter, String> converter = retrofit.stringConverter(type, annotations);
        return new ParameterHandler.Path<>(name, converter, path.encoded());

      }
まず、いくつかの一般的な検査を行い、@Path注釈のnameフィールドを取り出し、最後に一つのconverterを加えて、Parameter Handler.Pathオブジェクトを生成した。他のものは全部理解できますが、ここでなぜそのコンバーターの対象が必要ですか?それともこのハンドラのアプリを見に行きますか?
@Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) {
        throw new IllegalArgumentException(
            "Path parameter \"" + name + "\" value must not be null.");
      }
      builder.addPathParam(name, valueConverter.convert(value), encoded);
    }
name、valueをRequest Buiderに置く前にconverterでvalueを処理します。このvalueは私たち自身が方法に入ったものですから、stringかもしれません。他のオブジェクトかもしれません。どう処理するかはこのconverterで決めます。通常は私たちが入ってきたのはStringで、デフォルトで取得するのもToString Coverterですが、もし入ってきたのがStringではないなら、このconverterは私たち自身で定義して、初期化時にRetrafitオブジェクトに入る必要があります。
3.@Query
@Query注解はパラメータをurlの後ろに入れるためのものです。処理のソースコードを見てください。
else if (annotation instanceof Query) {
        Query query = (Query) annotation;
        String name = query.value();
        boolean encoded = query.encoded();

        Class> rawParameterType = Utils.getRawType(type);
        gotQuery = true;
        //              Iterable  ,   List  
        if (Iterable.class.isAssignableFrom(rawParameterType)) {
          if (!(type instanceof ParameterizedType)) {
            throw parameterError(p, rawParameterType.getSimpleName()
                + " must include generic type (e.g., "
                + rawParameterType.getSimpleName()
                + ")");
          }
          //       Iterable,       List
          ParameterizedType parameterizedType = (ParameterizedType) type;
          //         ,      List  String  
          Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
          Converter, String> converter =
              retrofit.stringConverter(iterableType, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded).iterable();
        } else if (rawParameterType.isArray()) {
        //             ,                 
          Class> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
          Converter, String> converter =
              retrofit.stringConverter(arrayComponentType, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded).array();
        } else {
        //        
          Converter, String> converter =
              retrofit.stringConverter(type, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded);
        }

      }
Queryに対するパラメータ処理は主に三つの種類に分けられており、一つは反復可能なタイプであり、例えばlistであり、一つは配列であり、もう一つは一般的なオブジェクトである。まず、一般的なオブジェクトに対する処理です。@Path注解と同じ理屈でconverterオブジェクトが必要です。もちろん、ここにはもう一つのencodedフィールドがあります。主に、現在入ってきたパラメータがurl符号化されていますか?このhandlerのappy方法についても研究してみます。
 @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) return; // Skip null values.

      String queryValue = valueConverter.convert(value);
      if (queryValue == null) return; // Skip converted but null values

      builder.addQueryParam(name, queryValue, encoded);
    }
実は簡単ですよね。valueはconverterで転化して、それに対応する情報トラックRequesetBuiderの中にあります。次に、パラメータは配列またはIterableタイプの処理です。ここでは通常の構造方法によってhandlerオブジェクトを生成するのではなく、iterableとarrayメソッドを呼び出しました。これらはすべて親タイプのものです。ソースを見てみます。
final ParameterHandler> iterable() {
    return new ParameterHandler>() {
      @Override void apply(RequestBuilder builder, @Nullable Iterable values)
          throws IOException {
        if (values == null) return; // Skip null values.

        for (T value : values) {
          ParameterHandler.this.apply(builder, value);
        }
      }
    };
  }

  final ParameterHandler array() {
    return new ParameterHandler() {
      @Override void apply(RequestBuilder builder, @Nullable Object values) throws IOException {
        if (values == null) return; // Skip null values.

        for (int i = 0, size = Array.getLength(values); i < size; i++) {
          //noinspection unchecked
          ParameterHandler.this.apply(builder, (T) Array.get(values, i));
        }
      }
    };
  }
この2つの方法で生成されたオブジェクトはまだ対応するオブジェクトですが、apply方法を書き換えて、通常のhandlerオブジェクト中のapplyメソッドを全ての値を巡回して呼び出します。
4.@QueryName
@Query Nameyはurlにないvalueのkeyを追加します。その処理方法は@Queryとほぼ同じです。valueがないだけです。ここでは紹介しません。
5.@QueryMap
@QueryMapは、Map形式のパラメータをUrlの後に追加するために使用されます。処理のソースコードを見てください。
else if (annotation instanceof QueryMap) {
        Class> rawParameterType = Utils.getRawType(type);
        if (!Map.class.isAssignableFrom(rawParameterType)) {
          throw parameterError(p, "@QueryMap parameter type must be Map.");
        }
        Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
        if (!(mapType instanceof ParameterizedType)) {
          throw parameterError(p, "Map must include generic types (e.g., Map)");
        }
        ParameterizedType parameterizedType = (ParameterizedType) mapType;
        Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
        if (String.class != keyType) {
          throw parameterError(p, "@QueryMap keys must be of type String: " + keyType);
        }
        Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
        Converter, String> valueConverter =
            retrofit.stringConverter(valueType, annotations);

        return new ParameterHandler.QueryMap<>(valueConverter, ((QueryMap) annotation).encoded());

      } 
ここで三つのifは三つの検査を行いました。それぞれの要求されるパラメータはmapタイプでなければなりません。mapの値タイプは基本タイプでなければなりません。mapのkeyはstringタイプでなければなりません。最後に、mapに導入されたvalueのタイプと一つのconverterを利用して、handlerを生成しました。このhandlerはきっとこのmapを遍歴することができます。そしてキーをurlに置くと、applyのコードを見ることができます。
    @Override void apply(RequestBuilder builder, @Nullable Map value)
        throws IOException {
      if (value == null) {
        throw new IllegalArgumentException("Query map was null.");
      }

      for (Map.Entry entry : value.entrySet()) {
        String entryKey = entry.getKey();
        if (entryKey == null) {
          throw new IllegalArgumentException("Query map contained null key.");
        }
        T entryValue = entry.getValue();
        if (entryValue == null) {
          throw new IllegalArgumentException(
              "Query map contained null value for key '" + entryKey + "'.");
        }

        String convertedEntryValue = valueConverter.convert(entryValue);
        if (convertedEntryValue == null) {
          throw new IllegalArgumentException("Query map value '"
              + entryValue
              + "' converted to null by "
              + valueConverter.getClass().getName()
              + " for key '"
              + entryKey
              + "'.");
        }

        builder.addQueryParam(entryKey, convertedEntryValue, encoded);
      }
    }

ここで以前の推測を確認しました。全体のapply方法はmapを巡回して、そして一つ一つのvalueの値をconverterに変換して、最終的にRequest Buiderに伝えます。
6.@ヘッド
@Headerコメントはhttp要求のheaderにパラメータを置くために使われます。この注釈はnameフィールドを設定し、ソースを見てください。
else if (annotation instanceof Header) {
        Header header = (Header) annotation;
        String name = header.value();

        Class> rawParameterType = Utils.getRawType(type);
        if (Iterable.class.isAssignableFrom(rawParameterType)) {
          if (!(type instanceof ParameterizedType)) {
            throw parameterError(p, rawParameterType.getSimpleName()
                + " must include generic type (e.g., "
                + rawParameterType.getSimpleName()
                + ")");
          }
          ParameterizedType parameterizedType = (ParameterizedType) type;
          Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
          Converter, String> converter =
              retrofit.stringConverter(iterableType, annotations);
          return new ParameterHandler.Header<>(name, converter).iterable();
        } else if (rawParameterType.isArray()) {
          Class> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
          Converter, String> converter =
              retrofit.stringConverter(arrayComponentType, annotations);
          return new ParameterHandler.Header<>(name, converter).array();
        } else {
          Converter, String> converter =
              retrofit.stringConverter(type, annotations);
          return new ParameterHandler.Header<>(name, converter);
        }

      } 
@Query注释と同様に、ここにも入ってくるパラメータのタイプを3つに分類します。Iterable、Aray及び普通の単一のタイプに対応して、異なるhanderを生成しました。コアhandlerか、それともParameter Handler.Headerの中のappley方法です。
    @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) return; // Skip null values.

      String headerValue = valueConverter.convert(value);
      if (headerValue == null) return; // Skip converted but null values.

      builder.addHeader(name, headerValue);
    }

ここでは簡単に注解修飾のパラメータをconverterで変換した後、Request Builderに押し込むだけです。Parameeter Handler.iterable()方法及びarray方法を利用して生成されたのもこのhanderであることが分かります。ただ、apply方法では全てのデータを遍歴し、それぞれに上のapply方法を呼び出すだけです。
7.@Header Map
@Header Map注释は、一つのmapのデータをhttp要求のHeaderに追加するもので、全体の処理過程は@Headerと同じで、ただ対応するhandlerのappy方法はこのmapを巡回してから、一つずつRequest Buiderに入れて、ここでコードを貼りません。
8.@Field、@FieldMap
上の7つの注釈の分析を経て、この二つの注釈に対する処理は大体同じで、対応するhandlerを生成して、皆さんがhandlerの中のapply方法を理解すればいいです。ここで自分で分析します。
9.@Partポイント
ここまで書いて、いくつかの注釈の分析を怠けていても、皆さんに残しておきましたが、文章はちょっと長いです。特に@Partの注釈はファイルのアップロードに関わっています。前とは違って、ソースを理解するにはまずhttp協議書のアップロード部分について理解しなければなりません。だから、別の文章を書きます。今回はここに来ます。