For Android Developer, many open source libraries are essential knowledge points for development, from the use of ways to implementation principles to source code parsing, which require us to have a certain degree of understanding and application ability. So I’m going to write a series of articles about source code analysis and practice of open source libraries, the initial target is EventBus, ARouter, LeakCanary, Retrofit, Glide, OkHttp, Coil and other seven well-known open source libraries, hope to help you 😇😇

Official account: byte array

Article Series Navigation:

  • Tripartite library source notes (1) -EventBus source detailed explanation
  • Tripartite library source notes (2) -EventBus itself to implement one
  • Three party library source notes (3) -ARouter source detailed explanation
  • Third party library source notes (4) -ARouter own implementation
  • Three database source notes (5) -LeakCanary source detailed explanation
  • Tripartite Library source note (6) -LeakCanary Read on
  • Tripartite library source notes (7) -Retrofit source detailed explanation
  • Tripartite library source notes (8) -Retrofit in combination with LiveData
  • Three party library source notes (9) -Glide source detailed explanation
  • Tripartite library source notes (10) -Glide you may not know the knowledge point
  • Three party library source notes (11) -OkHttp source details
  • Tripartite library source notes (12) -OkHttp/Retrofit development debugger
  • Third party library source notes (13) – may be the first network Coil source analysis article

In the last article, I explained how Retrofit was implemented to support different API return values. For example, we can use Retrofit’s native Call

as the return value, or make a network request using Observable

in RxJava for the same API

/ * * * the author: leavesC * time: 2020/10/24 12:45 * description: * GitHub:https://github.com/leavesC * /
interface ApiService {

    //Retrofit the original request mode
    @GET("getUserData")
    fun getUserDataA(a): Call<ResponseBody>

    //RxJava request mode
    @GET("getUserData")
    fun getUserDataB(a): Observable<ResponseBody>

}
Copy the code

When we set up the network request framework of the project, an important design link is to avoid memory leakage caused by asynchronous delayed callback of network request results. Therefore, when using RxJava, we usually use RxLifecycle together. A big highlight of Google’s Jetpack component is LiveData, which provides life cycle security: Source code Jetpack (3) -LiveData source parsing

LiveData is implemented based on the observer pattern, which is exactly what we are used to when making network requests. Therefore, this article will begin to implement a LiveDataCallAdapter, that is, to implement the following network request callback

interface ApiService {

    @GET("getUserData")
    fun getUserData(a): 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}}})Copy the code

1. Basic definitions

Assume that the return values of the API interface in our project are in the following data format. Status is used to indicate whether the network request result is successful, and specific target data is stored in data

{
	"status": 200."msg": "success"."data": {}}Copy the code

The actual code in our project is a generic class

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

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

}
Copy the code

Therefore, the ApiService can be defined as follows, using LiveData as the wrapper class for the target data

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

interface ApiService {

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

}
Copy the code

While network requests inevitably have exceptions, we also need to pre-define several exceptions, for common Exception types: no network or 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("Network request failed", throwable)
                }
                else -> {
                    UnknownException("Unknown error", throwable)
                }
            }
        }

    }

}

/** API request failed due to network reasons *@param errorMessage
 * @param realException
 */
class NetworkBadException(errorMessage: String, realException: Throwable) :
    BaseHttpException(CODE_NETWORK_BAD, errorMessage, realException)

/** * API request was successful, but code! = successCode *@param bean
 */
class ServerCodeNoSuccessException(bean: HttpWrapBean<*>) :
    BaseHttpException(bean.status, bean.msg, null)

/** * Unknown error *@param errorMessage
 * @param realException
 */
class UnknownException(errorMessage: String, realException: Throwable?) :
    BaseHttpException(CODE_UNKNOWN, errorMessage, realException)
Copy the code

When a network request fails, we often need to Toast the failure reason to the user, so we need to send the LiveData postValue to call back the exception. You also need a method that can generate the corresponding HttpWrapBean object from the Throwable

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

}
Copy the code

Second, the LiveDataCallAdapter

First of all we need to inherit CallAdapter Factory class, LiveDataCallAdapterFactory class judge whether to support the specific API method, return to LiveDataCallAdapter on type checking is passed

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

    companion object {

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

    }

    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        if(getRawType(returnType) ! = LiveData::class.java) {
            Return null if it is not the target type
            return null
        }
        // get the internal generic types that LiveData contains
        val responseType = getParameterUpperBound(0, returnType as ParameterizedType)
        require(getRawType(responseType) == HttpWrapBean::class.java) {
            "LiveData contains generic types that must be HttpWrapBean"
        }
        return LiveDataCallAdapter<Any>(responseType)
    }

}
Copy the code

The logic of LiveDataCallAdapter is also simple. If the network request succeeds and the status code is equal to 200, the interface value is returned directly. Otherwise, different HttpWrapBean objects need to be built according to different failure reasons

/ * * * the author: leavesC * time: 2020/10/22 21:06 * description: * GitHub:https://github.com/leavesC * /
class LiveDataCallAdapter<R>(private val responseType: Type) : CallAdapter<R, LiveData<R>> {

    override fun responseType(a): Type {
        return responseType
    }

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

            private val started = AtomicBoolean(false)

            override fun onActive(a) {
                // Avoid duplicate requests
                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) {
                                // Success status, return body directly
                                postValue(response.body())
                            } else {
                                // Failure state, return the formatted HttpWrapBean object
                                postValue(HttpWrapBean.error(ServerCodeNoSuccessException(body)) as R)
                            }
                        }

                        override fun onFailure(call: Call<R>, t: Throwable) {
                            // The network request fails and the HttpWrapBean is built based on the Throwable type
                            postValue(HttpWrapBean.error(t) as R)
                        }
                    })
                }
            }

        }
    }

}
Copy the code

Then add LiveDataCallAdapterFactory when building Retrofit

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)

}
Copy the code

You can then initiate the network request directly in the Activity. LiveData does not call back any data when the Activity is in the background, avoiding common memory leaks and NPE problems

/ * * * the author: leavesC * time: 2020/10/24 dough * description: * 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()
    }

}
Copy the code

Third, making

The implementation logic of LiveDataCallAdapter is quite simple, and it is also very simple to use. This article is also a practical example of Retrofit’s source code. 😁😁 here is also a GitHub link to the above code: AndroidOpenSourceDemo