retrofitのcontent-type浅析

10831 ワード

背景:問題のフィードバックをする時、バックエンドはPythonで書いて、postの複雑なlistの時、1つの穴に出会った.バックエンドpost構造体配列の場合、androidチーム版に伝わるcontent-typeは「text/html;charset=UTF-8」で、バックエンドの同級生は解析できないと言います.ios側から伝わってきたcontent-typeに従って、バックエンドjsonにこのstringを解析させると、渡すstringのフォーマットが面倒になり、各パラメータがkey-valueのフォーマットになります.
後で協議を経て、バックエンドはインタフェースcontent-typeを統一して“application/json;charset=utf-8”に変えて、つまり私達が伝えたのは1つのjsonで、よし、これでiosは問題があって、jsonのcontent-typeを使う時、チーム版のバックエンドは解析できません.すなわち、騎手プラットフォームappフィードバックこのインタフェースは、「アプリケーション/json;charset=utf-8」形式のcontent-typeを単独で使用する必要があり、他のすべてのインタフェースは、「text/html;charset=UTF-8」を使用する必要がある.
私たちandroidとiosを悩ませているcontent-typeはいったい何なのか.
私の理解によると、content-typeはパラメータです.私たちが伝えたパラメータは何ですか.そして、このパラメータに基づいてデータを異なる処理をします.私たちがretrofitを使うときと、responseに戻ってもcontent-typeがあります.content-typeタイプによって、データをどのように読み取るかを決めます.
チーム版でPOSTリクエストをしたとき、なぜcontent-typeはjsonだったりtextだったりしますか?
実際には、インタフェースファイルに@FormUrlEncodedを追加すると、content-typeはtext形式であり、この注釈がない場合、retrofitにGsonRequestBodyConverterを追加するため、このconvertはデフォルトのcontent-typeをjsonに変更することがわかります.つまり、インタフェースに注釈が付いていない場合は、カスタムcontent-typeを使用し、注釈があれば注釈のcontent-typeを使用します.
その事実はそうではないでしょうか.ソースコードを探してみましょう.
エントリ関数Retrofitのcreate関数.
public  T create(final Class service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
  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 {
          // If the method is a method from Object then defer to normal invocation.
          if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
          }
          if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
          }
          ServiceMethod serviceMethod =
              (ServiceMethod) loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

この中でダイナミックエージェントを使う方法で、エージェントが必要なクラスを得るのは、私たちが注釈をつけたクラスです.他は詳しく見ません.サービスMethodは何ですか.
ServiceMethod, ?> loadServiceMethod(Method method) {
  ServiceMethod, ?> result = serviceMethodCache.get(method);
  if (result != null) return result;

  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = new ServiceMethod.Builder<>(this, method).build();
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

戻ってくるのはresult、resultは何ですか?サービスを見てみましょうBuilderは何をしましたか?
Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit;
  this.method = method;
  this.methodAnnotations = method.getAnnotations();
  this.parameterTypes = method.getGenericParameterTypes();
  this.parameterAnnotationsArray = method.getParameterAnnotations();
}

一目瞭然で、注釈のあるクラスの注釈、方法、typeなどを手に入れ、build()でbuilderのパラメータを手に入れ、
Type responseType;
boolean gotField;
boolean gotPart;
boolean gotBody;
boolean gotPath;
boolean gotQuery;
boolean gotUrl;
String httpMethod;
boolean hasBody;
boolean isFormEncoded;
boolean isMultipart;
String relativeUrl;
Headers headers;
MediaType contentType;

ここには私たちが探しているcontentTypeがあります.具体的なbuild()は具体的に見てもいいですが、ここでは詳しく説明しません.
ここまで来たらcontentTypeを手に入れてどうやってネットリクエストしますか?OkHttpCallのenqueue関数を見てみましょう.コードが1行入っています.
call = rawCall = createRawCall();

ここではretrofitが使用するokhttpのcallです.createRawCall関数を見てみましょう.
private okhttp3.Call createRawCall() throws IOException {
  Request request = serviceMethod.toRequest(args);
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

戻るのも確かにokhttpのcallですが、ここのrequestはどこから来たのでしょうか?では、前のserviceMethodのtoRequest関数に戻ります.
Request toRequest(Object... args) throws IOException {
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
      contentType, hasBody, isFormEncoded, isMultipart);

  @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
  ParameterHandler[] handlers = (ParameterHandler[]) parameterHandlers;

  int argumentCount = args != null ? args.length : 0;
  if (argumentCount != handlers.length) {
    throw new IllegalArgumentException("Argument count (" + argumentCount
        + ") doesn't match expected count (" + handlers.length + ")");
  }

  for (int p = 0; p < argumentCount; p++) {
    handlers[p].apply(requestBuilder, args[p]);
  }

  return requestBuilder.build();
}

ははは、RequestBuilderが最終的に私たちが配置したファイルだったのか、入ってみると一目瞭然で、contentTypeが目に入りました.
RequestBuilder(String method, HttpUrl baseUrl, String relativeUrl, Headers headers,
    MediaType contentType, boolean hasBody, boolean isFormEncoded, boolean isMultipart) {
  this.method = method;
  this.baseUrl = baseUrl;
  this.relativeUrl = relativeUrl;
  this.requestBuilder = new Request.Builder();
  this.contentType = contentType;
  this.hasBody = hasBody;

はい、build()関数はokhttp requestを構成するために必要なステップです.contentTypeがどこにあるかを知る必要があります(ここではパラメータをbodyに構成してから、具体的な方法に戻ります).
if (contentType != null) {
  if (body != null) {
    body = new ContentTypeOverridingRequestBody(body, contentType);
  } else {
    requestBuilder.addHeader("Content-Type", contentType.toString());
  }
}

ぐるぐる回って、やっと君を見つけた!私たちの注釈の中にいくつかの注釈があるとき、ここではヘッダーのcontenttypeを直接私たちの注釈の中に置き換えます.注釈にcontentTypeがなかったら?
void addHeader(String name, String value) {
  if ("Content-Type".equalsIgnoreCase(name)) {
    MediaType type = MediaType.parse(value);
    if (type == null) {
      throw new IllegalArgumentException("Malformed content type: " + value);
    }
    contentType = type;
  } else {
    requestBuilder.addHeader(name, value);
  }
}

注記がなければaddHeaderなどでcontentTypeを設定します.
ContentTypeのretrofitでの流れはこのようなもので、迂回していますが、最終的には私たちの観点を確定しました.
インタフェースに注釈が付いていない場合は、カスタムcontent-typeを使用し、注釈があれば注釈のcontent-typeを使用します.
過程は複雑で、結論は簡単で、後でバックエンドと調整する時、何かcontent-typeが間違っている問題に出会ったら、私たちはすぐに問題を見つけることができます.
もちろんretrofitには他の注釈もありますが、興味のある人は理解してください.