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

前陣子定了個小目標,打算來深入瞭解下幾個常用的開源庫,看下其實現原理和源碼,進行總結並輸出成文章。初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、Coil、OkHttp 等幾個。目前已經完成了七篇關於 EventBus、ARouter、LeakCanary、Retrofit 的文章,本篇是第八篇,繼續來對 Retrofit 進行講解,算作是對 Retrofit 的知識擴展😂😂
原文地址:https://juejin.im/user/923245496518439/posts

在上篇文章中我講解了 Retrofit 是如何實現支持不同的 API 返回值的。例如,對於同一個 API 接口,我們既可以使用 Retrofit 原生的 Call<ResponseBody>方式來作爲返回值,也可以使用 Observable<ResponseBody>這種 RxJava 的方式來發起網絡請求

/**
 * 作者:leavesC
 * 時間:2020/10/24 12:45
 * 描述:
 * GitHub:https://github.com/leavesC
 */
interface ApiService {

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

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

}

我們在搭建項目的網絡請求框架的時候,一個重要的設計環節就是要避免由於網絡請求結果的異步延時回調導致內存泄漏情況的發生,所以在使用 RxJava 的時候我們往往是會搭配 RxLifecycle 來一起使用。而 Google 推出的 Jetpack 組件一個很大的亮點就是提供了生命週期安全保障的 LiveData:從源碼看 Jetpack(3)-LiveData源碼解析

LiveData 是基於觀察者模式來實現的,也完全符合我們在進行網絡請求時的使用習慣。所以,本篇文章就來動手實現一個 LiveDataCallAdapter,即實現以下方式的網絡請求回調

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
        })
    }

}

一、基礎定義

假設我們的項目中 API 接口的返回值的數據格式都是如下所示。通過 status 來標明本次網絡請求結果是否成功,在 data 裏面存放具體的目標數據

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

對應我們項目中的實際代碼就是一個泛型類

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

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

}

所以,ApiService 就可以如下定義,用 LiveData 作爲目標數據的包裝類

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

interface ApiService {

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

}

而網絡請求不可避免會有異常發生,我們還需要預定義幾個 Exception,對常見的異常類型:無網絡 或者 status!=200 的情況進行封裝

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("網絡請求失敗", throwable)
                }
                else -> {
                    UnknownException("未知錯誤", throwable)
                }
            }
        }

    }

}

/**
 * 由於網絡原因導致 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)

而在網絡請求失敗的時候,我們往往是需要向用戶 Toast 失敗原因的,所以此時一樣需要向 LiveData postValue,以此將異常情況回調出去。因爲還需要一個可以根據 Throwable 來生成對應的 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 方法,在類型校驗通過後返回 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) {
            //並非目標類型的話就直接返回 null
            return null
        }
        //拿到 LiveData 包含的內部泛型類型
        val responseType = getParameterUpperBound(0, returnType as ParameterizedType)
        require(getRawType(responseType) == HttpWrapBean::class.java) {
            "LiveData 包含的泛型類型必須是 HttpWrapBean"
        }
        return LiveDataCallAdapter<Any>(responseType)
    }

}

LiveDataCallAdapter 的邏輯也比較簡單,如果網絡請求成功且狀態碼等於200則直接返回接口值,否則就需要根據不同的失敗原因構建出不同的 HttpWrapBean 對象

/**
 * 作者:leavesC
 * 時間:2020/10/22 21:06
 * 描述:
 * GitHub:https://github.com/leavesC
 */
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() {
                //避免重複請求
                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) {
                                //成功狀態,直接返回 body
                                postValue(response.body())
                            } else {
                                //失敗狀態,返回格式化好的 HttpWrapBean 對象
                                postValue(HttpWrapBean.error(ServerCodeNoSuccessException(body)) as R)
                            }
                        }

                        override fun onFailure(call: Call<R>, t: Throwable) {
                            //網絡請求失敗,根據 Throwable 類型來構建 HttpWrapBean
                            postValue(HttpWrapBean.error(t) as R)
                        }
                    })
                }
            }

        }
    }

}

然後在構建 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 中發起網絡請求了。當 Activity 處於後臺時 LiveData 不會回調任何數據,避免了常見的內存泄漏和 NPE 問題

/**
 * 作者:leavesC
 * 時間:2020/10/24 12:39
 * 描述:
 * GitHub:https://github.com/leavesC
 */
@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()
    }

}

三、結尾

LiveDataCallAdapter 的實現邏輯挺簡單的,在使用上也很簡單。本篇文章也算作是在瞭解了 Retrofit 源碼後所做的一個實戰😁😁這裏也提供上述代碼的 GitHub 鏈接:Demo

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