文章目录
- Retrofit的使用
- 最好用的网络库: Retrofit
- Retrofit的基本用法
- 处理复杂接口的地址类型
- Retrofit构建器的最佳写法
Retrofit的使用
最好用的网络库: Retrofit
- Retrofit是一款由Square公司开发的网络库,但是它和OkHttp定位完全不同,OkHttp的侧重点是底层通信的实现,而Retrofit的侧重点是上层接口的封装
- 事实上,Retrofit就是Square公司在OkHttp的基础上进一步开发出来的应用层网络通信库,使得我们可以用更加面向对象的思维进行网络操作
Retrofit的基本用法
- 首先我们可以配置好一个根路径,然后在指定服务接口地址地址时只需要使用相对路径即可,这样就不用每次都指定完整URL地址了
- 另外Retrofit允许我们对服务器接口进行归类,将功能同属一类的服务器接口定义到同一个接口文件当中,从而让代码结构变得更加合理
- 最后我们也完全不用关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值,然后通过注解的方式指定该方法对应哪个服务器接口,以及需要提供哪些参数.
- 我们在程序中调用该方法的时候,Retrofit会自动向对应的服务器接口发起请求,并将相应的数据解析成返回值声明的类型,这就使得我们可以用更加面向对象的方式来进行网络操作.
- 要想使用Retrofit,我们必须在项目当中添加必要的依赖库
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
-
因为Retrofit是基于OkHttp开发的,因此添加上述第一条依赖会自动将Retrofit,OkHttp和Okio这几个库一起下载,我们无需再手动引入OkHttp库
-
另外Retrofit还会将服务器返回的JSON数据自动解析成对象,因此上述第二条依赖就是一个Retrofit转换库,它借助GSON来解析JSON数据,所以会将GSON库一起下载下来,这样我们就不需要再导入GSON库了
-
继续使用上面的JSON接口,所以先定义一个App类,并加入id,name,version这三个字段
-
class App(val id: Int, val name: String, val version: String)
-
接下来我们可以根据服务器接口的功能进行归类,创建不同种类的接口文件,并在其中定义对应具体服务器接口的方法
-
但是在我们本地Apache服务器只有一个提供JSON数据的接口,所以只需要定义一个接口文件,并包含一个方法即可
-
新建AppService接口,代码如下所示
interface AppService {
@GET("get_data.json")
fun getAppData() : Call<List<App>>
}
- 通常Retrofit的接口文件建议以具体功能种类名开头,以Service结尾,这是一种比较好的命名方式
- 在上述代码当中,在方法上面添加了@GET注解,表示当调用getAppData()方法的时候,Retrofit会发起一条GET请求,请求的地址就是我么你在@GET注解中传入的具体参数,在这个地方只需要我们传入相对路径就可以了,根路劲会在稍后进行设置.
- 然后再上述的代码当中,getAppData()方法的返回值必须声明成为Retrofit中内置的Call类型,并通过泛型来指定服务器响应的数据应该转换成什么对象,由于服务器响应的是一个包含App数据类型的JSON数据,因此在声明中将泛型指定为了List
- 定义好接口之后,接下来在界面上添加一个按钮
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/getAppData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get App Data" />
</LinearLayout>
- 在MainActivity当中处理它的点击事件
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getAppData.setOnClickListener {
//首先使用Retrofit.Builder来构建Retrofit对象
val retrofit = Retrofit.Builder()
//指定retrofit请求的根路径
.baseUrl("http://10.0.2.2/")
//指定retrofit在解析数据的时候所使用的转换库
.addConverterFactory(GsonConverterFactory.create())
.build()
//使用retrofit对象的create方法,传入具体Service接口所对应的Class类型,创建一个该接口的动态代理对象
//有了动态代理对象之后,就可以随意调用接口中定义的所有方法了,而retrofit会自动执行具体的处理就可以了
val appService = retrofit.create(AppService::class.java)
//然后调用getAppData()方法会返回一个Call<List<App>>对象
//这时候我们再调用一下enqueue()方法,retrofit就会根据注解中配置的服务器接口去进行网络请求了
//服务器响应的数据会回调到enqueue()方法中传入的Callback实现当中
appService.getAppData().enqueue(object : Callback<List<App>> {
override fun onResponse(call: Call<List<App>>, response: Response<List<App>>) {
//调用response.body()方法将会得到retrofit解析后的对象,也就是List<App>数据,最后遍历其中的数据,将数据打印出来即可
val list = response.body()
if (list != null) {
for (app in list) {
Log.d("MainActivity", "id is ${app.id}")
Log.d("MainActivity", "name is ${app.name}")
Log.d("MainActivity", "version is ${app.version}")
}
}
}
override fun onFailure(call: Call<List<App>>, t: Throwable) {
t.printStackTrace()
}
})
}
}
}
- 因为服务器使用的是HTTP,需要对网络安全进行以下配置
- 这里设置允许使用明文的方式来进行网络请求,同时声明了网络权限,运行程序
- 可以看到服务器的数据成功响应并解析出来了
处理复杂接口的地址类型
- 在真实的开发环境中,服务器所提供的接口地址不可能会一直如此简单,但是合理使用Retrofit是可以应对千变万化的情况的
- 先定义一个Data类
class Data(val id: String, val content: String)
- 当服务器的地址如下所示的时候
GET http://example.com/get_data.json
- 这是最简单的一种情况,接口地址是静态的,永远不会改变,那么对应到Retrofit当中,使用如下写法即可
interface ExampleService {
@GET("get_data.json")
fun getData() : Call<Data>
}
- 但是服务器不会总是给我们提供这种静态类型的接口,很多的时候,接口地址中的部分内容可能会是动态变化的,比如下面这个接口地址
GET http://example.com/<page>/get_data.json
- 在这个接口当中,部分代表页数,我们传入不同的页数,服务器返回的数据也会不同,这种接口地址对应到Retrofit当中的写法如下所示
interface ExampleService {
@GET("{page}/get_data.json")
fun getData(@Path("page") page: Int) : Call<Data>
}
- 在@GET注解中指定的接口地址当中,这里使用了一个{page}占位符,然后又在getData()方法中添加了一个page参数,并使用@Path(“page”)注解来声明这个参数,这样当调用getData()方法发起请求的时候,Retrofit就会自动将page参数的值替换到占位符的位置,从而组成一个合法的请求地址
- 另外很多服务器接口还要求我们传入一系列的参数,格式如下
GET http://example.com/get_data.json?u=<user>&t=<token>
- 这是一种标准的带参数GET请求的格式,接口地址的最后用问号来连接参数部分,每一个参数都是一个等号连接键值对,多个参数之间使用"&"符号进行分隔
- 在上述的地址中,服务器要求我们传入user和token两个参数的值,对于这种情况,虽然也可以使用上述@Path注解的方式来进行解决,但是这样会比较麻烦,Retrofit针对这种带参数的GET请求,专门提供了一种语法支持
interface ExampleService {
@GET("get_data.json")
fun getData(@Query("u") user: String, @Query("t") token: String) : Call<Data>
}
- 这样在getData()方法中添加了user和token两个参数,并使用@Query注解对它们进行声明,这样发起网络请求的时候,Retrofit就会自动按照带参数GET请求的格式将这两个参数构建到请求地址当中
- 不过HTTP并不只是只是之后GET这一种请求类型,还包含,POST,PUT,PATCH,DELETE
- 这些方法之间的分工也很明确,GET用于从服务器获取数据,POST用于向服务器提交数据,PUT和PATCH请求用于修改服务器上的数据,DELETE用于删除服务器上面的数据
- 而Retrofit对常用的HTTP请求类型都进行了支持,使用@GET,@POST,@PUT,@PATCH,@DELETE等注解,就可以让Retrofit发出相应的请求了.
Retrofit构建器的最佳写法
- 就是说下面这种获取Service接口的动态代理对象的写法实际上是有一些麻烦的
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val appService = retrofit.create(AppService::class.java)
- 我们想要得到AppService的动态代理对象,需要先使用Retrofit.Builder构建出一个Retrofit对象,然后调用Retrofit对象的create()方法创建动态代理对象.如果只是写一次还好,每次调用任何服务接口都要像这样写一次的话,肯定没人能受的了.
- 事实上,确实也没有每次都要写一遍的必要,因为构建出的Retrofit对象是全局通用的,只需要在调用create()方法时针对不同的Service接口传入相应的Class类型即可.因此,我们可以将通用的这部分功能封装起来,从而简化获取Service接口动态代理对象的过程.
- 新建一个ServiceCreator单例类,代码如下所示
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)
}
- 这里定义了一个单例类,并在它的内部定义了一个BASE_URL常量,用于指定Retrofit的根路径.
- 然后同样在内部使用Retrofit.Builder构建一个Retrofit对象,注意这些都是private修饰符来进行修饰声明的,相当于对于外部而言,它们都是不可见的.
- 最后提供一个外部可见的create()方法,并接收一个Class类型的参数,当在外部调用这个方法的时候,实际上就是调用了Retrofit对象的create()方法,从而创建出相应的Service接口的动态代理对象.
- 经过这样封装之后,Retrofit的用法将会变得异常简单,比如我们想要获得一个AppService接口的动态代理对象,只需要使用如下写法即可
val appService = ServiceCreator.create(AppService::class.java)
- 之后就可以随意调用AppService接口中定义的任何方法了.