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

對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 😇😇java

公衆號:字節數組git

系列文章導航:github

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

/** * 做者: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源碼解析api

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

}
複製代碼

1、基礎定義

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

{
	"status": 200,
	"msg": "success",
	"data": {
		
	}
}
複製代碼

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

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

}
複製代碼

2、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()
    }

}
複製代碼

3、GitHub

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

相關文章
相關標籤/搜索