對于 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