手把手實現一個mini-Retrofit框架

卓_修武發表於2019-04-27

前文

本篇文章將採用循序漸進的編碼方式,從零開始實現一個Retorift框架;在實現過程中不斷提出問題並分析實現,最終開發出一個mini版的Retrofit框架

手把手實現一個mini-Retrofit框架

演示一個使用OkHttp的專案Demo

為了更好的演示框架的實現過程,這裡我先建立了一個簡單的Demo專案

這個Demo專案中主要包含3個部分

  1. Json資料對應JavaEntity類
  2. 專案中包裝網路請求回撥的Callback
  3. 一個包含專案所有網路介面請求的管理類RestService

JavaBean

@Data
@ToString
public class BaseResponse<T> {
    private boolean error;
    private T results;
}
複製程式碼
package com.knight.sample.entity;

import java.util.List;
import java.util.Map;

public class XianduResponse extends BaseResponse<List<GankEntity>> {
}

複製程式碼

NetCallback

package com.knight.sample;

import java.io.IOException;

/**
 * 專案封裝的統一網路請求的回撥
 * @param <T>
 */
public interface NetCallback<T> {
    void onFailure(Exception e);

    void onSuccess(T data);
}
複製程式碼

NetWorkService

package com.knight.sample;

import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class RestService {
    private static OkHttpClient okHttpClient;
    public static void init() {
        okHttpClient = new OkHttpClient.Builder()
                .build();
    }

    public static<T>  void todayGank(Class<T> responseClazz,NetCallback<T> callback) {
        Request request = new Request.Builder().url("http://gank.io/api/today")
                .get()
                .build();
        okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback));
    }

    public static<T>  void xianduGank(int count, int page,Class<T> responseClazz,NetCallback<T> callback) {
        Request request = new Request.Builder()
                .url("http://gank.io/api/xiandu/data/id/appinn/count/" + count + "/page/" + page)
                .get().build();
        okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback));
    }

    static class WrapperOkHttpCallback<T> implements Callback {
        private static Gson gson = new Gson();
        private Class<T> clazz;
        private NetCallback<T> callback;

        public WrapperOkHttpCallback(Class<T> responseClazz, NetCallback<T> netCallback) {
            this.clazz = responseClazz;
            this.callback = netCallback;
        }

        @Override
        public void onFailure(Call call, IOException e) {
            Log.e("WrapperOkHttpCallback", "onFailure");
            e.printStackTrace();
            callback.onFailure(e);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            JsonReader jsonReader = gson.newJsonReader(response.body().charStream());
            T entity = gson.getAdapter(clazz).read(jsonReader);
            Log.d("response", entity.toString());
            callback.onSuccess(entity);

        }
    }

}

複製程式碼

在NetworkService類中我們目前定義了2個Http 請求 todayGankxianduGank ,目前兩個請求方式都是 Get 其中 xianduGank 需要傳入 countpage引數分別表示每頁資料的資料以及請求的頁碼,除此之外這兩個網路請求都需要傳入 一個Class物件表示響應的Json資料對應的Model,以便在內部使用Gson來解析,以及網路請求的非同步回撥 NetCallback

我們不直接使用OkHttp提供的Callback 而是在內部簡單的做了封裝轉換成專案自己的NetCallback,因為對專案的開發人員來說,更希望的是能夠直接在Callback的success回撥中直接得到響應的Json資料對應的JavaBean.

本次提交詳細程式碼見

思考專案現狀

上文模擬的程式碼只是一個簡單的例子,可能會有更好的封裝方式,但這並不是我們這篇文章想要討論的重點。 我們回到示例中RestService類中的程式碼部分,看下目前網路請求的寫法;

因為我們專案中已經有了OKHttp這個網路庫了,有關Http具體的連線及通訊的髒話累活都可以交給它來處理,對於專案開發者,事實上我們只需要配置以下Http請求部分

  • 請求的url 地址
  • 請求的方式 (GET、POST、PUT...)
  • 請求內容

假設我們已經具備了 Java註解 以及 動態代理的相關知識,並瞭解以下知識

  • 註解可以新增在方法上
  • Retention為RUNTIME的註解可以在虛擬機器執行時也獲取到註解上的資訊
  • Java的動態代理可以執行時生成原介面型別的代理實現類物件並hook方法的呼叫

每一個網路介面呼叫請求的url地址和請求方式都是唯一的 ,那麼對於一個簡單的網路請求 我們能不能使用 註解 + 動態代理 來簡化這一過程,改為宣告式的程式設計方式來實現網路呼叫,比如就像這樣

/**
 * Created by zhuoxiuwu
 * on 2019/4/25
 * email nimdanoob@gmail.com
 */
public interface NetRestService {

    @GET("http://gank.io/api/today")
    public Call todayGank();
}
複製程式碼

我們在一個抽象介面類中新增了一個方法,在方法上新增了註解 @GET 表示這是一個Http GET請求的呼叫
註解中GET帶的預設參數列示GET請求的地址。宣告這個方法後,我們再通過Java動態代理技術在執行時解析這個方法上的註解的資訊,內部通過呼叫OKHttp的相關方法生成一個 Call物件

有了大概思路了,我們接下來先簡單的實現這樣一個小例子來驗證我們的想法是否可行

編碼實現

3.1 簡單實現一個支援GET、POST請求的Retrofit

新建一個註解類@GET

package retrofit2.http;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * Created by zhuoxiuwu
 * on 2019/4/25
 * email nimdanoob@gmail.com
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {
    //註解中 方法名寫成value 這樣的話,在使用註解傳入引數時就不用帶key了,它會作為一個預設的呼叫
    String value();
}
複製程式碼

新建一個處理Http介面類的動態代理的類Retrofit,因為我們實際網路請求的呼叫是依賴OKHttp,所以我們要求建構函式傳入OkHttp物件
目前Retrofit 類只有一個方法public T createService(final Class service) 它接收一個抽象類,並生成該抽象類的代理實現。

package retrofit2;



import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.http.GET;

public class Retrofit {

    private OkHttpClient mOkHttpClient;

    public Retrofit(OkHttpClient mOkHttpClient) {
        this.mOkHttpClient = mOkHttpClient;
    }

    @SuppressWarnings("unchecked")
    public <T> T createService(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                                         Object[] args) throws Throwable {
                        //獲取方法所有的註解
                        final Annotation[] annotations = method.getAnnotations();
                        for (int i = 0; i < annotations.length; i++) {
                            if (annotations[i] instanceof GET) { //如果註解是GET型別

                                final GET annotation = (GET) annotations[i];
                                final String url = annotation.value();
                                final Request request = new Request.Builder()
                                        .url(url)
                                        .get().build();
                                return mOkHttpClient.newCall(request);
                            }
                        }
                        return null;
                    }
                });
    }
}

複製程式碼

目前我們主要的目標是為了驗證這個方案的可行性,因此createService方法內部的邏輯很簡單

1.獲取方法上的所有註解

 //獲取方法所有的註解
                        final Annotation[] annotations = method.getAnnotations();
複製程式碼

2.判斷如果存在@GET註解則獲取註解內的值作為請求的地址

if (annotations[i] instanceof GET) { //如果註解是GET型別

    final GET annotation = (GET) annotations[i];
    final String url = annotation.value();
複製程式碼

3.根據url構造GET請求的Request物件,並作為引數呼叫OkHttpClient的newCall方法生成Call物件作為該方法呼叫的返回值

 final Request request = new Request.Builder()
                                        .url(url)
                                        .get().build();
                                return mOkHttpClient.newCall(request);
複製程式碼

以上完成了一個對@GET註解申明的Http請求的動態代理封裝,下面我們在自己的專案中驗證一下

3.2在專案中驗證

1.建立一個介面類,並新增一個方法,方法的返回型別為Call,方法是新增了@GET註解

package com.knight.sample;

import okhttp3.Call;
import retrofit2.http.GET;

/**
 * Created by zhuoxiuwu
 * on 2019/4/25
 * email nimdanoob@gmail.com
 */
public interface NetRestService {

    @GET("http://gank.io/api/today")
    public Call todayGank();
}

複製程式碼

2.在專案中新增測試方法並呼叫

private void getToDayGankByRetrofit() {
        final Retrofit retrofit = new Retrofit(new OkHttpClient());
        retrofit.createService(NetRestService.class).todayGank().enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                JsonReader jsonReader = gson.newJsonReader(response.body().charStream());
                TodayGankResponse todayGankResponse = gson.getAdapter(TodayGankResponse.class).read(jsonReader);
                showHttpResult(todayGankResponse.toString());
                 Log.d("RetrofitTest","呼叫成功,結果為"+todayGankResponse.toString());
            }
        });
    }
複製程式碼

執行之後,方法呼叫成功並得到了響應結果

 D/RetrofitTest: 呼叫成功,結果為BaseResponse(error=false, results={Android=[GankEntity(url=https://github.com/iqiyi/Neptune, desc=適用於Android的靈活,強大且輕量級的外掛框架...
複製程式碼

通過簡單的一個實現,我們成功驗證了使用註解加動態代理的方式實現一個宣告式的網路請求框架是可行的,那麼後續我們需要繼續完善這個專案,提供對更多請求方式 以及引數的支援


對於其他請求方式的支援,我們可以新增更多的表示請求方式的註解,當使用者設定了不同的註解,在內部我們使用OKHttp呼叫相應的方法。Http的請求方式大概如下

  • @DELETE
  • @GET
  • @HEAD
  • @PATCH
  • @POST
  • @PUT
  • @OPTIONS

本次提交見git

3.3繼續實現POST註解

為了加深理解,我們繼續簡單的實現一個POST請求,並支援傳入一個引數物件,作為POST請求的JSON資料

首先我們新增一個POST註解


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
    String value();
}

複製程式碼
package retrofit2;


import com.google.gson.Gson;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import retrofit2.http.GET;
import retrofit2.http.POST;

public class Retrofit {

    private OkHttpClient mOkHttpClient;

    public Retrofit(OkHttpClient mOkHttpClient) {
        this.mOkHttpClient = mOkHttpClient;
    }

    @SuppressWarnings("unchecked")
    public <T> T createService(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                                         Object[] args) throws Throwable {
                        //獲取方法所有的註解
                        final Annotation[] annotations = method.getAnnotations();

                        for (int i = 0; i < annotations.length; i++) {
                            if (annotations[i] instanceof GET) { //如果註解是GET型別
                                final GET annotation = (GET) annotations[i];
                                return parseGet(annotation.value(), method, args);
                            } else if (annotations[i] instanceof POST) {
                                final POST annotation = (POST) annotations[i];
                                return parsePost(annotation.value(), method, args);
                            }
                        }
                        return null;
                    }
                });
    }


    private Call parseGet(String url, Method method, Object args[]) {
        final Request request = new Request.Builder()
                .url(url)
                .get().build();
        return mOkHttpClient.newCall(request);
    }

    private Gson gson = new Gson();
    private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");

    private Call parsePost(String url, Method method, Object args[]) {
        final Type[] genericParameterTypes = method.getGenericParameterTypes();
        if (genericParameterTypes.length > 0) {
            final Class<?> clazz = Utils.getRawType(genericParameterTypes[0]);
            final String jsonBody = gson.toJson(args[0], clazz);
            final Request request = new Request.Builder()
                    .url(url)
                    .post(RequestBody.create(MEDIA_TYPE, jsonBody))
                    .build();
            return mOkHttpClient.newCall(request);
        }
        return null;
    }


}

複製程式碼

在 paresePost方法中我們首先通過Method的getGenericParameterTypes方法獲取所有引數的Type型別,並且通過Type類獲得引數的原始Class型別,之後就可以使用Gson轉換成對應的Json物件了。

點選檢視本次git提交的詳細程式碼

3.4 實現ConverterFactory 解耦Json轉換

在上面的例子中,我們直接在框架Retrofit中使用了Gson庫做Json轉換,但作為一個框架來說 我們不希望直接強耦合一個第三方Json轉換庫,這部分更希望交由開發者根據具體情況自由選擇;
因此我們可以對這部分做下抽象封裝,提取成一個負責Json轉換的介面, 讓應用層傳入該介面的具體實現.

package retrofit2;

import java.lang.reflect.Type;

import javax.annotation.Nullable;

import okhttp3.RequestBody;

/**
 * Created by zhuoxiuwu
 * on 2019/4/25
 * email nimdanoob@gmail.com
 */
public interface Converter<F, T> {
    @Nullable
    T convert(F value);

    abstract class Factory {
        public @Nullable
        Converter<?, RequestBody> requestBodyConverter(Type type) {
            return null;
        }

    }
}

複製程式碼

應用層需要傳入一個ConverterFactory,該工廠類負責根據傳入的Type型別,返回一個能夠將該Type型別的物件轉換成RequestBody的Converter

我們對Retrofit的建構函式以及paresePost方法做下修改,要求建構函式中傳入一個ConverterFactory的實現,並在paresePost方法中使用這個ConverterFactory來做Java物件到ReqeustBody的轉換

public class Retrofit {

    private OkHttpClient mOkHttpClient;

    private Converter.Factory mConverterFactory;

    public Retrofit(OkHttpClient mOkHttpClient, Converter.Factory mConverterFactory) {
        this.mOkHttpClient = mOkHttpClient;
        this.mConverterFactory = mConverterFactory;
    }
    //..省略部分程式碼
    
    private Call parsePost(String url, Method method, Object args[]) {
        final Type[] genericParameterTypes = method.getGenericParameterTypes();
        if (genericParameterTypes.length > 0) {
            //直接呼叫得到RequestBody
            final RequestBody requestBody = requestBodyConverter(genericParameterTypes[0]).convert(args[0]);
            final Request request = new Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();
            return mOkHttpClient.newCall(request);
        }
        return null;
    }


    public <T> Converter<T, RequestBody> requestBodyConverter(Type type) {
        return (Converter<T, RequestBody>) mConverterFactory.requestBodyConverter(type);
    }

複製程式碼

在應用層,我們實現並傳入一個Gson的ConvertFactory的實現

package com.knight.sample;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.charset.Charset;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import retrofit2.Converter;

/**
 * Created by zhuoxiuwu
 * on 2019/4/25
 * email nimdanoob@gmail.com
 */
public class GsonConverterFactory extends Converter.Factory {
    public static GsonConverterFactory create() {
        return create(new Gson());
    }

    public static GsonConverterFactory create(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        return new GsonConverterFactory(gson);
    }

    private final Gson gson;

    private GsonConverterFactory(Gson gson) {
        this.gson = gson;
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type) {
        //通過Type 轉換成Gson的TypeAdapter
        //具體型別的json轉換依賴於這個TypeAdapter
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);

    }


    final static class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
        private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
        private static final Charset UTF_8 = Charset.forName("UTF-8");

        private final Gson gson;
        private final TypeAdapter<T> adapter;

        GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
            this.gson = gson;
            this.adapter = adapter;
        }

        @Override
        public RequestBody convert(T value) {
            Buffer buffer = new Buffer();
            Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
            JsonWriter jsonWriter = null;
            try {
                jsonWriter = gson.newJsonWriter(writer);
                adapter.write(jsonWriter, value);
                jsonWriter.close();
                return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }

        }
    }

}

複製程式碼

點選檢視本次git提交的詳細程式碼

3.5 實現CallAdapter 支援方法返回型別

繼續回到Http請求的宣告中,目前我們方法所支援的返回型別都是OKHttp的Call物件,而Call物件從使用上來說,目前還是有些繁瑣,原生的Call物件返回的是ResponseBody還需要開發者自己處理並做轉換。

public interface NetRestService {

    @GET("http://gank.io/api/today")
    public Call todayGank();
}
複製程式碼

也許我們希望這個方法可以這樣定義

public interface NetRestService {

    @GET("http://gank.io/api/today")
    public TodayGankResponse todayGank();
}
複製程式碼

也許我們可以在框架內部通過判斷方法的返回型別是不是Call物件,如果不是,就在框架內部直接同步呼叫網路請求得到響應的Json內容後直接轉換成JavaBean物件作為方法的返回值。
但是這個設想存在這樣幾個問題

  1. 要實現直接返回Http結果則方法呼叫是同步呼叫,如果在主執行緒做IO請求肯定是不合理的

  2. 如果內部IO異常了,或者JSON轉換失敗了方法返回的是什麼呢?為null嗎?

因此更合理的話,在應用我們希望的是返回一個包裝的支援非同步呼叫的型別;

比如我們的專案自己新增了一個支援非同步呼叫的NetCall抽象介面

/**
 * Created by zhuoxiuwu
 * on 2019/4/26
 * email nimdanoob@gmail.com
 */
public interface NetCall<T> {
    public void execute(NetCallback<T> netCallback);
}

複製程式碼

我們希望我們的方法可以這樣申明

public interface NetRestService {

    @GET("http://gank.io/api/today")
    public NetCall<TodayGankResponse> todayGank();
}
複製程式碼

這樣的話在應用層我們呼叫的時候就可以像這樣使用

        retrofit.createService(NetRestService.class).todayGank()
                .execute(new NetCallback<TodayGankResponse>() {
                    @Override
                    public void onFailure(Exception e) {

                    }

                    @Override
                    public void onSuccess(TodayGankResponse data) {
                        Log.d("RetrofitTest","呼叫成功,結果為"+data.toString());
                        showHttpResult(data.toString());
                    }
                });
複製程式碼

那麼具體要怎麼實現呢,這個功能相當於讓Retrofit框架支援 對方法返回型別的自定義適配;和Converter介面一樣的思路,我們在框架可以定義一個 CallAdapter介面,讓應用層來具體實現並傳入

package retrofit2;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import okhttp3.Call;

/**
 * Created by zhuoxiuwu
 * on 2019/4/26
 * email nimdanoob@gmail.com
 */
public interface CallAdapter<T> {

    T adapt(Call call);


    abstract class Factory {

        public abstract CallAdapter<?> get(Type returnType,Retrofit retrofit);

        /**
         * 這是一個框架提供給開發者的util方法
         * 用於獲取型別的泛型上的型別
         * 比如 Call<Response> 則 第0個泛型是Response.class
         */
        protected static Type getParameterUpperBound(int index, ParameterizedType type) {
            return Utils.getParameterUpperBound(index, type);
        }

        /**
         * 獲取Type對應的Class
         * @param type
         * @return
         */
        protected static Class<?> getRawType(Type type) {
            return Utils.getRawType(type);
        }
    }
}

複製程式碼

在應用層我們可以實現一個NetCallAdapter,支援Call物件到 NetCall物件的轉換

package com.knight.sample;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;

/**
 * Created by zhuoxiuwu
 * on 2019/4/26
 * email nimdanoob@gmail.com
 */
package com.knight.sample;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;

/**
 * Created by zhuoxiuwu
 * on 2019/4/26
 * email nimdanoob@gmail.com
 */
public class NetCallAdapterFactory extends CallAdapter.Factory {

    /**
     * returnType引數 和 retroift引數 由底層框架傳遞給開發者
     * @param returnType
     * @param retrofit
     * @return
     */
    @Override
    public CallAdapter<?> get(final Type returnType, final Retrofit retrofit) {
        //判斷返回型別是否是 NetCall
        if (getRawType(returnType) != NetCall.class) {
            return null;
        }
        //要求開發者方法的返回型別必須寫成 NetCall<T> 或者NetCall<? extends Foo> 的形式,泛型內的型別就是Json資料對應的Class
        if (!(returnType instanceof ParameterizedType)) {
            throw new IllegalStateException(
                    "NetCall return type must be parameterized as NetCall<Foo> or NetCall<? extends Foo>");
        }
        final Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType);
        
        return new CallAdapter<NetCall>() {

            @Override
            public NetCall adapt(final Call call) {

                return new NetCall() {
                    @Override
                    public void execute(final NetCallback netCallback) {
                        call.enqueue(new Callback() {
                            @Override
                            public void onFailure(Call call, IOException e) {
                                netCallback.onFailure(e);
                            }

                            @Override
                            public void onResponse(Call call, Response response) throws IOException {
                                //由retrofit 提供 ResponseBody 到 某個Type Class的轉換
                                final Object value = retrofit.responseBodyTConverter(innerType).convert(response.body());
                                netCallback.onSuccess(value);
                            }
                        });
                    }
                };
            }
        };
    }
}
複製程式碼

Retrofit類中,我們新增了判斷方法返回型別的邏輯,如果發現方法的返回型別不是Call型別,則使用CallAdapter做轉換

package retrofit2;


import com.google.gson.Gson;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.http.GET;
import retrofit2.http.POST;

public class Retrofit {

    private OkHttpClient mOkHttpClient;

    private Converter.Factory mConverterFactory;

    private CallAdapter.Factory mCallAdapterFactory;

    public Retrofit(OkHttpClient mOkHttpClient, Converter.Factory mConverterFactory, CallAdapter.Factory callAdapterFactory) {
        this.mOkHttpClient = mOkHttpClient;
        this.mConverterFactory = mConverterFactory;
        this.mCallAdapterFactory = callAdapterFactory;
    }

    @SuppressWarnings("unchecked")
    public <T> T createService(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                                         Object[] args) throws Throwable {
                        //獲取方法所有的註解
                        final Annotation[] annotations = method.getAnnotations();
                        final Type returnType = method.getGenericReturnType();
                        for (int i = 0; i < annotations.length; i++) {
                            if (annotations[i] instanceof GET) { //如果註解是GET型別
                                final GET annotation = (GET) annotations[i];
                                return parseGet(annotation.value(), method, args);
                            } else if (annotations[i] instanceof POST) {
                                final POST annotation = (POST) annotations[i];
                                return parsePost(annotation.value(), method, args);
                            }
                        }
                        return null;
                    }
                });
    }


    private Object parseGet(String url, Method method, Object args[]) {
        final Request request = new Request.Builder()
                .url(url)
                .get().build();

        final Call call = mOkHttpClient.newCall(request);


        return adaptCall(method, call);
    }


    private Object parsePost(String url, Method method, Object args[]) {
        final Type[] genericParameterTypes = method.getGenericParameterTypes();
        if (genericParameterTypes.length > 0) {
            final RequestBody requestBody = requestBodyConverter(genericParameterTypes[0]).convert(args[0]);
            final Request request = new Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();
            final Call call = mOkHttpClient.newCall(request);

            return adaptCall(method, call);

        }
        return null;
    }

    /**
     * 負責 任意Java型別到 RequestBody的轉換
     * @param type
     * @param <T>
     * @return
     */
    public <T> Converter<T, RequestBody> requestBodyConverter(Type type) {
        return (Converter<T, RequestBody>) mConverterFactory.requestBodyConverter(type);
    }

    /**
     * 負責ResponseBody到Type型別的轉換
     * @param type
     * @param <T>
     * @return
     */
    public <T> Converter<ResponseBody,T> responseBodyTConverter(Type type){
        return (Converter<ResponseBody, T>) mConverterFactory.responseBodyConverter(type);
    }


    /**
     * 獲取方法的返回型別,並使用CallAdapter做型別轉換
     * @param method
     * @param call
     * @return
     */
    private Object adaptCall(Method method, Call call) {
        final Type returnType = method.getGenericReturnType();
        if (Utils.getRawType(returnType) != Call.class) {
            final CallAdapter<?> callAdapter = mCallAdapterFactory.get(returnType,this);
            return callAdapter.adapt(call);
        } else {
            return call;
        }
    }

}
複製程式碼

框架的後續實現及優化

到目前為止我們已經實現了一個簡單的retrofit框架,也許程式碼不夠精簡,邊界處理沒有十分嚴謹,但已經初具雛形。我們可以繼續思考現有專案的不足 新增更多的支援。

比如在網路請求方面目前只支援GET、POST,那麼我們後續需要新增更多請求方式的支援

在Retrofit物件的構造上,目前我們的建構函式傳入了3個物件,如果後續有更多的引數需要配置化,那麼我們可以使用 Builder設計模式來構建Retrofit

在CallAdapter的設計上,我們目前只支援傳入一個CallAdapterFactory,因此方法的返回型別除了原生的Call物件外 只支援應用開發者新增一個。實際上,這不太合理,因此這部分我們可以支援開發者傳入一個列表,在內部我們迭代這個List ,遍歷過程中呼叫CallAdapter<?> get(Type returnType,Retrofit retrofit);,如果這個Factory返回了null,則說明它對該型別不支援,則繼續呼叫下個CallFactory 直到找到合適的


    abstract class Factory {
        //如果返回了null 則該Factory不支援該returnType的轉換
        public abstract CallAdapter<?> get(Type returnType,Retrofit retrofit);
    }
複製程式碼

在框架的效能優化上,目前我們每次呼叫 createService(final Class service) 都是返回一個新的代理類,其實我們可以建立一個 Service類型別到該型別代理物件的Map快取,如果發現快取池有則直接複用該物件。更進一步的思考,我們是否可以以Method方法為緯度,每次呼叫一個抽象方法時我們解析該方法上的註解生成一個自己的ServiceMethod類物件並加到快取池中,下次呼叫同樣的方法時,我們就不需要解析註解了,而是直接使用內部的ServiceMethod

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
            Retrofit retrofit, Method method){

        //...解析method並生成一個HttpServiceMethod物件
        return null;
    }

    //框架內部表示一個Http方法呼叫的類
    class HttpServiceMethod<ResponseT, ReturnT>{
        //arguments
        //解析方法及註解並把資訊儲存下來,同樣的方法就不需要二次解析了


        //args為方法的引數,每次方法呼叫時只需要處理不同的引數
        //具體是POST 方法還是GET方法, callAdapter具體使用哪一個 都已經確定下來了
        final  ReturnT invoke(Object[] args) {
            return null;
        }
    }

複製程式碼

以上提出的一些優化點,大家可以自己先思考實現並重新閱讀寫Retrofit原始碼來加深自己的理解。從整個思考流程及實現上來看Retrofit的實現並不複雜,但是從實現一個簡單可用的網路封裝庫到實現一個擴充性強、職責分離的框架,中間的過程還是有很多細節的,如果你看完了這篇文章可以再抽1個小時左右的時間重新看下Retorift框架的原始碼相信從中還會有更多的收穫.

歡迎關注我的微信公眾號獲取更多技術文章

image

相關文章