Retrofit 使用

Retrofit 是一款由 Square 公司开发的网络库,但是它和 OkHttp 的定位不同,OkHttp 侧重的是底层通信的实现,而 Retrofit 侧重的是上层接口的封装。事实上,Retrofit 就是 Square 公司在 OkHttp 的基础上进一步开发出来的应用层网络通信库,使得我们可以用更加面向对象的思维进行网络操作。

Retrofit 的设计基于以下几个事实:

  • 同一款应用程序中所发起的网络请求绝大多数指向的是同一个服务器域名。(Retrofit 允许我们可以配置好一个根路径,然后在指定服务器接口地址时只需要使用相对路径即可,这样就不用每次都指定完整的 URL 地址)
  • 服务器提供的接口通常是可以根据功能来归类的。(Retrofit 允许我们对服务器接口进行分类,将功能同属一类的服务器接口定义到同一个接口文件当中,从而让代码结构变得更加合理)
  • 其实大多数人并不关心网络的具体通信细节,但是传统网络库的用法却需要编写太多网络相关的代码。(Retrofit 让我们完全不用关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值,然后通过注解的方式指定该方法对应哪个服务器接口,以及需要提供哪些参数。当我们在程序中调用该方法时,Retrofit 会自动向对应的服务器接口发起请求,并将响应的数据解析成返回值声明的类型。)

准备工作

添加权限:

1
<uses-permission android:name="android.permission.INTERNET" />

引入依赖:

1
2
3
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
implementation 'com.squareup.retrofit2:retrofit-converters:2.7.1'
implementation 'com.squareup.retrofit2:retrofit-adapters:2.7.1'

添加扩展功能,如:

1
2
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'

如果现有的扩展包不能满足需要,还可以自己扩展 converter,adapter 等。

基本使用

定义接口

Retrofit 不直接做网络请求,是按照接口去定制能做网络请求的 Call 网络工作对象。

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 这个接口并不是传统意义上的网络请求接口,这个接口不是用来获取数据的接口,而是用来生产对象的接口,这个接口相当* 于一个工厂,接口中每个函数的返回值不是网络数据,而是一个能进行网络请求的工作对象,要先调用函数获得工作对象,* 再用这个工作对象去请求网络数据。
*/
public interface INetApiService {
/**
* 注解 GET 声明 URL 路径
* @param name 注解声明请求参数
* @return 返回值是一个 Retrofit 的 Call 对象。
*/
@GET("/demo/user")
Call<UserBean> getData(@Query("name")String name);
}

获取相关对象

依次获得 Retrofit 对象、接口实例对象、网络工作对象

首先,需要新建一个 retrofit 对象。
然后,根据上一步的接口,实现一个 retrofit 加工过的接口对象。
最后,调用接口函数,得到一个可以执行网络访问的网络工作对象。

1
2
3
4
5
6
7
8
9
10
11
// 创建一个 Retrofit 对象,建造者模式。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL) // 请求 URL 是拼接而成。
.addConverterFactory(GsonConverterFactory.create())
.build();
// 利用动态代理获取到之前定义的接口,并调用该接口定义的 getData 方法得到 Call 对象。
// 用 retrofit 加工出对应的接口实例对象
INetApiService iNetApiService = retrofit.create(INetApiService.class);

// 调用接口函数,获得网络工作对象。
Call<UserBean> callWorker = iNetApiService.getData("xx");

访问网络请求

1
2
3
4
5
6
7
8
9
10
11
12
13
// 用获取的 callWork 对象执行网络请求
// 这里是异步请求,回调的 Callback 运行在 UI 线程。同步使用 execute,中断网络请求使用 cancel。
callWorker.enqueue(new Callback<UserBean>() {
@Override
public void onResponse(Call<UserBean> call, Response<UserBean> response) {
Log.e(TAG,response.toString());
}

@Override
public void onFailure(Call<UserBean> call, Throwable t) {

}
});

参数注解

Retrofit 的注解分为三大类

  • HTTP 请求方法注解:GET、POST、PUT、DELETE、HEAD、PATCH、OPTIONS 和 HTTP。其中前七种分别对应 HTTP 的请求方法,HTTP 则可以替换以上七种,也可以扩展请求方法。

  • 标记类注解:

    • FormUrlEncoded:标明这是一个表单请求。
    • Multipart:表示允许多个 @Part。
    • Streaming:代表响应的数据以流的形式返回,如果不使用它,则默认会把全部数据加载到内存,所以下载大文件时需要加上这个注解。
  • 参数类注解:Header、headers、Body、Path、Field、FieldMap、Part、PartMap、Query 和 QueryMap 等。

@Path

动态配置 URL 地址:

1
2
3
4
5
6
// @GET 注解中的 {path} 对应着 @Path 注解中的 “path”。
@GET("{path}/getIpInfo.php?ip=59,108,54,37")
Call<UserBean> getData(@Path("path") String path);

// 传入 service 来替换 @GET 注解中的 {path} 的值。
Call<UserBean> callWorker = iNetApiService.getData("service");
1
2
3
// 请求网络时,只需要传入想要查询的 ip 值就可以,会将参数拼接在 url 后面。
@GET("getIpInfo.php")
Call<UserBean> getData(@Query("ip") String ip);

@QueryMap

动态指定查询条件组:

1
2
3
4
5
6
7
8
9
// 当需要传入很多查询参数,可将参数集成在一个 Map 中统一传递。
@GET("getIpInfo.php")
Call<UserBean> getData(@QueryMap Map<String,String> options);

// 调用接口函数,获得网络工作对象。
HashMap<String,String> map = new HashMap<>();
map.put("ip","59.108.54.37");
map.put("","");
Call<UserBean> callWorker = iNetApiService.getData(map);

@Field

传输数据类型为键值对:

1
2
3
4
// @FormUrlEncoded 注解标明这是一个表单请求,然后使用 @Field 注解标示所对应的 String 类型数据的键。
@FormUrlEncoded
@POST("getIpInfo.php")
Call<UserBean> getData(@Field("ip") String first);

@Body

传输数据类型 JSON 字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 将 JSON 字符串作为请求体发送到服务器
// Retrofit 会将 Ip 对象转换为字符串
@POST("getIpInfo.php")
Call<UserBean> getData(@Body Ip json);


public class Ip {

private String ip;
private String name;
public Ip(String ip,String name){
this.ip = ip;
this.name = name;
}
}


Call<UserBean> callWorker = iNetApiService.getData(new Ip(msg,"xx"));

@Part

单个文件上传:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @Multipart 注解表示允许多个 @Part
* @param photo 准备上传的图片文件,使用了 MultipartBody.Part 类型。
* @param description RequestBody 类型,用来传递简单的键值对。
* @return
*/
@Multipart
@POST("user/photo")
Call<UserBean> getData(@Part MultipartBody.Part photo, @Part("description")RequestBody description);


File file = new File(Environment.getExternalStorageDirectory(),"wangshu.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"),file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos","wangshu.png",photoRequestBody);

// 用 retrofit 加工出对应的接口实例对象
INetApiService iNetApiService = retrofit.create(INetApiService.class);

// 调用接口函数,获得网络工作对象。
String msg="59.108.54.37";
Call<UserBean> callWorker =iNetApiService.getData(photo,RequestBody.create(null,"wangshu"));

@PartMap

多个文件上传:

1
2
3
4
// 与单文件上传类似,只是使用了 Map 封装了上传的文件,并用 @PartMap 注解标示起来。
@Multipart
@POST("user/photo")
Call<UserBean> getData(@PartMap Map<String,RequestBody> photos,@Part("description") RequestBody description);

消息报头

在 HTTP 请求中,为了防止攻击或过滤掉不安全的访问,或者添加特殊加密的访问等,以便减轻服务器的压力和保证请求的安全,通常都会在消息报头中携带一些特殊的消息头处理。Retrofit 提供了两种添加消息报头的方式:

静态添加

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
@GET("getIpInfo.php?ip=59.108.54.37")
@Headers({
"Accept-Encoding: application/json",
"User-Agent: MoonRetrofit"
})
Call<UserBean> getData();


// 拦截器
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {

Request request = chain.request();
Log.e(TAG, String.valueOf(request.headers()));

return chain.proceed(request);
}
}).build();



// 创建一个 Retrofit 对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://ip.taobao.com/service/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();

// 用 retrofit 加工出对应的接口实例对象
INetApiService iNetApiService = retrofit.create(INetApiService.class);

// 调用接口函数,获得网络工作对象。
String msg="59.108.54.37";
Call<UserBean> callWorker = iNetApiService.getData();

// 用获取的 callWork 对象执行网络请求
callWorker.enqueue(new Callback<UserBean>() {
@Override
public void onResponse(Call<UserBean> call, Response<UserBean> response) {
Log.e(TAG,response.toString());
Log.e(TAG,response.body().toString());
}

@Override
public void onFailure(Call<UserBean> call, Throwable t) {

}
});

动态添加

1
2
@GET("getIpInfo.php?ip=59.108.54.37")
Call<ResponseBody> getData(@Header("Location") String location);

功能扩展

OkHttpClient

Retrofit 可使用自己扩展的 OkHttpClient,一般最常扩展的就是 Interceptor 拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {

Request.Builder builder = chain.request().newBuilder();
builder.addHeader("Accept-Charset", "UTF-8");
builder.addHeader("Accept", " application/json");
builder.addHeader("Content-type", "application/json");

Request request = builder.build();
return chain.proceed(request);
}
}).build();


// 创建一个 Retrofit 对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();

addConverterFactory

扩展的是对返回的数据类型的自动转换,把一种数据对象转换为另一种数据对象。

1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL)
// 把 Http 访问得到的 json 字符串转换为 Java 数据对象 UserBean。
.addConverterFactory(GsonConverterFactory.create())
.build();

如果现有的扩展包不能满足需要,可以继承 Retrofit 的接口。retrofit2.Converter<F,T>,自己实现 Converter 和ConverterFactory。

addCallAdapterFactory

扩展的是对网络工作对象 callWorker 的自动转换,把 Retrofit 中执行网络请求的 Call 对象,转换为接口中定义的Call 对象。

Retrofit底 层虽然使用了 OkHttpClient 去处理网络请求,但并没有使用 okhttp3.call 这个 Call 接口,而是自己又建了一个 retrofit2.Call 接口,OkHttpCall 继承的是 retrofit2.Call,与 okhttp3.call 只是引用关系。(依赖倒置原则)

1
2
3
4
5
6
7
public interface INetApiService {
/**
* 返回值是个 RxJava 的 Flowable 对象
**/
@GET("/demo/yy")
Flowable<UserBean> getInfo(@Query("id")String id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个 Retrofit 对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
// 为 Retrofit 添加对应的扩展
// callAdapter 做的事情就是把 retrofit2.Call 对象适配转换为 Flowable<T> 对象。
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();

// 用 retrofit 加工出对应的接口实例对象
INetApiService iNetApiService = retrofit.create(INetApiService.class);

// 调用接口函数,获得网络工作对象,得到 Flowable 类型的 callWorker 对象。
Flowable<UserBean> callWorker = iNetApiService.getInfo("111");

如果现有的扩展包不能满足需要,可以继承 Retrofit 的接口 retrofit2.CallAdapter<R,T>,自己实现 CallAdapter 和 CallAdapterFactory。


Kotlin 用法

首先添加依赖

1
2
3
4
5
// Retrofit 是基于 OkHttp 开发的,因此这个依赖会自动将 Retrofit、OkHttp 和 Okio 一起下载。
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
// Retrofit 会将服务器返回的 JSON 数据自动解析成对象
// 因此这是一个 Retrofit 的转换库,它是借助 GSON 库来解析数据的,所以会自动将 GSON 一起下载。
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'

data class 可以通过 JSON To Kotlin Class(JsonToKotlinClass)插件生成,快捷键 Option + K on Mac。

使用 Kotlin 简化 Retrofit 网络请求的回调

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
// 传统写法
val appService = ServiceCreator.create<AppService>()
appService.getAppData().enqueue(object : Callback<List<AppBean>>{
override fun onResponse(call: Call<List<AppBean>>, response: Response<List<AppBean>>) {
// 得到服务器返回的数据
}

override fun onFailure(call: Call<List<AppBean>>, t: Throwable) {
// 对异常情况进行处理
}
})

/**
* 等价于上面的写法
*/
suspend fun getAppData(){
// 对于 try catch,现在每次请求都要进行一次。
// 也可以选择不处理,然后发生异常时会一层层向上抛出,直到被某一层的函数处理了位置,
// 所以也可以在某一统一的入口函数中只进行一次 try catch,从而让代码变得更加精简。
try {
val appList = ServiceCreator.create<AppService>().getAppData().await()
}catch (e:Exception){
// 对异常情况进行处理
}
}
/**
* 定义一个挂起函数 await()
* 由于不同的 Service 接口返回的数据类型也不同,所以使用泛型的方式。
* 给它声明了一个泛型 T,并将函数定义成了 Call<T> 的扩展函数,这样,
* 所有返回值是 Call 类型的 Retrofit 网络请求接口就都可以直接调用此函数了。
*/
suspend fun <T> Call<T>.await(): T{
// 挂起当前协程,并且由于扩展函数的原因,现在拥有了 Call 对象的上下文,可直接调用 enqueue()。
return suspendCoroutine { continuation ->
enqueue(object : Callback<T>{
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body!=null)continuation.resume(body)
else continuation.resumeWithException(RuntimeException("response body is null"))
}

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

注:挂起函数只能在协程作用域或其他挂起函数中调用,使用起来会有局限性。但通过合理的项目架构设计,也可以轻松地将各种协程的代码应用到一个普通的项目当中。

示例1

1
2
3
4
5
6
7
8
9
10
11
interface Api{
@GET("users/{user}/repos")
fun listRepos(@Path("user") user:String): Call<List<Repo>>

/**
* suspend,是挂起函数的使用,会自动放到后台请求。
* 因为会自动切回来,所以也就不需要使用 Call 回调了。
*/
@GET("users/{user}/repos")
suspend fun listReposKt(@Path("user") user:String): List<Repo>
}
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
        val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()

val api = retrofit.create(Api::class.java)

// api.listRepos("feiyeyuanye")
// .enqueue(object : Callback<List<Repo>?>{
// override fun onFailure(call: Call<List<Repo>?>, t: Throwable) {
//
// }
//
// override fun onResponse(call: Call<List<Repo>?>, response: Response<List<Repo>?>) {
// // ViewBinding
// binding.textView.text = response.body()?.get(0)?.name
// }
// })

// 协程对 Retrofit 的支持。
GlobalScope.launch {
// 因为没有了回调,但需要处理错误的情况(没有 onFailure),所以用 try catch 来处理。
try {
val repos = api.listReposKt("feiyeyuanye") // 挂起函数,会自动切回来。。后台
binding.textView.text = repos[0].name // 前台
}catch (e:Exception){
binding.textView.text = e.message
}
}

示例2

1
class AppBean(val id: String, val name: String, val version: String)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 接口文件,通常建议以具体的功能种类名开头,并以 Service 结尾。
*/
interface AppService {

/**
* GET 请求,并传入请求地址的相对路径,根路径会在稍后设置。
* 返回值必须声明成 Retrofit 内置的 Call 类型,并通过泛型指定服务器响应的数据应该转换成什么对象。
* -----------------------------------------------------------------------------
* Retrofit 还提供了强大的 Call Adapters 功能来允许自定义方法返回值的类型,
* 比如结合 RxJava 使用就可以将返回值声明成 Observable、Flowable 等类型。
*/
@GET("get_data.json")
fun getAppData(): Call<List<AppBean>>
}
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
class RetrofitActivity : BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_retrofit)

btnGet.setOnClickListener{
// 构建 Retrofit 对象
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2/") // 根路径
.addConverterFactory(GsonConverterFactory.create()) // 指定 Retrofit 在解析数据时所使用的转换库
.build()
// 创建一个该接口的动态代理对象,然后就可以随意调用接口中定义的方法,而 Retrofit 会自动执行具体的处理。
val appService = retrofit.create(AppService::class.java)
// appService.getAppData() 返回一个 Call<List<AppBean>> 对象,再调用它的 enqueue() ,
// Retrofit 就会根据注解中配置的服务器接口地址去进行网络请求,服务器响应的数据会回调到 enqueue() 中传入的 Callback 实现里。
// 当发起请求时,Retrofit 会自动在内部开启子线程,当数据回调到 Callback 中之后,Retrofit 又会自动切换回主线程。
appService.getAppData().enqueue(object : Callback<List<AppBean>> {
override fun onResponse(call: Call<List<AppBean>>, response: Response<List<AppBean>>) {
// 得到 Retrofit 解析后的对象,也就是 List<AppBean> 类型的数据。
val list = response.body()
if (list != null){
for (app in list){
Log.d("TAG","id is ${app.id}")
Log.d("TAG","name is ${app.name}")
Log.d("TAG","version is ${app.version}")
}
}
}

override fun onFailure(call: Call<List<AppBean>>, t: Throwable) {
t.printStackTrace()
}
})
}
}
}

示例3:

1
2
3
4
5
6
7
8
interface ApiService {
/**
* suspend,是挂起函数的使用,会自动放到后台请求。
* 因为会自动切回来,所以也就不需要使用 Call 回调了。
*/
@GET("users")
suspend fun queryData():List<UserBean>
}
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
class TestActivity : AppCompatActivity() {
private val TAG = "TAG_TestActivity"

private lateinit var binding: ActivityTestBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestBinding.inflate(layoutInflater)
setContentView(binding.root)

initData()

// KTX 扩展也提供了创建协程作用域的方法,如在 UI 页面中可以使用 lifecycleScope 构建一个协程作用域:
// lifecycleScope.launch { }
// lifecycleScope.launchWhenCreated { }
// lifecycleScope.launchWhenResumed { }
// lifecycleScope.launchWhenStarted { }
// 后三者会创建出某个生命周期下的协程作用域,同样地,在 ViewModel 中可以使用 ViewModelScope 创建协程作用域:
// viewModelStore.launch{}

// 使用 KTX 扩展创建协程作用域的好处是,viewModelStore 或 lifecycleScope 是与生命周期绑定的,所以当 Activity 销毁时,会自动取消协程操作,无需进行额外的操作。

// 在实际开发中,数据相关的操作一般放在 ViewModel 中进行,这时就需要将数据操作结果回调给 UI 层,
// 这可使用 LiveData 组件解决,但由于 LiveData 应对复杂场景时支持能力较弱,所以 Google 建议将 LiveData 迁移到 Kotlin 数据流(Flow)中。
}

private fun initData() {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)

val job = Job()
CoroutineScope(job).launch(Dispatchers.Main) {
// 这里参数为 Dispatchers.Main,但网络请求依然可以正常执行,
// 这是因为 Retrofit 自动为我们做了处理:当接口声明为 suspend 类型时,Retrofit 会自动切换到子线程中执行。
val result = Gson().toJson(apiService.queryData())
binding.tvText.text = result
// 如果 Retrofit 没有自动处理,则只需要在执行网络请求时开启一个 Dispatchers.IO 协程即可
// val result = withContext(Dispatchers.IO){
// apiService.queryData()
// }
// binding.tvText.text = Gson().toJson(apiService.queryData())
}
}
}

如果报错: java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or its super classes (declaration of ‘java.lang.invoke.LambdaMetafactory’ appears in /apex/com.android.runtime/javalib/core-oj.jar)

在 build.gradle 中添加:

1
2
3
4
5
6
7
8
android {
....

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

处理复杂的接口地址类型

除了上述简单的固定接口地址外,还有可能接口地址中的部分内容是动态变化的,如:

GET http://example.com/<page>/get_data.json,page 部分代表页数。可按照如下写法:

1
2
3
4
5
6
/**
* {page} 占位符
* 当调用此方法发起请求时,Retrofit 就会自动将 page 参数的替换到占位符的位置。
*/
@GET("{page}/get_data.json")
fun getData(@Path("page") page: Int):Call<Data>

服务器接口可能还会要求我们传入一系列的参数,格式如下:

GET http://example.com/get_data.json?u=<user>&t=<token>。可按照如下写法:

1
2
3
4
5
/**
* 当发起网络请求时,Retrofit 会自动按照带参数 GET 请求的格式将这两个参数构建到请求地址当中。
*/
@GET("get_data.json")
fun getData(@Query("u") user: String, @Query("t") token:String):Call<Data>

除了 GET ,还有一些其它的常用请求方法。GET 请求用于从服务器获取数据,POST 请求用于向服务器提交数据,PUT 和 PATCH 请求用于修改服务器上的数据,DELETE 请求用于删除服务器上的数据。

比如服务器提供了如下接口:DELETE http://example.com/data/<id>,可按如下写法:

1
2
3
4
5
6
7
/**
* Call 的泛型指定成了 ResponseBody,这是因为
* POST,PUT,PATCH,DELETE 这几种请求类型更多是用于操作服务器上的数据,而不是获取数据,
* 所以通常它们对服务器响应的数据并不关心,ResponseBody 表示 Retrofit 能够接收任意类型的响应数据,并且不会对响应数据进行解析。
*/
@DELETE("data/{id}")
fun deleteData(@Path("id") id:String):Call<ResponseBody>

如果需要向服务器提交数据:

POST http://example.com/data/create

{“id”:1, “content”:”The data”}

1
2
3
4
5
6
7
8
/**
* 使用 POST 请求,需要将数据放到 HTTP 请求的 body 部分。借助 @Body 注解完成。
* 请求时,会自动将对象中的数据转换成 JSON 格式的文本,并放到 HTTP 请求的 body 部分,
* 服务器在收到请求后只需要从 body 中将这部分数据解析出来即可。
* 这中写法同样适用于 PUT,PATCH,DELETE 类型的请求提交数据。
*/
@POST("data/create")
fun createData(@Body data:AppBean):Call<ResponseBody>

如果服务器接口要求在 HTTP 请求的 header 中指定参数:

1
2
3
GET http://example.com/get_data.json
User-Agent: okhttp
Cache-Control: max-age=0
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 静态 header 声明
* 这些 header 参数其实就是一个个的键值对。
*/
@Headers("User-Agent: okhttp","Cache-Control: max-age=0")
@GET("get_data.json")
fun getData():Call<AppBean>

/**
* 发起网络请求时,Retrofit 会自动将参数中传入的值设置到 User-Agent 和 Cache-Control 中。
*/
@GET("get_data.json")
fun getData1(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl:String):Call<AppBean>

Retrofit 构造器的最佳写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 构建的 Retrofit 对象是全局通用的,
* 只需要在调用 create() 时针对不同的 Service 接口传入相应的 Class 类型即可。
* 因此,可以将通用的部分功能封装起来,从而简化获取 Service 接口动态代理对象的过程。
* --------------------------------------------------------------------
* 使用 object 关键字变成一个单例类
*/
object ServiceCreator {

/**
* 定义了一个常量
*/
private const val BASE_URL = "http://10.0.2.2/"

private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()

fun <T> create(serviceClass: Class<T>):T = retrofit.create(serviceClass)
}
1
2
// 使用封装类来获取一个 AppService 接口的动态代理对象
val appService = ServiceCreator.create(AppService::class.java)

通过泛型实化继续优化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object ServiceCreator {

private const val BASE_URL = "http://10.0.2.2/"

private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()

fun <T> create(serviceClass: Class<T>):T = retrofit.create(serviceClass)

/**
* 定义了一个不带参数的 create(),
* 并使用 inline 关键字修饰方法,使用 reified 关键字修饰泛型,这是泛型实化的两大前提条件。
* 接下来就可以使用 T::class.java 这种语法了。
*/
inline fun <reified T> create():T = create(T::class.java)
}
1
2
// 通过泛型实化优化上面代码
val appService = ServiceCreator.create<AppService>()

备注

参考资料:

GitHub 官方主页

第一行代码(第3版)

Android 进阶之光

从架构角度看 Retrofit 的作用、原理和启示