Android 协程与retrofit库的结合使用

文章目录

  • 前言
  • **一、retrofit2.6.0之前使用步骤**
        • 1.引入库
        • 2.创建接收服务器返回数据的类(根据具体数据对应)
        • 3.定义描述网络请求的接口
        • 4.创建retrofit实例及接口实例创建函数
        • 5.发送网络请求
        • 6.调用封装为LiveData()数据
  • **二、retrofit2.6.0之后使用步骤**
        • 1.导入依赖
        • 2.创建接收服务器返回数据的类(根据具体数据对应)
        • 3.定义描述网络请求的接口
        • 4.创建retrofit实例
        • 5.创建协程调用函数
  • **三、scope协程作用域使用场景**
        • 1.GlobalScope
        • 2.MainScope
        • 3.ViewModelScope
        • 4.LiveData
        • 5.LifecycleScope
  • 总结


前言

现在Android端网络访问基本使用的都是okhttp或retrofit第三方库,retrofit库对okhttp库进行了再次封装与改进,并在2.6.0版本开始,retrofit增强了对kotlin语言的兼容性,并内置了对kotlin协程的支持,使得retrofit+kotlin组合使用更加轻便简洁,这篇文章将介绍retrofit 2.6.0之前和之后使用kotin协程异步请求网络的步骤,通过对比你会体现到升级后的极简体验。

一、retrofit2.6.0之前使用步骤

这里借《第一行代码》的天气预报项目来讲述。

1.引入库
    implementation 'com.squareup.retrofit2:retrofit:2.6.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

这里引入了retrofit 2.6.0版本,converter-gson转换器使用gson库集合retrofit将网络访问数据直接转换为对应的实例实体对象,kotlinx-coroutines-core 为 Coroutine 的核心 API, 而kotlinx-coroutines-android 为安卓平台的一些提供了一些支持,特别是提供了 Dispatchers.Main 这个 UI Dispatcher。

2.创建接收服务器返回数据的类(根据具体数据对应)
class DailyResponse(val status: String, val result: Result) {

    class Result(val daily: Daily)

    class Daily(val temperature: List<Temperature>, val skycon: List<Skycon>, @SerializedName("life_index") val lifeIndex: LifeIndex)

    class Temperature(val max: Float, val min: Float)

    class Skycon(val value: String, val date: Date)

    class LifeIndex(val coldRisk: List<LifeDescription>, val carWashing: List<LifeDescription>, val ultraviolet: List<LifeDescription>, val dressing: List<LifeDescription>)

    class LifeDescription(val desc: String)

}
3.定义描述网络请求的接口

天气相关的接口(根据经纬度):

interface WeatherService {
    @GET("v2.5/${SunnyWeatherApplication.TOKEN}/{lng},{lat}/realtime.json")
    fun getRealtimeWeather(@Path("lng") lng: String, @Path("lat") lat: String): Call<RealtimeResponse>
    
    @GET("v2.5/${SunnyWeatherApplication.TOKEN}/{lng},{lat}/daily.json")
    fun getDailyWeather(@Path("lng") lng: String, @Path("lat") lat: String): Call<DailyResponse>
}

${}表示取变量值,{lng}表示标记了lng变量用于传入具体数值,在对应函数接收参数中使用@Path("")注解将传入变量替换{lng}的位置。函数返回类型为指定泛型的Call接口。

interface PlaceService {
    @GET("v2/place?token=${SunnyWeatherApplication.TOKEN}&lang=en_US")
    fun searchPlaces(@Query("query") query: String): Call<PlaceResponse>
}

@QUery("")用于条件字段参数,作用于方法参数(主要在GET中使用),这里替换后实际请求的网址为:
在这里插入图片描述若query多个参数,如这个网址:
http://102.10.10.132/api/News?newsId=1&type=类型1
则接口这样描述:

    @GET("News")
    Call<NewsBean> getItem(@Query("newsId") String newsId, @Query("type") String type);

更多retrofit注解参考:Retrofit网络请求参数注解

4.创建retrofit实例及接口实例创建函数
object ServiceCreator {

    private const val BASE_URL = "https://api.caiyunapp.com/"
    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

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

    //inline内联  物化reified 使得泛型能获取自身的Class类型
    inline fun <reified T> create(): T = create(T::class.java)
}

这里使用object单例类,使得retrofit只有一份实例,同时这里使用了inline来修饰方法(inline编译时就会将内联函数的代码替换到实际调用的地方,则不存在泛型擦除),reified来修饰泛型从而实现了泛型实化(获取泛型的具体class类型,这在Java中是不允许的,具体参考:kotlin之泛型的实化、协变、逆变 )

5.发送网络请求
    //搜索地点
    suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()

    private suspend fun <T> Call<T>.await(): T {
        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)
                }
            })
        }
    }

这里对Call接口扩展了一个await属性方法用于实现在协程中的网络访问,这里使用suspend标记为挂起函数,使用suspendCoroutine(必须在协程作用域或挂起函数中调用,接收一个lambda表达式,开启子线程执行代码同时阻塞当前协程,表达式参数有一个Continuation对象,调用resume()或resumeWithException()恢复协程执行),由于这里调用时传入的Call指定了T的具体类型,所以调用serachPlaces方法可直接得到对应的数据实体对象。

6.调用封装为LiveData()数据
    fun searchPlaces(query: String) = fire(Dispatchers.IO) {
        val placeResponse = SunnyWeatherNetwork.searchPlaces(query)
        if (placeResponse.status == "ok") {
            val places = placeResponse.places
            Result.success(places)
        } else {
            Result.failure(RuntimeException("response status is ${placeResponse.status}"))
        }
    }
    private fun <T> fire(context: CoroutineContext, block: suspend () -> Result<T>) =
        liveData<Result<T>>(context) {
            val result = try {
                block()
            } catch (e: Exception) {
                Result.failure<T>(e)
            }
            emit(result)
        }

其中封装的fire函数第一个参数context指定协程作用域所在线程,第二个参数block是一个函数参数,然后类型为Result,在livedata(){}代码块中拥有了协程作用域,result变量接收block执行结果得到Result对象,调用emit(result)封装LiveData<>数据,则fire函数将LiveData作为执行返回结果。在searchplace()函数中执行子线程请求网络数据,在闭包代码块中调用Result.success(places)封装数据,最终searchPlace()函数返回LiveData>,至此网络数据获取完成。

二、retrofit2.6.0之后使用步骤

这里使用一个简单get接口返回来描述。
postman请求如下:
Android 协程与retrofit库的结合使用_第1张图片

1.导入依赖
	implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

这里使用2.9.0版本的retrofit。

2.创建接收服务器返回数据的类(根据具体数据对应)
data class urlData(val address:String,val time:String)
3.定义描述网络请求的接口
interface urlInterface {
    @GET("lit/geturl")
    suspend  fun getUrl(): UrlData
    }

注意这里将接口方法声明为suspend,同时返回类型直接设置为数据类,retrofit内部直接封装了协程请求网络,数据解析,线程切换,极大的精简了业务代码。

4.创建retrofit实例
      var retrofit = Retrofit.Builder()
            .baseUrl("http://45.32.72.37:8081/") //设置BaseURL需以'/'结尾
            .addConverterFactory(GsonConverterFactory.create()) //json解析器Gson
            .callbackExecutor(Executors.newSingleThreadExecutor()) //使用单独的线程
            .build() //创建Retrofit对象
5.创建协程调用函数
        MainScope().launch {
            val api = MyApplication.retrofit.create(urlInterface::class.java) //创建Api代理对象
            var data = api.getUrl()
            Log.w("Retrofit_test","网址${data.address}时间${data.time}")
            webview.loadUrl("${data.address}")
        }

由于接口的geturl声明为suspend函数,这里需要开启一个协程作用域来调用,MainScope用于在主线程开启作用域,由于retrofit底层的处理,此段代码和前面的一样实现了异步请求同步化,在该作用域中,执行getUrl()函数期间协程处于挂起状态,等待结果返回后恢复执行,同时由于mainScope作用域下处于主线程,可以直接更新UI,至于这里为什么使用mainScope构建作用域,下面会进行说明。

三、scope协程作用域使用场景

1.GlobalScope

通常使用GlobalScope.launch() 启动一个协程,Globalscope 通常启动顶级协程,这些协程在整个应用程序生命周期内运行,不会被过早地被取消。程序代码通常应该使用自定义的协程作用域。直接使用 GlobalScope 的 async 或者 launch 方法是强烈不建议的。

2.MainScope

查看MainScope()创建源码:

@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

我们发现MainScope默认的调度器是主线程,所以就可以直接在其作用域更新UI了。

3.ViewModelScope

s如果项目采用的是MVVM架构的话,上述例子就不适用了,这里介绍下MVVM在Android中的模式:

  • Model层: 主要负责数据的提供。Model层提供业务逻辑的数据结构(比如,实体类),提供数据的获取(比如,从本地数据库或者远程网络获取数据),提供数据的存储。
  • View层: 主要负责界面的显示。View层不涉及任何的业务逻辑处理,它持有ViewModel层的引用,当需要进行业务逻辑处理时通知ViewModel层。在Android中对应xml和activity。
  • ViewModel层: 主要负责业务逻辑的处理。ViewModel层不涉及任何的视图操作。通过官方提供的Data Binding库,View层和ViewModel层中的数据可以实现绑定,ViewModel层中数据的变化可以自动通知View层进行更新,因此ViewModel层不需要持有View层的引用。ViewModel层可以看作是View层的数据模型和Presenter层的结合。

所以在ViewModel中使用viewModelScope 来进行协程相关操作,同时当 ViewModel.onCleared() 被调用的时候,viewModelScope 会自动取消作用域内的所有协程。

4.LiveData

Kotlin 同样为 LiveData 赋予了直接使用协程的能力。在第一个网络访问举例中就使用了Livedata的协程作用域,直接在 liveData {} 代码块中调用需要异步执行的挂起函数,并调用 emit() 函数发送处理结果。当 LiveData 进入 active 状态时,liveData{ } 会自动执行。

5.LifecycleScope

当 LifeCycle 回调 onDestroy() 时,协程作用域 lifecycleScope 会自动取消。在 Activity/Fragment 等生命周期组件中我们可以很方便的使用。

suspend fun <T> Lifecycle.whenCreated()
suspend fun <T> Lifecycle.whenStarted()
suspend fun <T> Lifecycle.whenResumed()
suspend fun <T> LifecycleOwner.whenCreated()
suspend fun <T> LifecycleOwner.whenStarted()
suspend fun <T> LifecycleOwner.whenResumed()

这种特殊用法可以指定至少在特定的生命周期之后再执行挂起函数,可以进一步减轻 View 层的负担。

总结

jetpack组件和retrofit对kotlin语言进行了很多便捷的扩展,使用需要引入相应的ktx扩展库即可感受到极大的编码简洁化。同时在以往采用okhttp+Thread访问网络需要大量的模板代码和异常处理,但采用retrofit+协程来实现的话会很大的便利开发者实现具体业务。

你可能感兴趣的:(Android,Android-kotlin,android,kotlin,java)