Why does the enqueue of retrofit2 take time?

Recently, it was found that the enqueue method of Call in retrofit2 was time-consuming. Why? Here is a Baidu face recognition interface

    /**
     * Baidu M:N face recognition
     */
    @POST
    @FormUrlEncoded
    Call<BaiduMultiSearchFaceResp> searchMultiFacesByBaiDu(@Url String url,
                                                           @Field("image") String image,
                                                           @Field("image_type") String imageType,
                                                           @Field("group_id_list") String groupIdList,
                                                           @Field("liveness_control") String livenessControl,
                                                           @Field("max_face_num") Integer maxFaceNum,
                                                           @Field("max_user_num") String maxUserNum,
                                                           @Field("match_threshold") String matchThreshold);

OkHttpCall is one of the implementation of Call, and the key point of enqueue method is to Call createRawCall() to create RealCall object to see if createRawCall is time-consuming.

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = serviceMethod.toCall(args);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

Then there is toCall(), which is obviously building request parameters

  /** Builds an HTTP request from method arguments. */
  okhttp3.Call toCall(@Nullable 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<Object>[] handlers = (ParameterHandler<Object>[]) 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 callFactory.newCall(requestBuilder.build());
  }

handlers[p].apply(requestBuilder, args[p]); this is to process the request parameters and continue to see its implementation. Apply is ParameterHandler, which is an abstract method, such as Body, Field, Query, etc. And my interface uses Field, so look at the implementation of Field apply

    @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) return; // Skip null values.

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

      builder.addFormField(name, fieldValue, encoded);
    }
  }

Looks like the problem is with addFormField

  void addFormField(String name, String value, boolean encoded) {
    if (encoded) {
      formBuilder.addEncoded(name, value);
    } else {
      formBuilder.add(name, value);
    }
  }

What magic operation did add do

    public Builder add(String name, String value) {
      if (name == null) throw new NullPointerException("name == null");
      if (value == null) throw new NullPointerException("value == null");

      names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true, charset));
      values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true, charset));
      return this;
    }

    public Builder addEncoded(String name, String value) {
      if (name == null) throw new NullPointerException("name == null");
      if (value == null) throw new NullPointerException("value == null");

      names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true, charset));
      values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true, charset));
      return this;
    }

It shouldn't take time to add an object to the list. Look at HttpUrl.canonicalize again

  static String canonicalize(String input, int pos, int limit, String encodeSet,
      boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly,
      Charset charset) {
    int codePoint;
    for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
      codePoint = input.codePointAt(i);
      if (codePoint < 0x20
          || codePoint == 0x7f
          || codePoint >= 0x80 && asciiOnly
          || encodeSet.indexOf(codePoint) != -1
          || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))
          || codePoint == '+' && plusIsSpace) {
        // Slow path: the character at i requires encoding!
        Buffer out = new Buffer();
        out.writeUtf8(input, pos, i);
        canonicalize(out, input, i, limit, encodeSet, alreadyEncoded, strict, plusIsSpace,
            asciiOnly, charset);
        return out.readUtf8();
      }
    }

    // Fast path: no characters in [pos..limit) required encoding.
    return input.substring(pos, limit);
  }
  static void canonicalize(Buffer out, String input, int pos, int limit, String encodeSet,
      boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly,
      Charset charset) {
    Buffer encodedCharBuffer = null; // Lazily allocated.
    int codePoint;
    for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
      codePoint = input.codePointAt(i);
      if (alreadyEncoded
          && (codePoint == '\t' || codePoint == '\n' || codePoint == '\f' || codePoint == '\r')) {
        // Skip this character.
      } else if (codePoint == '+' && plusIsSpace) {
        // Encode '+' as '%2B' since we permit ' ' to be encoded as either '+' or '%20'.
        out.writeUtf8(alreadyEncoded ? "+" : "%2B");
      } else if (codePoint < 0x20
          || codePoint == 0x7f
          || codePoint >= 0x80 && asciiOnly
          || encodeSet.indexOf(codePoint) != -1
          || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))) {
        // Percent encode this character.
        if (encodedCharBuffer == null) {
          encodedCharBuffer = new Buffer();
        }

        if (charset == null || charset.equals(Util.UTF_8)) {
          encodedCharBuffer.writeUtf8CodePoint(codePoint);
        } else {
          encodedCharBuffer.writeString(input, i, i + Character.charCount(codePoint), charset);
        }

        while (!encodedCharBuffer.exhausted()) {
          int b = encodedCharBuffer.readByte() & 0xff;
          out.writeByte('%');
          out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]);
          out.writeByte(HEX_DIGITS[b & 0xf]);
        }
      } else {
        // This character doesn't need encoding. Just copy it over.
        out.writeUtf8CodePoint(codePoint);
      }
    }
  }

It's obvious!???

 Call<BaiduMultiSearchFaceResp> call = getServer().searchMultiFacesByBaiDu(BaiduFaceApiHelper.getApiFaceMultiSearch(),
                imageParam, Constants.BaiDuSupportImageType.BASE64, schoolId, "NONE", CameraPreviewLayout.MAX_FACE_NUM,
                "1", "80");

Yes, when the request parameter is a large string (BASE64 encoding), canonicalize will card the main thread.

The key to the problem has been found and the problem has been solved very well.

Keywords: Mobile encoding

Added by bookbuyer2000 on Tue, 10 Dec 2019 20:54:02 +0200