三方庫源碼筆記(8)- Retrofit 與 LiveData 的結(jié)合使用

對于 Android Developer 來說,很多開源庫都是屬于開發(fā)必備的知識點(diǎn),從使用方式到實(shí)現(xiàn)原理再到源碼解析,這些都需要我們有一定程度的了解和運(yùn)用能力。所以我打算來寫一系列關(guān)于開源庫源碼解析實(shí)戰(zhàn)演練的文章,初定的目標(biāo)是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,希望對你有所幫助 ????

在上篇文章中我講解了 Retrofit 是如何實(shí)現(xiàn)支持不同的 API 返回值的。例如,對于同一個 API 接口,我們既可以使用 Retrofit 原生的 Call<ResponseBody>方式來作為返回值,也可以使用 Observable<ResponseBody>這種 RxJava 的方式來發(fā)起網(wǎng)絡(luò)請求

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
interface ApiService {

    //Retrofit 原始請求方式
    @GET("getUserData")
    fun getUserDataA(): Call<ResponseBody>

    //RxJava 的請求方式
    @GET("getUserData")
    fun getUserDataB(): Observable<ResponseBody>

}

我們在搭建項(xiàng)目的網(wǎng)絡(luò)請求框架的時候,一個重要的設(shè)計(jì)環(huán)節(jié)就是要避免由于網(wǎng)絡(luò)請求結(jié)果的異步延時回調(diào)導(dǎo)致內(nèi)存泄漏情況的發(fā)生,所以在使用 RxJava 的時候我們往往是會搭配 RxLifecycle 來一起使用。而 Google 推出的 Jetpack 組件一個很大的亮點(diǎn)就是提供了生命周期安全保障的 LiveData:從源碼看 Jetpack(3)-LiveData 源碼解析

LiveData 是基于觀察者模式來實(shí)現(xiàn)的,也完全符合我們在進(jìn)行網(wǎng)絡(luò)請求時的使用習(xí)慣。所以,本篇文章就來動手實(shí)現(xiàn)一個 LiveDataCallAdapter,即實(shí)現(xiàn)以下方式的網(wǎng)絡(luò)請求回調(diào)

interface ApiService {

    @GET("getUserData")
    fun getUserData(): LiveData<HttpWrapBean<UserBean>>

}

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        RetrofitManager.apiService.getUserData().observe(this, Observer {
                val userBean = it.data
        })
    }

}

一、基礎(chǔ)定義

假設(shè)我們的項(xiàng)目中 API 接口的返回值的數(shù)據(jù)格式都是如下所示。通過 status 來標(biāo)明本次網(wǎng)絡(luò)請求結(jié)果是否成功,在 data 里面存放具體的目標(biāo)數(shù)據(jù)

{
    "status": 200,
    "msg": "success",
    "data": {
        
    }
}

對應(yīng)我們項(xiàng)目中的實(shí)際代碼就是一個泛型類

data class HttpWrapBean<T>(val status: Int, val msg: String, val data: T) {

    val isSuccess: Boolean
        get() = status == 200

}

所以,ApiService 就可以如下定義,用 LiveData 作為目標(biāo)數(shù)據(jù)的包裝類

data class UserBean(val userName: String, val userAge: Int)

interface ApiService {

    @GET("getUserData")
    fun getUserData(): LiveData<HttpWrapBean<UserBean>>

}

而網(wǎng)絡(luò)請求不可避免會有異常發(fā)生,我們還需要預(yù)定義幾個 Exception,對常見的異常類型:無網(wǎng)絡(luò) 或者 status!=200 的情況進(jìn)行封裝

sealed class BaseHttpException(
    val errorCode: Int,
    val errorMessage: String,
    val realException: Throwable?
) : Exception(errorMessage) {

    companion object {

        const val CODE_UNKNOWN = -1024

        const val CODE_NETWORK_BAD = -1025

        fun generateException(throwable: Throwable?): BaseHttpException {
            return when (throwable) {
                is BaseHttpException -> {
                    throwable
                }
                is SocketException, is IOException -> {
                    NetworkBadException("網(wǎng)絡(luò)請求失敗", throwable)
                }
                else -> {
                    UnknownException("未知錯誤", throwable)
                }
            }
        }

    }

}

/**
 * 由于網(wǎng)絡(luò)原因?qū)е?API 請求失敗
 * @param errorMessage
 * @param realException
 */
class NetworkBadException(errorMessage: String, realException: Throwable) :
    BaseHttpException(CODE_NETWORK_BAD, errorMessage, realException)

/**
 * API 請求成功了,但 code != successCode
 * @param bean
 */
class ServerCodeNoSuccessException(bean: HttpWrapBean<*>) :
    BaseHttpException(bean.status, bean.msg, null)

/**
 * 未知錯誤
 * @param errorMessage
 * @param realException
 */
class UnknownException(errorMessage: String, realException: Throwable?) :
    BaseHttpException(CODE_UNKNOWN, errorMessage, realException)

而在網(wǎng)絡(luò)請求失敗的時候,我們往往是需要向用戶 Toast 失敗原因的,所以此時一樣需要向 LiveData postValue,以此將異常情況回調(diào)出去。因?yàn)檫€需要一個可以根據(jù) Throwable 來生成對應(yīng)的 HttpWrapBean 對象的方法

data class HttpWrapBean<T>(val status: Int, val msg: String, val data: T) {

    companion object {

        fun error(throwable: Throwable): HttpWrapBean<*> {
            val exception = BaseHttpException.generateException(throwable)
            return HttpWrapBean(exception.errorCode, exception.errorMessage, null)
        }

    }

    val isSuccess: Boolean
        get() = status == 200

}

二、LiveDataCallAdapter

首先需要繼承 CallAdapter.Factory 類,在 LiveDataCallAdapterFactory 類中判斷是否支持特定的 API 方法,在類型校驗(yàn)通過后返回 LiveDataCallAdapter

class LiveDataCallAdapterFactory private constructor() : CallAdapter.Factory() {

    companion object {

        fun create(): LiveDataCallAdapterFactory {
            return LiveDataCallAdapterFactory()
        }

    }

    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        if (getRawType(returnType) != LiveData::class.java) {
            //并非目標(biāo)類型的話就直接返回 null
            return null
        }
        //拿到 LiveData 包含的內(nèi)部泛型類型
        val responseType = getParameterUpperBound(0, returnType as ParameterizedType)
        require(getRawType(responseType) == HttpWrapBean::class.java) {
            "LiveData 包含的泛型類型必須是 HttpWrapBean"
        }
        return LiveDataCallAdapter<Any>(responseType)
    }

}

LiveDataCallAdapter 的邏輯也比較簡單,如果**網(wǎng)絡(luò)請求成功且狀態(tài)碼等于 200 **則直接返回接口值,否則就需要根據(jù)不同的失敗原因構(gòu)建出不同的 HttpWrapBean 對象

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
class LiveDataCallAdapter<R>(private val responseType: Type) : CallAdapter<R, LiveData<R>> {

    override fun responseType(): Type {
        return responseType
    }

    override fun adapt(call: Call<R>): LiveData<R> {
        return object : LiveData<R>() {

            private val started = AtomicBoolean(false)

            override fun onActive() {
                //避免重復(fù)請求
                if (started.compareAndSet(false, true)) {
                    call.enqueue(object : Callback<R> {
                        override fun onResponse(call: Call<R>, response: Response<R>) {
                            val body = response.body() as HttpWrapBean<*>
                            if (body.isSuccess) {
                                //成功狀態(tài),直接返回 body
                                postValue(response.body())
                            } else {
                                //失敗狀態(tài),返回格式化好的 HttpWrapBean 對象
                                postValue(HttpWrapBean.error(ServerCodeNoSuccessException(body)) as R)
                            }
                        }

                        override fun onFailure(call: Call<R>, t: Throwable) {
                            //網(wǎng)絡(luò)請求失敗,根據(jù) Throwable 類型來構(gòu)建 HttpWrapBean
                            postValue(HttpWrapBean.error(t) as R)
                        }
                    })
                }
            }

        }
    }

}

然后在構(gòu)建 Retrofit 的時候添加 LiveDataCallAdapterFactory

object RetrofitManager { 

    private val retrofit = Retrofit.Builder()
        .baseUrl("https://getman.cn/mock/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(LiveDataCallAdapterFactory.create())
        .build()

    val apiService = retrofit.create(ApiService::class.java)

}

然后就可以直接在 Activity 中發(fā)起網(wǎng)絡(luò)請求了。當(dāng) Activity 處于后臺時 LiveData 不會回調(diào)任何數(shù)據(jù),避免了常見的內(nèi)存泄漏和 NPE 問題

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
@Router(EasyRouterPath.PATH_RETROFIT)
class LiveDataCallAdapterActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_live_data_call_adapter)
        btn_success.setOnClickListener {
            RetrofitManager.apiService.getUserDataSuccess().observe(this, Observer {
                if (it.isSuccess) {
                    showToast(it.toString())
                } else {
                    showToast("failed: " + it.msg)
                }
            })
        }
    }

    private fun showToast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }

}

三、GitHub

LiveDataCallAdapter 的實(shí)現(xiàn)邏輯挺簡單的,在使用上也很簡單。本篇文章也算作是在了解了 Retrofit 源碼后所做的一個實(shí)戰(zhàn) ???? 這里也提供上述代碼的 GitHub 鏈接:AndroidOpenSourceDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容