kotlin的retrofit

与okhttp不同的是,okhttp侧重的是底层通信的实现,而retrofit侧重的是上层接口的封装。

基本用法

使用retrofit,我们先要添加依赖库,编辑app/build.gradle文件:

dependencies {
    ...
    implementation 'com.squareup.retrofit2:retrofit:2.6.1'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
}

由于retrofit是基于okhttp开发的,因此添加第一条依赖时,会自动将retrofit、okhttp和okio这几个库一起下载,无需再手动引入okhttp。

此外,retrofit会将服务器返回的json数据自动解析成对象,因此第二条依赖是retrofit的一个转换库,他是借助gson来解析json的,会将gson库下载。

由于retrofit会借助gson将json数据转换为对象,因此我们需要新建一个app类:

class App(val id: String, val name: String, val version: String)

其次我们根据服务器接口的功能进行分类,以此创建不同的接口并在其中定义不同的接口方法。新建AppService接口:

interface AppService {

    //此处注解表示调用getAppData方法是会发起get请求
    //请求的地址是我们在get注解中传入的具体参数
    @GET("get_data.json")
    //此处的Call来自retrofit,服务器响应的是一个包含App数据的json数组
    fun getAppData(): Call>

}

修改activity_main.xml:



    

修改MainActivity:

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private var resList = listOf(getAppDataBtn)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        for (res in resList) {
            res.setOnClickListener(this)
        }
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.getAppDataBtn -> {
                val retrofit = Retrofit.Builder()
                        .baseUrl("http://10.0.2.2/")
                        //指定retrofit在解析数据时使用的转换库
                        .addConverterFactory(GsonConverterFactory.create())
                        .build()

                //获得retrofit对象之后,我们可以调用他的create方法,并传入具体的service接口所对应的class
                //传入后会创建一个该接口的动态代理对象,拥有之后可以随意调用接口中所定义的所有方法
                val appService = retrofit.create(AppService::class.java)

                //调用getAppData方法后,返回一个Call>对象
                //此时调用enqueue方法,retrofit就会根据GET注解中配置的服务器地址去进行网络请求
                //而请求后服务器返回的内容会回调到enqueue方法中传入的callback实现
                //需要注意的是,当我们发起网络请求时,retrofit会自动开启内部的子线程
                //数据回调到callback后,retrofit又会自动切换回主线程
                appService.getAppData().enqueue(object : Callback> {
                    //此处是enqueue方法中传入的callback实现
                    override fun onResponse(call: Call>, response: Response>) {
                        //调用body方法获得返回的内容,即List类型的数据
                        val list = response.body()
                        if (list != null) {
                            for (app in list) {
                                Log.e("MainActivity", "id is ${app.id}, " +
                                        "name is ${app.name}, " +
                                        "version is ${app.version} ")
                            }
                        }
                    }

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

为了让程序使用http,需要进行如下配置:右击res->New->Directory新建一个xml目录,接着右击xml->New->File,创建一个network_config.xml文件,写入以下内容:

 
 
     
         
             
         
     

此配置文件的意思是,允许我们以明文的方式在网络上传输数据,而http使用的就是明文传输方式。我们接着修改AndroidManifest.xm:

 
 
    
 
     
        ... 
     

这里设置了允许使用明文的方式来进行网络请求,同时声明了网络权限。此时可运行。

处理复杂的接口地址类型

真实情况下,服务器所提供的接口地址不可能像http://10.0.2.2/get_data.json这样如此简单。

为方便举例,此处先定义Data类,包含id和content两个字段:

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

}

但服务器不可能总给我们提供静态的接口,在很多情况下,接口的部分内容会是动态变化的:

GET http://example.com//get_data.json

在此处,表示页数,我们传入不同的页数,服务器就会返回不同的数据。该地址对应到retrofit中如下:

interface ExampleService {
    
    @GET("{page}/get_data.json")
    fun getData(@Path("page") page: Int): Call

}

在注解指定的接口中,使用了一个{page}的占位符,然后在getData方法中添加了一个page参数,并使用@Path("page")注解来声明这个参数。

这样,当我们调用getData方法发起请求时,retrofit会自动将page参数的值替换到占位符的位置,从而组成一个合法的请求地址。

此外,很多服务器接口会要求传入一系列参数,如:

GET http://example.com/get_data.json?u=&t=

这是一种标准的带参数GET请求的格式,接口地址最后的问号用于连接参数部分,而每个参数之间都是使用一个等号连接的键值对(即“key = value”的形式),多个参数之间使用“&”符号进行分割。

在示例的地址中,服务器要求传入user和token这两个参数的值,对于这种地址,我们可以使用刚才的@Path注解的方式,但会有些许麻烦,retrofit针对这种带参数的get请求,专门提供了一种语法支持:

interface ExampleService {
    
    @GET("get_data.json")
    fun getData(@Query("u") user: String, @Query("t") token: String): Call

}

我们在getData方法中添加了user和token两个参数,并使用Query注解对他们进行声明。发起网络请求时,retrofit会自动按照带参数GET请求的格式,将这两个参数构建到请求地址中。

不过HTTP并不是只有GET请求这一种类型,而是有很多种,其中比较常用的有GET、POST、PUT、PATCH、DELETE这几种。它们之间的分工也很明确,简单概括的话,GET请求用于从服务器获取数据,POST请求用于向服务器提交数据,PUT和PATCH请求用于修改服务器上的数据,DELETE请求用于删除服务器上的数据。

比如服务器提供了以下接口:

DELETE http://example.con/data/

这样的接口通常意味着要根据id来删除一条指定的数据,我们想要用retrofit发送这样的请求可以这样写:

interface ExampleService {

    @DELETE("data/{id}")
    fun deleteData(@Path("id") id: String): Call

}

在返回值声明是,我们将Call的泛型指定为ResponseBody,这是因为与GET请求不同,其余请求是操作服务器中的数据,而不是获取,所以一般不关心服务器返回的数据,而ResponseBody表示retrofit能够接受任意类型的响应数据,且不对其进行解析。

如果我们需要提交数据到服务器,接口地址如下:

POST http://example.com/data/create
{"id": 1, "content": "The description for this data."}

我们使用POST请求来提交数据,此时需要将数据放到HTTP请求的body部分,我们可以使用@Body注解来实现:

interface ExampleService {

    @POST("data/create")
    fun createData(@Body data: Data): Call

}

此时我们在createData方法中声明了一个Data类型的参数,并加上了@Body注解。当retrofit发出POST请求时,会自动将Data对象中的数据转换为json格式的文本,并放到HTTP请求的body部分,之后服务器就会解析我们传到body的内容。对于其他请求如PUTPATCHDELETE也可以使用这种写法。

最后,某些服务器接口可能要求我们在HTTP请求的header中指定参数:

GET http://example.com/get_data.json 
User-Agent: okhttp 
Cache-Control: max-age=0

这些header参数其实是一个个的键值对,在retrofit中可以使用@Headers注解来声明他们:

interface ExampleService {

    @Headers("User-Agent: okhttp", "Cache-Control: max-age=0")
    @GET("get_data.json")
    fun getData(): Call

}

这样的写法只能对应静态的header,如果要动态指定header,需要使用@Header注解:

interface ExampleService {

    @GET("get_data.json")
    fun getData(@Header("User-Agent") userAgent: String,
        @Header("Cache-Control") cacheControl: String): Call

}

现在当发起网络请求时,retrofit会自动将参数中传入的值,设置到User-Agent和CacheControl这两个header当中,从而实现了动态指定header值的功能。

retrofit构建器的最佳写法

想要得到AppService的动态代理对象,首先需要用Retrofit.Builder构建出一个retrofit对象,然后再调用这个对象的create方法创建动态代理对象。如果只是写一次还好,每次都要写未免太麻烦。

事实上构建出的retrofit对象是全局通用的,因此我们每次只需要在调用create方法是针对不同的service方法传入相应的Class类型即可。

新建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()

    //当外部调用这个create方法时,实际上调用的就是retrofit对象的create方法
    fun  create(serviceClass: Class): T = retrofit.create(serviceClass)
}

此处我们将ServiceCreator定义为一个单例类,并在内部制定了一个BASE_URL常量用于指定retrofit的根路径。定义retrofit对象的方法是相同的,使用Retrofit.Builder方法构建。

需要注意的是,我们都使用private来声明这些对象,对于外部他们都是不可见的。最后,我们提供了一个外部可见的create方法,并接受一个Class类型的参数。

使用这种方式封装的retrofit用法会变得很简单,比如此时我们想获取一个AppService接口的动态代理对象:

val appService = ServiceCreator.create(AppService::class.java)

之后我们就可以随意使用AppService内的方法了。

你可能感兴趣的:(kotlin,retrofit,开发语言)