Retrofit+kotlin Coroutines(協程)+mvvm(Jetpack架構組件)實現更簡潔的網絡請求

前言

使用kotlin協程也有一段時間了,給我最大的感受就是完全可以替代Rxjava了,並且寫起來更加的簡潔。

6月份Retrofit發佈的2.6.0版本內部支持了kotlin協程中的掛起(suspend)修飾符,這就意味着我們可以更加方便的用Retrofit結合kotlin協程來實現網絡請求了。
在這裏插入圖片描述

之前我都是使用Rxjava2+Retrofit實現網絡請求的功能,然後加入了AutoDispose來實現自動解綁以免發生內存泄漏的問題,感興趣的可以看看AutoDispose代替RxLifecycle優雅的解決RxJava內存泄漏問題

那協程有沒有自動解綁的東西呢。當然有了
目前google官方也給我們提供了androidx.lifecycle:lifecycle-viewmodel-ktx的依賴包,給ViewModel中擴展了一個作用域叫viewModelScope

viewModelScope是一個綁定到當前viewModel的作用域 當ViewModel被清除時會自動取消該作用域,所以不用擔心內存泄漏爲問題

那這樣一來,我們完全可以使用Retrofit+Coroutines這個方案來代替之前用Retrofit+Rxjava+AutoDispose的方案了。

話不多說,直接上代碼

我在網上找了個公開的API接口 https://www.apiopen.top/novelApi

我們就請求這個接口了

添加依賴

首先是添加需要的依賴包,如下

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
    implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
    implementation "com.squareup.retrofit2:retrofit:2.6.1"
    implementation "com.squareup.okhttp3:logging-interceptor:4.2.0"
    implementation "com.squareup.retrofit2:converter-gson:2.6.1"

代碼

在看下面的代碼之前,你可能需要先看一下下面幾篇文章,如果你都知道的話 直接跳過即可

ViewModelLiveData 還不是很瞭解的話先看一下這篇文章
mvp過渡到mvvm(Android 架構組件)

關於一些好用的擴展方法,看一下這個
Kotlin基於RxJava的擴展方法(超級好用)
這裏我們主要是看對數據類的擴展,節省一些無用代碼

關於Retrofit的工廠類這裏我就不放出來了,估計大家的代碼都差不多,需要的可以看demo,這裏我就不再浪費篇幅,直接用了。

實體類

響應基類

data class BaseResp<T>(
    var code: Int = 0,
    var msg: String = "",
    var `data`: T
)

小說數據實體類

package com.yzq.coroutineretofitmvvm.bean


import com.google.gson.annotations.SerializedName

data class Fiction(
    var bid: String = "",
    var bookname: String = "",
    var introduction: String = "",
    @SerializedName("book_info")
    var bookInfo: String = "",
    var chapterid: String = "",
    var topic: String = "",
    @SerializedName("topic_first")
    var topicFirst: String = "",
    @SerializedName("date_updated")
    var dateUpdated: Int = 0,
    var author: String = "",
    @SerializedName("author_name")
    var authorName: String = "",
    @SerializedName("top_class")
    var topClass: String = "",
    var state: String = "",
    var readCount: String = "",
    var praiseCount: String = "",
    @SerializedName("stat_name")
    var statName: String = "",
    @SerializedName("class_name")
    var className: String = "",
    var size: String = "",
    @SerializedName("book_cover")
    var bookCover: String = "",
    @SerializedName("chapterid_first")
    var chapteridFirst: String = "",
    var chargeMode: String = "",
    var digest: String = "",
    var price: String = "",
    var tag: List<String> = listOf(),
    @SerializedName("is_new")
    var isNew: Int = 0,
    var discountNum: Int = 0,
    @SerializedName("quick_price")
    var quickPrice: Int = 0,
    var formats: String = "",
    @SerializedName("audiobook_playCount")
    var audiobookPlayCount: String = "",
    var chapterNum: String = "",
    var isShortStory: Boolean = false,
    var userid: String = "",
    @SerializedName("search_heat")
    var searchHeat: String = "",
    @SerializedName("num_click")
    var numClick: String = "",
    @SerializedName("recommend_num")
    var recommendNum: String = "",
    @SerializedName("first_cate_id")
    var firstCateId: String = "",
    @SerializedName("first_cate_name")
    var firstCateName: String = "",
    var reason: String = ""
)

定義請求接口

這個沒什麼好說的,需要注意的就是我們的方法前面用 suspend 修飾

interface ApiService {
    @GET("https://www.apiopen.top/novelApi")
    suspend fun getFictions(): BaseResp<List<Fiction>>
}

用於解析響應數據的擴展方法

/*數據解析擴展函數*/
fun <T> BaseResp<T>.dataConvert(): T {
    if (code == 200) {
        return data
    } else {
        throw Exception(msg)
    }
}

ViewModel

註釋也很詳細了,這裏就不多說了

class NetViewModel : ViewModel() {
    var fictions = MutableLiveData<List<Fiction>>()
    fun getFictions() {
        /*viewModelScope是一個綁定到當前viewModel的作用域  當ViewModel被清除時會自動取消該作用域,所以不用擔心內存泄漏爲問題*/
        viewModelScope.launch {
            try {
                /*withContext表示掛起塊  配合Retrofit聲明的suspend函數執行 該塊會掛起直到裏面的網絡請求完成 最一行就是返回值*/
                val data = withContext(Dispatchers.IO) {

                    /*dataConvert擴展函數可以很方便的解析出我們想要的數據  接口很多的情況下下可以節省不少無用代碼*/
                    RetrofitFactory.instance.getService(ApiService::class.java)
                        .getFictions().dataConvert()
                }

                /*給LiveData賦值  ui會自動更新*/
                fictions.value = data

            } catch (e: Exception) {


                /*請求異常的話在這裏處理*/
                e.printStackTrace()

                Log.i("請求失敗", "${e.message}")

            }


        }
    }


}

Activity

Activity中代碼就很簡單了,主要就是創建ViewModel示例,更新ui


class MainActivity : AppCompatActivity() {
    private lateinit var netViewModel: NetViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        /*創建viewmodel*/
        netViewModel = ViewModelProviders.of(this).get(NetViewModel::class.java)

        btn.setOnClickListener {
            /*請求數據*/
            netViewModel.getFictions()
        }

        /*數據發生變化時更新ui*/
        netViewModel.fictions.observe(this, Observer {
            tv.text = Gson().toJson(it)
        })
    }
}

下面我們來看看運行結果:

在這裏插入圖片描述

可以看到,網絡請求的功能已經實現了。

那對比RxJava的實現方式,你覺得哪種方式更好呢?

好了,本篇文章到此結束。

demo


如果你覺得本文對你有幫助,麻煩動動手指頂一下,算是對本文的一個認可,如果文中有什麼錯誤的地方,還望指正,轉載請註明轉自喻志強的博客 ,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章