retrofit2的enqueue为何也会耗时?

最近发现retrofit2的Callenqueue 方法竟然出现了耗时操作,这究竟是为何呢?下面是一个百度人脸识别接口

    /**
     * 百度M:N人脸识别
     */
    @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 作为的Call的实现之一,而enqueue方法最关键地方在于调用了createRawCall()创建了RealCall对象,看看是否createRawCall耗时了。

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

接着是toCall() ,很明显这是在构建请求参数啊

  /** 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]); 这是在处理请求参数,继续看看它的实现,applyParameterHandler 是一个抽象方法,像 Body、Field、Query等都实现了。而我的接口使用Field 所以查看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);
    }
  }

看来问题出在 addFormField 上了

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

add 作了什么神奇的操作

    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;
    }

add 一个对象到list 不该耗时啊,再看看HttpUrl.canonicalize

  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);
      }
    }
  }

事情就很明显了!???

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

没错,当请求参数是一个很大的字符串时(BASE64编码),canonicalize 就会卡主线程了。

问题关键找到了,问题也很好解决了。

猜你喜欢

转载自blog.csdn.net/CSDNno/article/details/83721564