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