文章

Retrofit

Retrofit

Retrofit 基本用法

Retrofit 是什么?

是一个不错的 REST 网络请求库,官方介绍:A type-safe REST client for Android and Java

Retrofit API 使用

Retrofit 异步请求

Call 的 enqueue(callback)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface ApiGithubUser {
    @GET("users/{user}")
    Call<GithubUser> getGithubUser(@NonNull @Path("user") String user);
}

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BaseUrl.GITHUB_USER_INFO)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

ApiGithubUser apiGithubUser = retrofit.create(ApiGithubUser.class);
Call<GithubUser> githubUserCall = apiGithubUser.getGithubUser("hacket");
githubUserCall.enqueue(new Callback<GithubUser>() {
    @Override
    public void onResponse(Call<GithubUser> call, Response<GithubUser> response) {
        GithubUser body = response.body(); 
        mTvResult.setText(body.toString());
    }

    @Override
    public void onFailure(Call<GithubUser> call, Throwable t) {
        t.printStackTrace();
        mTvResult.setText(t.getMessage());
    }
});

Retrofit 同步请求

Call 的 execute()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface ApiGithubUser {
    @GET("users/{user}")
    Call<GithubUser> getGithubUser(@NonNull @Path("user") String user);
}

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

ApiGithubUser apiGithubUser = retrofit.create(ApiGithubUser.class);
Call<GithubUser> githubUserCall = apiGithubUser.getGithubUser("hacket");
try {
    Response<GithubUser> githubUserResponse = githubUserCall.execute();
    if (githubUserResponse.isSuccessful()) {
        return githubUserResponse.body();
    }
} catch (IOException e) {
    e.printStackTrace();
}

GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface ZhihuService {
    // 获取启动页大图 1080*1776
    @GET("api/{api}/start-image/{size}")
    Call<StartImageBean> getStartImage(@NonNull @Path("api") String api, @NonNull @Path("size") String size);
}

final Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BaseUrl.ZHIHU_NEWS)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
ZhihuService zhihuService = retrofit.create(ZhihuService.class);
final Call<StartImageBean> startImage = zhihuService.getStartImage("4", "1080*1776");
startImage.enqueue(new Callback<StartImageBean>() {
    @Override
    public void onResponse(Call<StartImageBean> call, Response<StartImageBean> response) {
        URL url = call.request().url().url();
        LogUtil.i(TAG, "onResponse: url:" + url);
        boolean successful = response.isSuccessful();
        LogUtil.i(TAG, "successful:" + successful);
        if (successful) {
            StartImageBean startImageBean = response.body();
            mTvResult.setText(startImageBean.text);
            Glide.with(RetrofitDemoActivity.this).load(startImageBean.img).into(iv_result);
        }
    }
    @Override
    public void onFailure(Call<StartImageBean> call, Throwable t) {
        URL url = call.request().url().url();
        LogUtil.i(TAG, "onResponse: url:" + url);
        mTvResult.setText(t.getMessage());
    }
});
}

Post

和 GET 差不多;区别是用 @Body 包装请求体

CallAdapter

https://github.com/square/retrofit/wiki/Call-Adapters

适配 Call 的返回类型 R 转换成 T,通过 CallAdapter.Factory 创建,通过 addCallAdapterFactory 添加

Retrofit2 提供了三个 CallAdapter:

  • RxJava[Observable](https://github.com/ReactiveX/RxJava/)&[Single](https://github.com/ReactiveX/RxJava/) - com.squareup.retrofit2:adapter-rxjava
  • Guava[ListenableFuture](https://github.com/google/guava/) - com.squareup.retrofit2:adapter-guava
  • Java 8[CompleteableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) - com.squareup.retrofit2:adapter-java8

还有一些第三方的库 Adapter:

CallAdapter.Factory

Converter & Converter.Factory

https://github.com/square/retrofit/wiki/Converters

Retrofit 允许以插件的形式来不同的序列化将 Java Type 转换成 HTTP 的描述;反序列化解析 HTTP 返回结果

Converter

Converter.Factory

创建 Converter 的工厂

  • Gson - com.squareup.retrofit2:converter-gson
  • Jackson - com.squareup.retrofit2:converter-jackson
  • Moshi - com.squareup.retrofit2:converter-moshi
  • Protobuf - com.squareup.retrofit2:converter-protobuf
  • Wire - com.squareup.retrofit2:converter-wire
  • Simple Framework - com.squareup.retrofit2:converter-simpleframework
  • Scalars - com.squareup.retrofit2:converter-scalars
    还有一些第三方的 converters:
  • LoganSquare - com.github.aurae.retrofit2:converter-logansquare
  • FastJson - org.ligboy.retrofit2:converter-fastjson ororg.ligboy.retrofit2:converter-fastjson-android

大文件下载

文件下载我们需要使用 @Url@Streaming,@Url 动态 Url 正好非常适合我们的场景,而使用@Streaming 注解可以让我们下载非常大的文件时,避免 Retrofit 将整个文件读进内存,否则可能造成 OOM 现象。
声明接口:

1
2
3
4
5
public interface BigDownloadService {
    @Streaming
    @GET
    Call<ResponseBody> downloadFileByDynamicUrlAsync(@Url String downloadUrl);
}

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@OnClick(R.id.btn_retrofit_down_big_file)
public void btn_retrofit_down_big_file() {
    final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BaseUrl.ZHIHU_NEWS)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    String url = "http://img-download.pchome.net/download/1k0/ur/4q/oc3l47-189v.jpg";

    Observable.just(url)
            .map(new Func1<String, String>() {
                @Override
                public String call(String url) {
                    BigDownloadService bigDownloadService = retrofit.create(BigDownloadService.class);
                    Call<ResponseBody> responseBodyCall = bigDownloadService.downloadFileByDynamicUrlAsync(url);
                    try {
                        Response<ResponseBody> bodyResponse = responseBodyCall.execute();
                        String imgUrl = writeResponseBodyToDisk(bodyResponse.body());
                        LogUtil.d(TAG, "下载文件 " + imgUrl);
                        return imgUrl;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return "";
                }
            })
            .compose(RxUtils.<String>applySchedulers())
            .subscribe(new Action1<String>() {
                @Override
                public void call(String url) {
                    mTvResult.setText("url:" + url);
                    Glide.with(RetrofitDemoActivity.this).load(url).into(iv_result);
                }
            })
    ;
}

/**
 * 写入到磁盘根目录
 */
private String writeResponseBodyToDisk(ResponseBody body) {
    try {
        File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "atom.jpg");
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            byte[] fileReader = new byte[4096];
            final long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;
            inputStream = body.byteStream();
            outputStream = new FileOutputStream(futureStudioIconFile);
            while (true) {
                int read = inputStream.read(fileReader);
                if (read == -1) {
                    break;
                }
                outputStream.write(fileReader, 0, read);
                fileSizeDownloaded += read;
                LogUtil.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
                final long finalFileSizeDownloaded = fileSizeDownloaded;
                LogUtil.i(TAG, "file download: " + finalFileSizeDownloaded + " of " + fileSize);
            }
            outputStream.flush();
            return futureStudioIconFile.getAbsolutePath();
        } catch (IOException e) {
            return "";
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    } catch (IOException e) {
        return "";
    }
}

Retrofit2.x Proguard

1
2
3
4
5
6
7
8
9
10
# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on RoboVM on iOS. Will not be used at runtime.
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions

表单上传(application/x-www-form-urlencoded)

表单上传,参数很少

1
2
3
@FormUrlEncoded
@POST("upload")
Call<ResponseBody> uploadParams(@Field("username")String username,@Field("token")String token);

表单上传,参数很多

1
2
3
@FormUrlEncoded
@POST("apiv2/app/getCOSToken")
Observable<COSTokenResponse1> getCOSToken(@FieldMap Map<String, String> formMap);

文件上传

单个文件上传 @Multipart

文件上传我们需要使用 @MultiPart@Part ,MultiPart 意思就是允许多个@Part 多部分上传,需要在@Part指定 file 和 filename 的值,避免一些不必要的麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Multipart
@POST
Observable<UploadResponse2> doUpload(@Url String url, @Part List<MultipartBody.Part> parts);

static Observable<UploadResponse2> uploadApkToCosObservable(String endpointUrl, UploadRequest2 req2) {
    
    RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), req2.file);
    MultipartBody multipartBody = new MultipartBody.Builder()
            .addFormDataPart("key", req2.key)
            .addFormDataPart("signature", req2.signature)
            .addFormDataPart("x-cos-security-token", req2.token)
            .addFormDataPart("file", req2.filename, body)
            .setType(MultipartBody.FORM)
            .build();

    return NetManager.createRetrofit().create(PgyerService.class)
            .doUpload(endpointUrl, multipartBody.parts());
}

多文件上传 @PartMap

  1. 使用@PartMap 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
@Multipart
@POST("upload")
Call<ResponseBody> uploadFiles(@PartMap Map<String, RequestBody> map);

RequestBody fb = RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
RequestBody fileTwo = RequestBody.create(MediaType.parse("image/*"), new File(Environment.getExternalStorageDirectory()
                + file.separator + "original.png"));
Map<String, RequestBody> map = new HashMap<>();
//这里的key必须这么写,否则服务端无法识别
map.put("file\"; filename=\""+ file.getName(), fileRQ);
map.put("file\"; filename=\""+ "2.png", fileTwo);

Call<ResponseBody> uploadCall = downloadService.uploadFiles(map);
  1. 使用@Part 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Multipart
@POST("upload")
Call<ResponseBody> uploadFiles(@Part List<MultipartBody.Part> parts);
RequestBody fileRQ = RequestBody.create(MediaType.parse("image/*"), file);

MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);

RequestBody fb = RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
RequestBody fileTwo = RequestBody.create(MediaType.parse("image/*"), new File(Environment.getExternalStorageDirectory()
                + file.separator + "original.png"));
MultipartBody.Part two=MultipartBody.Part.createFormData("one","one.png",fileTwo);
List<MultipartBody.Part> parts=new ArrayList<>();
parts.add(part);
parts.add(two);

Call<ResponseBody> uploadCall = downloadService.uploadFiles(parts);

文件和参数混合上传

1
2
3
4
5
6
7
@Multipart
@POST("upload")
Call<ResponseBody> uploadFile(@Part("body") RequestBody body, @Part MultipartBody.Part file);

MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);
RequestBody fb =RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
Call<ResponseBody> uploadCall = downloadService.uploadFile(fb,part);

通用上传方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@POST("upload")
Call<ResponseBody> uploadFile(@Body RequestBody body);

String name = etFileName.getText().toString().trim();
name = TextUtils.isEmpty(name) ? "1.png" : name;
String path = Environment.getExternalStorageDirectory() + File.separator + name;
File file = new File(path);
RequestBody fileRQ = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);

RequestBody body=new MultipartBody.Builder()
        .addFormDataPart("userName","lange")
        .addFormDataPart("token","dxjdkdjkj9203kdckje0")
        .addFormDataPart("header",file.getName(),fileRQ)
        .build();
Call<ResponseBody> uploadCall = downloadService.uploadFile(body);
uploadCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        Log.i("upload", response.isSuccessful() + "");
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
    }
});

Retrofit2 之注解详解

1、REQUEST METHOD (请求方法)

需要先定义一个接口,然后将请求方法注解在方法上,每个方法都需要一个请求方法注解和一个 url;有 5 种内置的请求方法注解

@GET

一个简单的 Get 请求

1
@GET("users/list")

也可以指定请求参数在 url 中

1
@GET("users/list?sort=desc")

@POST

@PUT

@DELETE

@PATCH

@Options

@HTTP

自定义一个 Http Method

2、URL MANIPULATION (请求 Path)

可以通过 {xxx} 来动态替换掉里面的内容,通过 @Path
@Path 注解。

@Path

1
2
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

也可以添加 Query parameters:

1
2
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

复杂的 Query parameter combinations 通过 Map:

1
2
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

3、Query Params (请求参数)

@Query

1
2
@GET("test/sayHello")
Call<String> sayHello(@Query("username") String username, @Query("age") String age);

@QueryMap

这两个和@Filed、@FiledMap 功能是一致的,区别在于参数形式体现在 URL 上。

4、REQUEST BODY (请求体)

通过 @Body 注解,可以指定 Object 为 HTTP 的请求体

@Body

1
2
@POST("users/new")
Call<User> createUser(@Body User user);

这个对象会被转换,如果没有添加转换器,默认 RequestBody

或者使用 HashMap

1
2
@POST(ROOM_GREET_TXT_UPDATE)
    suspend fun updateRoomGreetText(@Path("roomId") roomId: String, @Body params: Map<String, String>): RoomGreetTextBean.RoomGreetTxt

5、FORM ENCODED AND MULTIPART

接口里的方法可以声明发送表单编码 (form-encoded) 和 multipartdata。

@FormUrlEncoded @Field @FieldMap

表单编码 (form-encoded) 通过 @FormUrlEncoded 注解在方法上,每对 key-value 通过 @Field 指定 name 和要提供的 value

1
2
3
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
  • @Field @FieldMap
    这两个需要和@FormUrlEncoded 配合使用,参数形式体现在请求体上

@Multipart @Part 文件上传(表单)

Multipart parts 用 @Multipart 注解在方法上,每一个 Part 用 @Part 注解声明。

1
2
3
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
  • @Multipart @Part
    这两个用于上传文件,与 @MultiPart 注解结合使用

6、HEADER MANIPULATION (请求头)

@Headers

用于在方法添加请求头;指定静态的 Header,通过 @Headers 注解在接口的方法上

1
2
3
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
1
2
3
4
5
6
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

Note: 这些 Header 不会相互覆盖,所有的 Header(即使具有相同的 name)会被添加到 request 中

用于在方法参数里动态添加请求头;动态添加 Header 通过 @Header 注解,如果值为 null,这个 Header 会被忽略掉,否则调用 toString()

1
2
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

如果需要添加到每一个 request 中去,用 OkHttp interceptor.

7、大文件下载

@Url 动态 Url 正好非常适合我们的场景,而使用@Streaming 注解可以让我们下载非常大的文件时,避免 Retrofit 将整个文件读进内存,否则可能造成 OOM 现象。

@Url

使用动态的请求的网址,会复写之前的 baseUrl,值得注意的是@Url 需要在所有参数之前

@Streaming

如果正在下载一个大文件,Retrofit2 将尝试将整个文件移动到内存中。为了避免这种,我们必须向请求声明中添加一个特殊的注解 @Streaming

Retrofit 封装

Retrofit2 BaseResponse 封装处理

自定义 GsonResponseBodyConverter

  1. 自定义一个 GsonResponseBodyConverter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;
    private Type mType;

    GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter, Type type) {
        this.gson = gson;
        this.adapter = adapter;
        this.mType = type;
    }

    private BaseResponse<T> parseJson(JSONObject rootJsonObj) {
        int err = rootJsonObj.optInt("err");
        String errInfo = rootJsonObj.optString("err_msg");
        int dataLength = rootJsonObj.optInt("data_length");
        String dataInfo = rootJsonObj.optString("data_info");
        Builder<T> builder = new Builder();
        builder.setErr(err);
        builder.setErrInfo(errInfo);
        builder.setDataLength(dataLength);
        builder.setDataInfo(dataInfo);
        JSONObject responseJsonObj = rootJsonObj.optJSONObject("data");
        if (null != responseJsonObj) {
            T data = this.gson.fromJson(responseJsonObj.toString(), this.mType);
            builder.setData(data);
        } else {
            JSONArray responseJsonArray = rootJsonObj.optJSONArray("data");
            if (null != responseJsonArray) {
                T data = this.gson.fromJson(responseJsonArray.toString(), this.mType);
                builder.setData(data);
            }
        }

        return builder.build();
    }

    public T convert(ResponseBody value) throws IOException {
        try {
            String responseJson = value.string();
            JSONObject jsonObject = new JSONObject(responseJson);
            BaseResponse<T> response = this.parseJson(jsonObject);
            if (response == null) {
                throw new ServerException("response is null", -1);
            }

            boolean successful = response.isSuccessful();
            if (!successful) {
                throw new ServerException(response.errInfo, response.err);
            }

            Object var6 = response.getData();
            return var6;
        } catch (JSONException var10) {
            var10.printStackTrace();
        } finally {
            value.close();
        }
        return null;
    }
}
  1. 自定义 MyGsonConverterFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * 支持支持BaseResponse<T>的Converter.Factory
 *
 * A {@linkplain Converter.Factory converter} which uses Gson for JSON.
 * <p>
 * Because Gson is so flexible in the types it supports, this converter assumes that it can handle
 * all types. If you are mixing JSON serialization with something else (such as protocol buffers),
 * you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance}
 * last to allow the other converters a chance to see their types.
 */
public final class MyGsonConverterFactory extends Converter.Factory {
    /**
     * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static GsonConverterFactory create() {
        return create(new Gson());
    }

    /**
     * Create an instance using {@code gson} for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
    public static GsonConverterFactory create(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        return new GsonConverterFactory(gson);
    }

    private final Gson gson;

    private GsonConverterFactory(Gson gson) {
        this.gson = gson;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter, type);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}
  1. 使用
1
.addConverterFactory(MyGsonConverterFactory.create())

注解方式处理

Retrofi2t+RxJava2 异常处理

通过 onErrorResumeNext 操作符

Retrofit 处理后端自己返回的错误码,正常流程那么会走入到 onNext 中去,如何让其走到 onError 中去。

整个流程都是在 RxJava 的工作流中,所以 RxJava 对于 Retrofit 的适配器 (也就是工厂) 并不用做什么改变,因为只要在流程中抛出异常,便会执行 onError 方法,这是永恒不变的。我们最终的目的,就是解析服务器返回的 json,判断请求是否成功,如果不成功,那么抛出异常,因为在 RxJava 的工作流中,只要抛出异常,便会执行 subscribe 的 onError 事件。所以,我们必须要自定义一个 gson 的工厂。这个工厂能在内部先解析服务器返回的数据,根据数据的成功与否来判断是否需要抛出异常或者进行 object 转换。

1
2
3
4
5
6
.onErrorResumeNext(new Func1<Throwable, Observable<? extends InitUserInfo>>() {
    @Override
    public Observable<? extends InitUserInfo> call(Throwable throwable) {
        return Observable.error(ExceptionEngine.handleException(throwable));
    }
})

通过 CallAdapter.Factory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
 * inspired by
 * @see <a href="https://gist.github.com/julianfalcionelli/a585d2cb2fbaca2e123ae7df3e36fa39"></a>
 */
class RxErrorHandleCallAdapterFactory(private val context: Context, private val originFactory: CallAdapter.Factory) : CallAdapter.Factory() {

    companion object {

        @JvmStatic
        fun create(context: Context) : RxErrorHandleCallAdapterFactory{
            return RxErrorHandleCallAdapterFactory(context, RxJava2CallAdapterFactory.create())
        }
    }

    override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
        val callAdapter = originFactory.get(returnType, annotations, retrofit)
        callAdapter?.let {
            return RxErrorHandleCallAdapter(context, callAdapter)
        }
        return null
    }



    class RxErrorHandleCallAdapter(val context: Context, originAdapter: CallAdapter<*, *>) : CallAdapter<Any, Any> {

        private val originAdapter : CallAdapter<Any, *> = originAdapter as CallAdapter<Any, *>

        override fun adapt(call: Call<Any>): Any {
            val adaptedCall = originAdapter.adapt(call)

            if (adaptedCall is Observable<*>) {
                return adaptedCall.onErrorResumeNext {t: Throwable ->  Observable.error(asRetrofitException(t))}
            }

            if (adaptedCall is Flowable<*>) {
                return adaptedCall.onErrorResumeNext{t: Throwable -> Flowable.error(asRetrofitException(t)) }
            }

            if (adaptedCall is Single<*>) {
                return (adaptedCall ).onErrorResumeNext { throwable -> Single.error(asRetrofitException(throwable)) }
            }

            if (adaptedCall is Maybe<*>) {
                return adaptedCall.onErrorResumeNext { t: Throwable -> Maybe.error(asRetrofitException(t)) }
            }

            if (adaptedCall is Completable) {
                return adaptedCall.onErrorResumeNext { throwable -> Completable.error(asRetrofitException(throwable)) }
            }

            //todo (s1rius) return default or throw exception
            return adaptedCall
        }

        override fun responseType(): Type {
            return originAdapter.responseType()
        }

        private fun asRetrofitException(throwable: Throwable): CustomHttpException {
            return when (throwable) {
                // We had non-200 http error
                is HttpException -> {
                    val response = throwable.response()
                    ServerException.httpError(context, response)
                }
                // A network error happened
                is IOException -> ServerException.networkError(context, throwable)
                is CustomHttpException -> throwable
                else -> // We don't know what happened. We need to simply convert to an unknown error
                    ServerException.unexpectedError(context, throwable)
            }
        }
    }
}

class ServerException(msg: String, code: Int) : CustomHttpException(msg, code) {
    companion object {
        private const val UNUSE_HTTP_ERROR_CODE = Int.MIN_VALUE

        fun httpError(context: Context, response: Response<*>?): ServerException {
            try {
                val errorJson = JSONObject(response?.errorBody()?.string())
                val errCode = errorJson.optInt("err")
                val errorMsg = errorJson.optString("err_msg")
                if (errCode != 0 && !TextUtils.isEmpty(errorMsg)) {
                    return ServerException(errorMsg, errCode)
                }
            } catch (e: JSONException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            } catch (e: Exception) {
                e.printStackTrace()
            }

            return ServerException(context.getString(R.string.server_error_unknow), response?.code()?: UNUSE_HTTP_ERROR_CODE)
        }

        fun networkError(context: Context, exception: IOException): ServerException {
            return if (exception is SocketTimeoutException) {
                //TODO 这里理论上应该用 timeout 的,但是对用户而言可能没啥意义,后期完善
                ServerException(context.getString(R.string.server_error_unknow), UNUSE_HTTP_ERROR_CODE)
            } else ServerException(context.getString(R.string.notice_network_unavailable), UNUSE_HTTP_ERROR_CODE)
        }

        fun unexpectedError(context: Context, exception: Throwable): ServerException {
            var msg = exception.message
            val defaultMsg = context.getString(R.string.server_error_unknow)
            if (msg.isNullOrEmpty()) {
                msg = defaultMsg
            }
            return ServerException(msg ?: "" , UNUSE_HTTP_ERROR_CODE)
        }
    }
}

Retrofit2 协程

Retrofit2 协程的支持

需要 Retrofit2.6.0+
可以直接在主线程启动协程去请求网络,不需要在子线程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * 期望使用接口返回的数据时使用
 */
suspend fun <T> withHttpContext(block: suspend () -> T): T? {
    if (!isNetworkConnected()) {
        return null
    }
    var result: T? = null
    try {
        result = block.invoke()
        LogUtils.logi("hacket", "withHttpContext", "block invoke.")
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return result
}

lifecycleScope.launchWhenCreated {
    withHttpContext {
        val result = Api.getRetrofit().create(Api::class.java).getChapters()
        val data = result.data
        LogUtils.logi("hacket", "withHttpContext", "end data = ${data.log()}")
    }

}

Retrofit 对协程支持原理

这是一个使用 suspend 的例子:

1
2
3
4
interface Api {
    @GET("/wxarticle/chapters/json")
    suspend fun getChapters(): ListBeanResponse<Chapter>
}

使用:

1
Api.getRetrofit().create(Api::class.java).getChapters()

那么从 create 方法开始,看看 Retrofit 的执行流程:

1
2
3
4
5
6
7
8
9
Retrofit. <T> T create(final Class<T> service)  
Proxy.newProxyInstance.loadServiceMethod(method)  
ServiceMethod.parseAnnotations(Retrofit, Method) 
RequestFactory.parseAnnotations() 
RequestFactory.Builder.build()  // 1
HttpServiceMethod.parseAnnotations()  
ServiceMethod.invoke()  
HttpServiceMethod.adapt()  // 2
SuspendForBody/SuspendForResponse/CallAdapted/.adapt()

在 1 处,

suspend 方法

1
2
3
4
5
6
7
8
// 非 suspend 方法
fun test(){}

// suspend 方法
suspend fun testSuspend(){}

// 带参数的 suspend 方法
suspend fun testSuspend(text : String){}

反编译字节码看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Compiled from "SuspendTest.kt"
public final class SuspendTestKt {
  public static final void test();
    Code:
       0: return

  public static final java.lang.Object testSuspend(kotlin.coroutines.Continuation<? super kotlin.Unit>);
    Code:
       0: getstatic     # 17                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
       3: areturn

  public static final java.lang.Object testSuspend(java.lang.String, kotlin.coroutines.Continuation<? super kotlin.Unit>);
    Code:
       0: getstatic     # 17                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
       3: areturn
}

从反编译的结果看可知,suspend 方法比非 suspend 方法多一个参数, 并且这个参数在参数列表的最后一个,参数类型是 Continuation

如何判断方法是 suspend 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// RequestFactory
RequestFactory build() {
    // ...
    int parameterCount = parameterAnnotationsArray.length; // 参数注解个数
    parameterHandlers = new ParameterHandler<?>[parameterCount];
    for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
    parameterHandlers[p] =
        // 参数4:是否是最后一个参数,suspend函数最后一个参数为Continuation
        parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
    }
    // ...
}
private @Nullable ParameterHandler<?> parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
    ParameterHandler<?> result = null;
    if (annotations != null) {
        for (Annotation annotation : annotations) {
          ParameterHandler<?> annotationAction =
              parseParameterAnnotation(p, parameterType, annotations, annotation);
        
          if (annotationAction == null) {
            continue;
          }
        
          if (result != null) {
            throw parameterError(method, p,
                "Multiple Retrofit annotations found, only one allowed.");
          }
          result = annotationAction;
        }
    }
    if (result == null) {
        if (allowContinuation) { // 如果是最后一个参数
          try {
            if (Utils.getRawType(parameterType) == Continuation.class) { // 看是否是Continuation类型,是的话isKotlinSuspendFunction赋值为true
              isKotlinSuspendFunction = true;
              return null;
            }
          } catch (NoClassDefFoundError ignored) {
          }
        }
        throw parameterError(method, p, "No Retrofit annotation found.");
    }
    return result;
}

通过解析接口方法,Retrofit 创建了一个 RequestFactory 用于之后创建 OKhttp 的 Request, 而这个 RequestFactory 类多了一个 isKotlinSuspendFunction 的属性, 用于标记该方法是否为 suspend 方法,

如何判断方法的返回值

除了需要判断方法是否为 suspend 方法之外,还需要判断方法的返回类型是什么,要不然 Retrofit 就无法创建相应的 CallAdapter

suspend 方法的返回值其实就是 Continuation 的泛型的具体类型,所以只要获取 Continuation 的泛型的具体类型,我们就可以知道 suspend 方法 的返回类型,Retrofit 也是这么做的:

在 2 处,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;

    Annotation[] annotations = method.getAnnotations();
    Type adapterType;
    if (isKotlinSuspendFunction) { // 判断该方法是否为kotlin的suspend方法
        // 获取参数列表的泛型
        Type[] parameterTypes = method.getGenericParameterTypes();
        // 获取最后一个参数的第一个泛型类型,其实 Continuation 就一个泛型,这里 responseType 就是 suspend 方法的返回类型
        Type responseType = Utils.getParameterLowerBound(0, (ParameterizedType)parameterTypes[parameterTypes.length - 1]);
        // 判断返回类型是否为 Response, 这里这么判断是为了方便开发者
        if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
            // Unwrap the actual body type from Response<T>. 
            // 类似这种:suspend getUserResponse : Response<User>
            responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
            continuationWantsResponse = true;
        } else {
            // TODO figure out if type is nullable or not
            // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
            // Find the entry for method
            // Determine if return type is nullable or not
        }
        // 为 suspend 方法创建 CallAdapter 的类型,我们知道 Retrofit 默认情况只支持 Call 类型,比如下面的例子:
        // Call<User> getUser();
        // Retrofit 非常聪明地将 suspend 方法的 CallAdapter 类型转化成了 Call 类型,这里 ParameterizedTypeImpl 就是一个自定的 Call 类型。
        adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType); // 构建一个Call<responseType>的参数化类型
        annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
        // 获取方法返回值的参数化类型,为ParameterizedTypeImpl,如Call<User>
        adapterType = method.getGenericReturnType();
    }
    
    // 通过 adapterType 创建该方法的 CallAdapter, 这里 supsend 方法的adapterType为Call类型
    CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations);
    Type responseType = callAdapter.responseType();
    
    // ...
}

获取方法参数的最后一个参数,如果是 Continuation 类型,获取其泛型类型,构造一个 Call<responseType> 的参数化类型来获取对应的的 CallAdapter

如何处理 suspend 方法的返回值

Retrofit 支持两种类型返回值的 suspend 方法,一种是返回值 类型为 Response 类型,另一种是具体数据类型

  • Response 类型返回值的方法 Retrofit 返回了一个 SuspendForResponse 的 HttpServiceMethod
  • 具体数据类型返回值的方法 Retrofit 返回了一个 SuspendForBody 的 HttpServiceMethod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations() {
    
    // ... 前面就是获取responseType和callAdapter,上面讲解了
    
    Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
        return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
        // noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        // 如果 suspend 的返回类型为 Response 返回 SuspendForResponse
        return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
        // noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        // 如果 suspend 的返回类型为具体数据类型返回 SuspendForBody
        return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable);
    }
}

处理返回值为 Response 的 suspend 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;

    SuspendForResponse(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter, CallAdapter<ResponseT, Call<ResponseT>> callAdapter) {
        super(requestFactory, callFactory, responseConverter);
        this.callAdapter = callAdapter;
    }

    @Override protected Object adapt(Call<ResponseT> call, Object[] args) {
        // 参数里的 call 为 OkHttpCall
        call = callAdapter.adapt(call);
        
        //noinspection unchecked Checked by reflection inside RequestFactory.
        // 获取最后一个参数 Continuation, 用于接收返回结果
        Continuation<Response<ResponseT>> continuation = (Continuation<Response<ResponseT>>) args[args.length - 1];
        // 等待 Response 的返回
        return KotlinExtensions.awaitResponse(call, continuation);
    }
}

现在看下 awaitReponse(),这个是 kotlin 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        continuation.resume(response)
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}

Call#enqueue 封装成 suspend 方法,调用的是异步的 enqueue,也就是说在协程中用 Retrofit+suspend 是不需要切换线程的。

处理返回值为具体数据类型的 suspend 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
    private final boolean isNullable;

    SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter, CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) {
        super(requestFactory, callFactory, responseConverter);
        this.callAdapter = callAdapter;
        this.isNullable = isNullable;
    }

    @Override protected Object adapt(Call<ResponseT> call, Object[] args) {
        // 参数里的 call 为 OkHttpCall
        call = callAdapter.adapt(call);
            
        //noinspection unchecked Checked by reflection inside RequestFactory.
        // 获取最后一个参数 Continuation, 用于接收返回结果
        Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
        
        // Calls to OkHttp Call.enqueue() like those inside await and awaitNullable can sometimes
        // invoke the supplied callback with an exception before the invoking stack frame can return.
        // Coroutines will intercept the subsequent invocation of the Continuation and throw the
        // exception synchronously. A Java Proxy cannot throw checked exceptions without them being
        // declared on the interface method. To avoid the synchronous checked exception being wrapped
        // in an UndeclaredThrowableException, it is intercepted and supplied to a helper which will
        // force suspension to occur so that it can be instead delivered to the continuation to
        // bypass this restriction.
        try {
            // 这里不用纠结 isNullable, Retrofit 就没有实现如何判断方法返回结果是否可空,所以我们直接看 await 方法就行。
            return isNullable
                ? KotlinExtensions.awaitNullable(call, continuation)
                : KotlinExtensions.await(call, continuation);
        } catch (Exception e) {
            return KotlinExtensions.yieldAndThrow(e, continuation);
        }
    }
}

接着看 await 方法,这也是个 kotlin 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
suspend fun <T : Any> Call<T>.await(): T {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        if (response.isSuccessful) { // 判断请求是否成功
          val body = response.body()
          if (body == null) { // 如果具体数据类型为 null 也表示失败,直接返回异常
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException("Response from " +
                method.declaringClass.name + '.' + method.name +
                " was null but response body type was declared as non-null")
            continuation.resumeWithException(e) // 所以执行 suspend 方法的时候我们需要 try catch 去捕获异常。
          } else {
            continuation.resume(body) // 返回具体数据类型
          }
        } else {
          // 请求失败,返回异常。所以执行 suspend 方法的时候我们需要 try catch 去捕获异常。
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        // 请求失败,返回异常。所以执行 suspend 方法的时候我们需要 try catch 去捕获异常。
        continuation.resumeWithException(t)
      }
    })
  }
}

Retrofit 协程小结

  • Retrofit 对协程的支持其实就增加了不到 200 行代码,尤其是创建 suspend 方法的 CallAdapter 简直惊艳到我了,创建了一个自定义的 Call 类 型就完美地复用之前的代码。
  • Retrofit 使用协程时,不需要 withContext 来切换线程了,因为用的是 OkHttp 的 enqueue 异步方法,Retrofit 只是包装成一个 suspend 方法

异常

java.lang.IllegalArgumentException: Unable to create call adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
W: java.lang.IllegalArgumentException: Unable to create call adapter for me.hacket.assistant.samples.kotlin.协程.retrofit.ListBeanResponse<me.hacket.assistant.samples.kotlin.协程.retrofit.Chapter>
W:     for method Api.getChapters
W:     at retrofit2.Utils.methodError(Utils.java:52)
W:     at retrofit2.HttpServiceMethod.createCallAdapter(HttpServiceMethod.java:105)
W:     at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:66)
W:     at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:37)
W:     at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
W:     at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
W:     at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
W:     at $Proxy2.getChapters(Unknown Source)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1$1$1.invokeSuspend(RetrofitCoroutine.kt:22)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1$1$1.invoke(Unknown Source:10)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.BaseSuspendApiKt.withHttpContext(BaseSuspendApi.kt:43)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1$1.invokeSuspend(RetrofitCoroutine.kt:20)
W:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
W:     at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
W:     at androidx.lifecycle.DispatchQueue.drainQueue(DispatchQueue.kt:76)
W:     at androidx.lifecycle.DispatchQueue.enqueue(DispatchQueue.kt:106)
W:     at androidx.lifecycle.DispatchQueue.runOrEnqueue(DispatchQueue.kt:96)
W:     at androidx.lifecycle.PausingDispatcher.dispatch(PausingDispatcher.kt:184)
W:     at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:423)
W:     at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:162)
W:     at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
W:     at androidx.lifecycle.PausingDispatcherKt$whenStateAtLeast$2.invokeSuspend(PausingDispatcher.kt:163)
W:     at androidx.lifecycle.PausingDispatcherKt$whenStateAtLeast$2.invoke(Unknown Source:10)
W:     at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:156)
W:     at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
W:     at androidx.lifecycle.PausingDispatcherKt.whenStateAtLeast(PausingDispatcher.kt:157)
W:     at androidx.lifecycle.PausingDispatcherKt.whenCreated(PausingDispatcher.kt:43)
W:     at androidx.lifecycle.LifecycleCoroutineScope$launchWhenCreated$1.invokeSuspend(Lifecycle.kt:74)
W:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
W:     at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:457)
W:     at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
W:     at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
W:     at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:154)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
W:     at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
W:     at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
W:     at androidx.lifecycle.LifecycleCoroutineScope.launchWhenCreated(Lifecycle.kt:73)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1.onClick(RetrofitCoroutine.kt:18)
W:     at android.view.View.performClick(View.java:7140)
W:     at android.view.View.performClickInternal(View.java:7117)
W:     at android.view.View.access$3500(View.java:801)
W:     at android.view.View$PerformClick.run(View.java:27351)
W:     at android.os.Handler.handleCallback(Handler.java:883)
W:     at android.os.Handler.dispatchMessage(Handler.java:100)
W:     at android.os.Looper.loop(Looper.java:214)
W:     at android.app.ActivityThread.main(ActivityThread.java:7356)
W:     at java.lang.reflect.Method.invoke(Native Method)
W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
W: Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for me.hacket.assistant.samples.kotlin.协程.retrofit.ListBeanResponse<me.hacket.assistant.samples.kotlin.协程.retrofit.Chapter>.
W:   Tried:
W:    * retrofit2.CompletableFutureCallAdapterFactory
W:    * retrofit2.DefaultCallAdapterFactory
W:     at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
W:     at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
W:     at retrofit2.HttpServiceMethod.createCallAdapter(HttpServiceMethod.java:103)
W: 	... 50 more
W: timeout expired while flushing socket, closing
E: No valid NAT64 prefix (100, <unspecified>/0)
W: java.lang.IllegalArgumentException: Unable to create call adapter for me.hacket.assistant.samples.kotlin.协程.retrofit.ListBeanResponse<me.hacket.assistant.samples.kotlin.协程.retrofit.Chapter>
W:     for method Api.getChapters
W:     at retrofit2.Utils.methodError(Utils.java:52)
W:     at retrofit2.HttpServiceMethod.createCallAdapter(HttpServiceMethod.java:105)
W:     at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:66)
W:     at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:37)
W:     at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
W:     at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
W:     at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
W:     at $Proxy2.getChapters(Unknown Source)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1$1$1.invokeSuspend(RetrofitCoroutine.kt:22)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1$1$1.invoke(Unknown Source:10)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.BaseSuspendApiKt.withHttpContext(BaseSuspendApi.kt:43)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1$1.invokeSuspend(RetrofitCoroutine.kt:20)
W:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
W:     at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
W:     at androidx.lifecycle.DispatchQueue.drainQueue(DispatchQueue.kt:76)
W:     at androidx.lifecycle.DispatchQueue.enqueue(DispatchQueue.kt:106)
W:     at androidx.lifecycle.DispatchQueue.runOrEnqueue(DispatchQueue.kt:96)
W:     at androidx.lifecycle.PausingDispatcher.dispatch(PausingDispatcher.kt:184)
W:     at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:423)
W:     at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:162)
W:     at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
W:     at androidx.lifecycle.PausingDispatcherKt$whenStateAtLeast$2.invokeSuspend(PausingDispatcher.kt:163)
W:     at androidx.lifecycle.PausingDispatcherKt$whenStateAtLeast$2.invoke(Unknown Source:10)
W:     at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:156)
W:     at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
W:     at androidx.lifecycle.PausingDispatcherKt.whenStateAtLeast(PausingDispatcher.kt:157)
W:     at androidx.lifecycle.PausingDispatcherKt.whenCreated(PausingDispatcher.kt:43)
W:     at androidx.lifecycle.LifecycleCoroutineScope$launchWhenCreated$1.invokeSuspend(Lifecycle.kt:74)
W:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
W:     at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:457)
W:     at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
W:     at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
W:     at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:154)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
W:     at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
W:     at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
W:     at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
W:     at androidx.lifecycle.LifecycleCoroutineScope.launchWhenCreated(Lifecycle.kt:73)
W:     at me.hacket.assistant.samples.kotlin.协程.retrofit.RetrofitCoroutine$onCreate$1.onClick(RetrofitCoroutine.kt:18)
W:     at android.view.View.performClick(View.java:7140)
W:     at android.view.View.performClickInternal(View.java:7117)
W:     at android.view.View.access$3500(View.java:801)
W:     at android.view.View$PerformClick.run(View.java:27351)
W:     at android.os.Handler.handleCallback(Handler.java:883)
W:     at android.os.Handler.dispatchMessage(Handler.java:100)
W:     at android.os.Looper.loop(Looper.java:214)
W:     at android.app.ActivityThread.main(ActivityThread.java:7356)
W:     at java.lang.reflect.Method.invoke(Native Method)
W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
W: Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for me.hacket.assistant.samples.kotlin.协程.retrofit.ListBeanResponse<me.hacket.assistant.samples.kotlin.协程.retrofit.Chapter>.
W:   Tried:
W:    * retrofit2.CompletableFutureCallAdapterFactory
W:    * retrofit2.DefaultCallAdapterFactory
W:     at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
W:     at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
W:     at retrofit2.HttpServiceMethod.createCallAdapter(HttpServiceMethod.java:103)
W: 	... 50 more

原因:

1
2
@GET("/wxarticle/chapters/json")
fun getChapters(): ListBeanResponse<Chapter>

没有带上 suspend 关键字

1
2
@GET("/wxarticle/chapters/json")
suspend fun getChapters(): ListBeanResponse<Chapter>

Ref

Retrofit 遇到的问题?

接口实例不支持 T

用 retrofit 执行网络请求的 service 接口,必须是具体的类型,而不能是 T 泛型

1
2
@GET("api/customer/")
Call<DataModelResponse<T>> getList(@Query("take") int page, @Query("skip") int step);

官方 issue 说明:https://github.com/square/retrofit/issues/2012

java.lang.IllegalArgumentException: A @Path parameter must not come after a @Query.

错误代码:

1
2
3
4
5
6
@GET(LEVEL_USER_INFO)
suspend fun levelUserInfo(
        @Query("with_level_progress") with_level_progress: Int = 1,
        @Path("uid") uid: Int

): UserLevelUserInfoModel

解决:

1
2
3
4
5
6
@GET(LEVEL_USER_INFO)
suspend fun levelUserInfo(
        @Path("uid") uid: Int,
        @Query("with_level_progress") with_level_progress: Int = 1

): UserLevelUserInfoModel

Retrofit Call

Call 实例可以直接在同步或者异步,但只能用一次,调用 clone() 可以再次用

/ 写在哪?

baseUrl 结尾以 / 结尾,而接口方法的 path 就不以 / 开头

1
2
3
Retrofit retrofit = new Retrofit.Builder()
    //设置baseUrl,注意baseUrl 应该以/ 结尾。
    .baseUrl("http://news-at.zhihu.com/api/4/")

Retrofit 有多个 BaseUrl 怎么处理?

  1. 通过 @Headers 将 baseUrl 添加进去
1
2
3
4
5
6
7
@Headers("urlname:test1") 
@GET("地址1") 
Observable<BaseResponse<List<Bean>>> getHData(@QueryMap Map<String, String> map);
 
@Headers("urlname:test2") 
@GET("地址2") 
Observable<BaseResponse<List<Bean>>> getJData(@QueryMap Map<String, String> map);
  1. 添加拦截器,拦截 baseUrl 进行替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class BaseUrlInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        //获取request
        Request request = chain.request();
        //从request中获取原有的HttpUrl实例oldHttpUrl
        HttpUrl oldHttpUrl = request.url();
        //获取request的创建者builder
        Request.Builder builder = request.newBuilder();
        //从request中获取headers,通过给定的键url_name
        List<String> headerValues = request.headers("urlname");
        if (headerValues != null && headerValues.size() > 0) {
            //如果有这个header,先将配置的header删除,因此header仅用作app和okhttp之间使用
            builder.removeHeader("urlname");
            //匹配获得新的BaseUrl
            String headerValue = headerValues.get(0);
            HttpUrl newBaseUrl = null;
            if ("test1".equals(headerValue)) {
                newBaseUrl = HttpUrl.parse(BASE_URL1);
            }else if ("test2".equals(headerValue)) {
                newBaseUrl = HttpUrl.parse(BASE_URL2);
            }else{
                newBaseUrl = oldHttpUrl;
            }
            //重建新的HttpUrl,修改需要修改的url部分
            HttpUrl newFullUrl = oldHttpUrl
                    .newBuilder()
                    .scheme("https")//更换网络协议
                    .host(newBaseUrl.host())//更换主机名
                    .port(newBaseUrl.port())//更换端口
                    .removePathSegment(0)//移除第一个参数
                    .build();
            //重建这个request,通过builder.url(newFullUrl).build();
            // 然后返回一个response至此结束修改
            Log.e("Url", "intercept: "+newFullUrl.toString());
            return chain.proceed(builder.url(newFullUrl).build());
        }
        return chain.proceed(request);
    }
}
  1. 配置 OkHttpClient
1
2
3
4
5
6
7
8
9
10
// 创建一个OkHttpClient并设置超时时间
OkHttpClient client = new OkHttpClient.Builder()
    .readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
    .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
    .addInterceptor(mRewriteCacheControlInterceptor)//没网的情况下
    .addNetworkInterceptor(mRewriteCacheControlInterceptor)//有网的情况下
    .addInterceptor(new BaseUrlInterceptor())
    .addInterceptor(logInterceptor)
    .cache(cache)
    .build();

Retrofit2 handling HTTP 204 (No Content response ) situation with RxJava’s Observable concepts

  • 背景:使用 Pgyer Http v2 上传 APK 接口,上传成功后,返回 204,not content
  • 问题:正常写,由于 body 未返回,会当成失败来处理
1
2
@GET("/user/{topic}")
fun getAllUserFor(@Path(value="topic",encoded=true) topic:String) :Observable<List<User>>
  • 解决:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@GET("/user/{topic}")
fun getAllUserFor(@Path(value="topic",encoded=true) topic:String) :Observable<Result<List<User>>>

// 调用
api.getAllUserFor(topic)
	.observeOn(AndroidSchedulers.mainThread())
	.subscribeOn(Schedulers.io())
	.subscribe{ result ->
	   if(result.isError)
		   //Network Error
	   result.response()?.also {
		   if(result.isSuccessful)
			   //Success
		   else{
			   //api error
		   }
	}
}
  • Ref:

android - Retrofit2 handling HTTP 204 (No Content response ) situation with RxJava’s Observable concepts - Stack Overflow

Retrofit2 原理

Retrofit2 原理

Retrofit2 注意细节

  • Retrofit 的接口类不能继承其他的接口
  • Retrofit.create() 返回的是一个代理类,用的是动态代理,为什么是个代理类?
    因为我要监听该接口所有的方法
  • 动态代理,遇到 Object 类,那么跳过代理直接执行方法;如果是 JDK8 的 default 方法,那么也是跳过代理直接执行方法
  • 为什么用到了运行时,而不是编译时?
    如果接口中方法过多,如果是编译时,那么在运行时会全部加载该接口所有方法,消耗内存;但现在是利用动态代理,在运行时,需要哪个方法就动态的加载,然后缓存该方法。
  • Retrofit 中对方法上注解的解析,需要对反射注解加强
  • Retrofit 使用动态代理,invoke 返回值,需要对动态代理加强

1、创建 Retrofit 对象

通过 Retrofit.Builder 对 Retrofit 做一些配置

1
2
3
4
Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(BaseUrl.GITHUB_USER_INFO)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();

用到了 Builder 模式、外观模式(Facade,门面模式)。

2、定义 API 并获取实例

定义 API 接口:

1
2
3
4
public interface ApiGithubUser {
    @GET("users/{user}")
    Call<GithubUser> getGithubUser(@NonNull @Path("user") String user);
}

获取实例:

1
ApiGithubUser apiGithubUser = retrofit.create(ApiGithubUser.class);

create(service) 方法怎么实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service); // 接口校验,不能继承其他接口
    if (validateEagerly) { // 是否早点load method并缓存起来,默认false
      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 = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        }); // 动态代理
}
  • 第 2 行,validateServiceInterface() 进行了接口的校验
    validateServiceInterface() 方法进行了 service 接口的校验,service 必须是一个接口,而且不能继承其他接口
1
2
3
4
5
6
7
8
static <T> void validateServiceInterface(Class<T> service) {
    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.");
    }
}
  • 第 3 行,急切的验证方法,通过在 Retrofit.Builder.validateEagerly(boolean) 设置,最后调用 loadServiceMethod(Method) 来将 Method 包装成 ServiceMethod 缓存起来,后面详细讲解
  • 第 6 行,用了动态代理来来返回了一个代理类,为什么需要返回是一个代理类呢?因为需要监听该接口中所有的方法调用
  • 第 13 行,如果该方法是 Object 对象中的,那么就默认调用,如(equals()、toString());如果是 default 方法(jdk8 引入),就调用 default 方法
  • 第 19 行,最后 3 行是重点
1
2
3
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

2.1 ServiceMethod

首先看 ServiceMethod。注释:把对接口方法的调用转为一次 HTTP 调用。

1
2
// Retrofit#create()
ServiceMethod serviceMethod = loadServiceMethod(method);

一个 ServiceMethod 对象对应于 API Interface 的一个方法,loadServiceMethod(Method method) 负责加载 ServiceMethod,并实现了缓存逻辑,同一个 API 的同一个方法只会创建一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Retrofit#loadServiceMethod()
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
}

里面有一个 serviceMethodCache 是一个 Map,以 Method 为 key,ServiceMethod 为 value 的 Map;然后包装成 ServiceMethod 返回,并将其缓存到 serviceMethodCache,并且做了线程同步。

ServiceMethod 的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ServiceMethod
ServiceMethod(Builder<T> builder) {
    this.callFactory = builder.retrofit.callFactory();
    this.callAdapter = builder.callAdapter;
    this.baseUrl = builder.retrofit.baseUrl();
    this.responseConverter = builder.responseConverter;
    this.httpMethod = builder.httpMethod;
    this.relativeUrl = builder.relativeUrl;
    this.headers = builder.headers;
    this.contentType = builder.contentType;
    this.hasBody = builder.hasBody;
    this.isFormEncoded = builder.isFormEncoded;
    this.isMultipart = builder.isMultipart;
    this.parameterHandlers = builder.parameterHandlers;
 }

成员变量很多,重点关注四个成员:callFactroycallAdapterresponseConverterparameterHandlers

2.1.1 callFactory

负责创建 HTTP 请求,HTTP 请求被抽象为了 okhttp3.Call 类,它表示一个已经准备好,可以随时执行的 HTTP 请求。callFactory 默认实现是一个 OKHttpClient。
callFactory 在 ServiceMethod 中的初始化:

1
2
// ServiceMethod#ServiceMethod(Builder)
this.callFactory = builder.retrofit.callFactory();

ServiceMethod 中的 callFactory 实际上是由 Retrofit 类提供的,

1
2
3
4
// Retrofit
public okhttp3.Call.Factory callFactory() {
    return callFactory;
}

如果没有指定,设置为一个默认的值,为 OkHttp3 中的 OkhttpClient

1
2
3
4
5
// Retrofit.Builder#build()
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
    callFactory = new OkHttpClient();
}
2.1.2 callAdapter

callAdapter 把 retrofit2.Call<T> 转换为 T,这个过程会发送一个 HTTP 请求,拿到服务器返回的数据 (通过 Okhttp3.Call 实现),把数据转换成(由 Converter<F,T> 实现)声明的 T 类型对象。
在 ServiceMethod 中初始化:

1
2
3
4
5
6
7
8
9
// ServiceMethod#ServiceMethod(Builder)
this.callAdapter = builder.callAdapter;
// ServiceMethod.Builder
static final class Builder<T> {
    CallAdapter<?> callAdapter;
    public ServiceMethod build() {
        callAdapter = createCallAdapter();
    }
}

并在 build() 方法中调用 createCallAdapter() 构建 CallAdapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ServiceMethod.Builder#createCallAdapter()
private CallAdapter<?> createCallAdapter() {
    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
    throw methodError(
        "Method return type must not include a type variable or wildcard: %s", returnType);
    }
    if (returnType == void.class) {
    throw methodError("Service methods cannot return void.");
    }
    Annotation[] annotations = method.getAnnotations();
    try {
    return retrofit.callAdapter(returnType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(e, "Unable to create call adapter for %s", returnType);
    }
}

callAdapter 的创建,最终还是由 Retrofit 类提供,:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Retrofit#callAdapter()
public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
}
// Retrofit#nextCallAdapter()
public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,Annotation[] annotations) {
    checkNotNull(returnType, "returnType == null");
    checkNotNull(annotations, "annotations == null");

    int start = adapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
        CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
        if (adapter != null) {
            return adapter;
    }
}

发现,就是通过循环遍历 adapterFactories 来得到 CallAdapter,而 adapterFactories 从哪里来,从 Retrofit.Builder 中的 addCallAdapterFactory() 方法设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Retrofit
private final List<CallAdapter.Factory> adapterFactories;

// Retrofit.Builder
public static final class Builder {
    List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
    // 添加一个CallAdapter.Factory
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
        adapterFactories.add(checkNotNull(factory, "factory == null"));
        return this;
    }
    public Retrofit build() {
        // 默认添加的CallAdapter.Factory
        List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
        adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    }
}
// Paltform#defaultCallAdapterFactory()
// 默认添加的CallAdapter.Factory
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
    if (callbackExecutor != null) {
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }
    return DefaultCallAdapterFactory.INSTANCE;
}

可以发现,Retrofit 默认有一个 CallAdapter.Factory 是 ExecutorCallAdapterFactory

2.1.3 responseConverter

responseConverter 是 Converter<ResponseBody,T> 类型,负责把服务器返回的数据(JOSN、XML、二进制或其他格式,由 ResponseBody 封装)转为 T 类型的对象。

responseConverter 在 ServiceMethod 中初始化:

1
2
// ServiceMethod#ServiceMethod(Builder)
this.responseConverter = builder.responseConverter;

ServiceMethod.Builder 并没有提供添加 responseConverter 方法,而是 createResponseConverter() 方法来创建:

1
2
3
4
5
6
7
8
9
10
11
12
// ServiceMethod.Builder#build()
responseConverter = createResponseConverter();
// ServiceMethod.Builder
responseConverter = createResponseConverter();
private Converter<ResponseBody, T> createResponseConverter() {
    Annotation[] annotations = method.getAnnotations();
    try {
    return retrofit.responseBodyConverter(responseType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(e, "Unable to create converter for %s", responseType);
    }
}

同样,responseConverter 也是由 Retrofit 类提供,通过 Retrofit.Builder#addConverterFactory() 方法添加一个 Converter.Factory,默认添加了一个 BuiltInConverters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Retrofit
private final List<Converter.Factory> converterFactories;
// Retrofit.Builder
public static final class Builder {
    private List<Converter.Factory> converterFactories = new ArrayList<>();
    
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }
    
    Builder(Platform platform) {
      this.platform = platform;
      converterFactories.add(new BuiltInConverters());
    }
    
    public Retrofit build() {
        List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
    }
}
2.1.4 parameterHandlers

parameterHandlers 负责解析 API 定义时每个方法的参数,并在构造 HTTP 请求时设置参数。

在 ServiceMethod 构造方法中初始化,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ServiceMethod
private final ParameterHandler<?>[] parameterHandlers;
ServiceMethod(Builder<T> builder) {
    this.parameterHandlers = builder.parameterHandlers;
}
// ServiceMethod.Builder
static final class Builder<T> {
    ParameterHandler<?>[] parameterHandlers;
    public ServiceMethod build() {
        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);
        }
    }
}

每个参数都会有一个 ParameterHandler,由 ServiceMethod#parseParameter() 方法负责创建,其主要内容是解析每个参数使用的注解类型(如@Path、@Query、@Field),对每种类型进行单独的处理。构造 HTTP 请求时,我们传递的参数都是字符串,那么 Retrofit 如何把我们传递的各种参数都转化为 String,由 Retrofit 类提供的 converter。

Converter.Factory 除了提供 responseBodyConverter,还提供 requestBodyConverter 和 stringConverter,API 方法中除了@Body 和@Part 类型的参数,都利用 stringConverter 进行转换,而@Body 和@Part 类型的参数则利用 requestBodyConverter 进行转换。
除了上面四种成员,ServiceMethod 还包含了 api 方法中的 url 解析、众多关于泛型和反射相关代码。

2.2 OkHttpCall

1
2
// Retrofit#create()
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);

OkHttpCall 实现了 retrofit2.Call,我们会使用它的 execute()enqueue(callback) 接口,来执行同步、异步请求。

2.2.1 execute() 同步请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public Response<T> execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {
      // ...

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          creationFailure = e;
          throw e;
        }
      }
    }

    if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
}
  • 通过 createRawCall() 创建 okhttp3.Call,包括其构造参数 okhttp3.Request
  • 执行网络请求 call.execute()
  • 解析网络请求返回的数据 parseResponse(okhttp3.Response)
2.2.1 enqueue() 异步请求

异步交给了 okhttp3.Call#enqueue(responseCallback) 实现,并在 responseCallback 调用 parseResponse() 解析响应数据,并转发给传入的 callback。

2.3 serviceMethod.callAdapter.adapt

1
2
// Retrofit#create()
return serviceMethod.callAdapter.adapt(okHttpCall);

3、CallAdapter

CallAdapter<T>#adapt(retrofit2.Call<R> call) 函数负责把 retrofit2.Call<R> 转为 T。
DefaultCallAdapterFactory 就是把 T 转为 retrofit2.Call<R>,也就是直接返回参数。
ExecutorCallAdapterFactory
RxJava2CallAdapterFactory

retrofit-adapters 模块

retrofit 模块内置了 DefaultCallAdapterFactoryExecutorCallAdapterFactory,它们都适用于 API 方法得到的类型是 retrofit2.Call 情形,DefaultCallAdapterFactory 生产的 adapter 啥也不做,直接把参数返回,ExecutorCallAdapterFactory 生产的 adapter 则会在异步调用时在指定的 Executor 上执行回调。

retrofit-adapters 的各个子模块则实现了更多的工厂:
GuavaCallAdapterFactory
Java8CallAdapterFactory
RxJavaCallAdapterFactory

RxJavaCallAdapterFactory

// 待续

retrofit-converters 模块

retrofit 模块内置了 BuiltInConverters,只能处理 ResponseBodyRequestBodyString 类型的转换。而 retrofit-converters 中的子模块则提供了 JSON、XML、ProtoBuf 等类型数据的转换功能,而且还有多种转换方式可以选择。

Reference

拆轮子系列:拆 Retrofit(Piasy)
Retrofit 分析 - 漂亮的解耦套路(stay)

本文由作者按照 CC BY 4.0 进行授权