Retrofit的使用

Posted z啵唧啵唧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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接口中定义的任何方法了.

以上是关于Retrofit的使用的主要内容,如果未能解决你的问题,请参考以下文章

Android 网络请求框架之Retrofit 的 详细使用

Android 网络请求框架之Retrofit 的 详细使用

Android 网络请求框架之Retrofit 的 详细使用

Android 网络请求框架之Retrofit 的 详细使用

OkHttp,Retrofit 1.x - 2.x 基本使用

当列表的各个项目使用Retrofit和Gson的格式不同时,如何解析json列表?