From our previous Retrofit source code analysis, we know that CallAdapter plays an important role in Retrofit.

Today we’ll take a look at customizing the CallAdapter to encapsulate the handling of error messages. Then, we will analyze the entire execution flow of the custom CallAdapter through the source code.

Custom CallAdapter

Before customizing the CallAdapter, we figured out which roles we needed. In Retrofit, CallAdapters were created using factory classes, so we needed to define a factory class for the CallAdapter;

In order to differentiate ourselves from the existing Call, we also need to customize our own MyCall interface. This means that our custom CallAdapter will be used only if the return type of the interface function is MyCall. With the MyCall interface, we need a corresponding implementation class to handle the results (success and failure) of the request uniformly, and then pass the success or failure to our custom Callback.

Therefore, customized CallAdapter encapsulates error information in a unified manner. The main classes are as follows:

  • MyCallAdapterFactory
  • MyCallAdapter
  • MyCall
  • MyCallImpl
  • Callback
  • ApiException

Callback.java:

Interface Callback<T> {// Callback fun onSuccess(data: T?) Funonerror (error: ApiException)}Copy the code

MyCall.java:

Interface MyCall<T> {// Cancel the request fun cancel() // Initiate the request fun request(callback: callback <T>) funclone(): MyCall<T>
}
Copy the code

MyCallImpl.java:

private class MyCallImpl<T>(private val call: Call<T>, private val callbackExecutor: Executor?) : MyCall<T> {

    override fun cancel() { call.cancel() } override fun request(callback: Callback<T>) { call.enqueue(object : Retrofit2. Callback<T> {override fun onFailure(call: call <T>, T: Throwable) {// Local function funprocessFailure() {/ / exception handling through unified transformException function callback onError (ExceptionHelper. TransformException (t))} / / failure callback thread of executionif(callbackExecutor ! = null) { callbackExecutor.execute { processFailure() } }elseOverride fun onResponse(call: call <T>, response: response <T>) {// Local function funprocessResponse() {
                    val code = response.code()
                    if (code in200.. 299) { callback.onSuccess(response.body()) }else {
                        callback.onError(ApiException("-"."$code ${response.message()}")}} // Successful callback to execute threadif(callbackExecutor ! = null) { callbackExecutor.execute { processResponse() } }else {
                    processResponse()
                }
            }
        })
    }

    override fun clone(): MyCall<T> {
        return MyCallImpl(call.clone(), callbackExecutor)
    }
}

Copy the code

MyCallAdapter.java:

private class MyCallAdapter<R>(private val responseType: Type, private val executor: Executor?) Override fun Adapt (Call: Call<R>): MyCall<R> {return MyCallImpl(call, executor)
    }

    override fun responseType(): Type {
        return responseType
    }
}
Copy the code

MyCallAdapterFactory.java:

class MyCallAdapterFactory : CallAdapter.Factory() {
    override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {// If the return type of the interface function is not MyCall, MyCallAdapter is not usedif (getRawType(returnType) ! = MyCall::class.java)returnNull // Return type of generic check(returnType is ParameterizedType) { "MyCall must have generic type (e.g., MyCall<ResponseBody>)" }
        val responseType = getParameterUpperBound(0, returnType) / / is actually MainThreadExecutor val executor. = retrofit callbackExecutor () / / return our custom MyCallAdapterreturn MyCallAdapter<Any>(responseType, executor)
    }
}
Copy the code

Custom exception class apiException.java:

class ApiException(val errorCode: String? , errorMessage: String?) : Exception(errorMessage)Copy the code

Unified handling of exceptions, encapsulation into our custom exception ApiException:

class ExceptionHelper {
    companion object {
        private const val ERROR_CODE = "error_code_001"
        @JvmStatic
        fun transformException(t: Throwable): ApiException {
            t.printStackTrace()
            return when (t) {
                is SocketTimeoutException -> ApiException(
                    ERROR_CODE,
                    "Network access timeout"
                )
                is ConnectException -> ApiException(
                    ERROR_CODE,
                    "Network connection is abnormal"
                )
                is UnknownHostException -> ApiException(
                    ERROR_CODE,
                    "Network access timeout"
                )
                is JsonParseException -> ApiException(
                    ERROR_CODE,
                    "Data parsing exception"
                )
                else -> ApiException(
                    ERROR_CODE,
                    t.message
                )
            }
        }

    }
}
Copy the code

Then apply our custom MyCallAdapterFactory to Retrofit:

val retrofit: Retrofit = retrofit.builder ().baseurl (API_URL) // Add MyCallAdapterFactory factory .addCallAdapterFactory(MyCallAdapterFactory()) .addConverterFactory(GsonConverterFactory.create()) .build()Copy the code

Finally, define the return MyCall type of the business interface function:

interface UserService {
    @POST("register")
    @FormUrlEncoded
    fun register2(
        @Field("username") username: String,
        @Field("mobile") mobile: String
    ): MyCall<ResponseModel<User>>
}

private fun customCall() {
    // MyCall
    val call = userService.register2("chiclaim"."110") call.request(object : Callback<ResponseModel<User>> { override fun onSuccess(data: ResponseModel<User>?) { // todo something... } override fun onError(error: ApiException) { // todo something... }})}Copy the code

At this point, we have completed the unified encapsulation of network error handling by customizing the CallAdapter.

Source code interpretation of the CallAdapter

We also covered CallAdapter in the last article Android Retrofit, but we didn’t focus on analyzing it.

Since we add our custom CallAdapterFactory through the addCallAdapterFactory function, let’s see how it works:

public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
  callAdapterFactories.add(Objects.requireNonNull(factory, "factory == null"));
  return this;
}
Copy the code

It’s just putting it in a collection, so when do you use it?

If you remember from the previous Retrofit execution process analysis, you should know that the Retrofit execution process is:

Proxy.newProxyInstance()
    -> InvocationHandler.invoke()
        -> loadServiceMethod()
            -> ServiceMethod.parseAnnotations()
                -> HttpServiceMethod.parseAnnotations()
Copy the code

CallAdapterFactories is in HttpServiceMethod parseAnnotations function used in the:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory RequestFactory) {// omit other code... CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); }Copy the code

We found that the CallAdapter is created using the createCallAdapter method:

private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
	  Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
	try {
	  //noinspection unchecked
	  return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
	} catch (RuntimeException e) {
	  throw methodError(method, e, "Unable to create call adapter for %s".returnType); }}Copy the code

Retrofit.calladapter to get the callAdapter:

public final class Retrofit { public CallAdapter<? ,? > callAdapter(TypereturnType, Annotation[] annotations) {
        return nextCallAdapter(null, returnType, annotations); } public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, TypereturnType, Annotation[] annotations) {// skipPast = null, so start = 0 Our custom MyCallAdapterFactory int start = callAdapterFactories. IndexOf (skipPast) + 1;for(int i = start, count = callAdapterFactories.size(); i < count; I++) {// that is our custom MyCallAdapter CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
          if(adapter ! = null) {returnadapter; }} // omit other code... }}Copy the code

Ok, now we know how the CallAdapter is created. In HttpServiceMethod. ParseAnnotations finish () method to create CallAdapter, then passed it on to HttpServiceMethod subclass constructor:

  • CallAdapted
  • SuspendForResponse
  • SuspendForBody

These classes override the httpServicemethod. adapt method, which essentially calls the callAdapter.adapt method passed in.

We know that the HttpServiceMethod. Invoke method is invoked in the dynamic proxy:

@Override final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
}
Copy the code

The call parameter of the CallAdapter.adapt method is actually an OkHttpCall. The actual request operations are wrapped in OkHttpCall.

The Invoke method calls the httpServicemethod. adapt method, which is the adapt method of our custom CallAdapter.

Call. Enenque (OkHttpCall); call (OkHttpCall)

We then hand the return results of the network request (including exceptions) to our custom Callback.

At this point, the entire custom CallAdapter execution process is described.

Later, I will introduce to you:

  • How to whole RxJava, Coroutine?
  • How do I upload Retrofit files
  • Retrofit file upload problems, how to analyze the source code to find answers

All the use cases for Retrofit are in myAndroidAll GitHubIn the warehouse. In addition to Retrofit, the repository also has other common Android open source library source analysis, such as “RxJava” “Glide” “LeakCanary” “Dagger2” “Retrofit” “OkHttp” “ButterKnife” “Router” and so on. In addition, there is a complete mind map of the technology stack that Android programmers need.