細細品讀Retrofit的設計之美一

Retrofit是這兩年比較流行的網絡通信庫,之因此流行天然有它的優勢,首先是大廠Square公司開源之做,俗話說大廠出品,必須精品。
做爲網絡通信框架,基本的組成部分就是三大塊:1. 請求體部分。2. 響應體部分。3. 與UI層回調部分。java


Retrofit的使用這裏就不細說了,咱們從構建Retrofit對象談起。程序員

private Retrofit buildRetrofit(){
        return new Retrofit.Builder()
                .baseUrl(AppConst.BASE_URL)      // 設置基礎請求地址
                .client(buildHttpClient())       // 構建自定義的httpClient 對象
                .addConverterFactory(FastJsonConverterFactory.create())  // 添加數據解析工廠
                .build();  // 構建
    }複製代碼

首先:構建者Builder模式

總體看很明顯有個構建者Builder模式,Builder模式在Android中是很常見的一種設計模式,它的優勢很明顯通常狀況一個框架都是須要靈活自由的配置參數屬性的,若是不用Builder模式,都改爲setter、getter,那初始化一個Retrofit對象就顯得複雜和臃腫了。而這裏Builder模式加上鍊式調用方式,爲Retrofit框架的參數配置增添了很多靈活和自由,並且代碼可讀性也加強了。
其實Builder模式的套路很簡單,下面來個簡單的僞代碼Builder模式:設計模式

// Builder模式的套路模板
public class Retrofit{
    final HttpUrl baseUrl;
    final List<Converter.Factory> converterFactories;
    ....// 省略一大坨代碼

    Retrofit(HttpUrl baseUrl, List<Converter.Factory> converterFactories) {
        this.baseUrl = baseUrl;
        this.converterFactories = unmodifiableList(converterFactories); 
    }
    ....// 省略一大坨代碼, 其實就是上面參數屬性的一些獲取方法
    public HttpUrl baseUrl() {
      return baseUrl;
    }

    public static final class Builder{
        private HttpUrl baseUrl;
        private final List<Converter.Factory> converterFactories = new ArrayList<>();

        public Builder baseUrl(HttpUrl baseUrl) {
          // ... 省去部分代碼
          this.baseUrl = baseUrl;
          return this;
        }

        public Builder addConverterFactory(Converter.Factory factory) {
          converterFactories.add(checkNotNull(factory, "factory == null"));
          return this;
        }

        public Retrofit build() {
          // Make a defensive copy of the converters.
          List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

          return new Retrofit(baseUrl, converterFactories);
        }
    }
}複製代碼

以上就是Builder模式的套路模板,外部Retrofit的對象的構建最終是在build()方法new出來返回。
Retrofit框架內部有好多地方都用到了Builder模式,也是爲了方便自由配置參數的。
Builder模式在Android開發中最多見的就是AlertDialog.Builder,能夠自由的配置對話框的標題、內容、內容設置來源、確認取消等按鈕事件等等。有興趣的能夠去了解下AlertDialog的源碼,基本也是上面模板的套路。api

代理模式

構建好Retrofit對象後,你們都知道這個框架網絡請求的通信接口api都是Interface接口中聲明的,框架自己爲了與網絡請求業務作解耦用了動態代理的方式,爲業務通信接口生成代理對象,當代理對象調用業務接口方法api的時候,動態代理類就能監測到並回調,這時候就能夠作網絡框架該有的功能:解析通信業務接口,生成網絡請求體便於供應給底層OkHttp作具體的網絡請求工做。其實就是框架自己沒有辦法直接使用業務接口,因此請了一個代理中介對象去間接的訪問業務接口,來完成相關的業務功能。數組

public ApiWrapper() {
        // 構建生成retrofit對象
        Retrofit retrofit = buildRetrofit();
        // ApiService是網絡經過create方法建立出的代理對象mApiService
        mApiService = retrofit.create(ApiService.class);
}複製代碼

來看看create方法是如何建立出ApiService接口的對象的。網絡

public <T> T create(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 {
           }
    }
}複製代碼

經過Proxy.newProxyInstance方法動態建立了代理對象,也就是上文create方法返回的mApiService對象。是否是很懵逼,爲何Proxy.newProxyInstance方法就能建立出代理對象,並且又正好是ApiService.class這個接口對象呢,是怎麼建立出來的?帶着這些問題,咱們來聊聊「代理模式」。app

要搞清楚上面的這些問題,就得明白代理模式的套路,最重要就是區分清楚角色:
角色一:目標接口
角色二:目標對象
角色三:代理對象框架

這裏先舉個簡單的例子,有個這樣的場景:ide

Jerry是個程序員年紀大了眼看就要30歲了仍是單身,家裏人很是的着急,因而找到了隔壁老王(老王認識的妹紙比較多,爲人熱情)叫着幫忙介紹妹子給Jerry認識。性能

上面這個場景來區分下角色:
角色一:目標接口,大齡單身汪找妹子(要乾的事情)
角色二:目標對象,單身程序員Jerry(須要找妹子的目標人物)
角色三:代理對象,皮條客隔壁老王(表明Jerry去找妹子)

這裏建立三個文件:目標接口 IFindGirl、目標類Jerry、代理類ProxyLaoWan

// IService.java
/** * 目標接口 * Created by Administrator on 2017/9/17 0017. */
public interface IService {
    /** * 找妹子 * @param name 名字 * @param age 年齡 */
    void findGirl(String name, int age);
}

// Jerry.java
/** * 目標對象:單身汪Jerry * Created by Administrator on 2017/9/17 0017. */
public class Jerry implements IService {
    private static final String TAG = "Jerry";

    @Override
    public void findGirl(String name, int age) {
        Log.e(TAG, name + " 說願意作Jerry的女友");
    }
}

// ProxyLaoWan.java
/** * 代理對象:找對象的代理人老王 * Created by Administrator on 2017/9/17 0017. */
public class ProxyLaoWan implements IService {

    private IService service;

    public ProxyLaoWan(IService service) {
        this.service = service;
    }

    @Override
    public void findGirl(String name, int age) {
        // 老王找到妹子後,再這告訴Jerry
        service.findGirl(name, age);
    }
}複製代碼

使用的時候很簡單:

// jerry
IService service = new Jerry();
// 建立代理人, 而後把Jerry委託給老王
ProxyLaoWan laoWan = new ProxyLaoWan(service);
// 老王幫Jerry去找妹子
laoWan.findGirl("Tom", 22);複製代碼

這個例子中Jerry沒有直接去找妹子「Tom」,而是經過了老王,這是一個典型的靜態代理模式,Jerry把找妹子的事情委託代理給了老王,一樣Jerry若是還有其它的事情,好比買最新的腎phone手機,但是國行的很貴,恰好老王要去香港,又委託老王買港版的iPhone X,因而就要IService目標接口中加入新的要乾的事情buyPhone(),一樣老王的類、Jerry類都須要實現相應的方法。若是Jerry不斷的有新的事情要作,新的功能要擴展那須要修改的地方就比較多了不利於項目的擴展和團隊開發。爲此這樣的需求就產生了動態代理模式。
先來看看套路模板:

// 動態代理
    public void testDynamicProxy(){
        // 目標對象jerry
        final IService jerryService = new Jerry();
        // 代理對象老王
        IService proxyLaoWan = (IService) Proxy.newProxyInstance(
                jerryService.getClass().getClassLoader(),
                jerryService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 調用目標接口方法的時候,就調用invoke方法
                        long currentTime = System.currentTimeMillis();
                        Object returnValue = method.invoke(jerryService, args);
                        long calledMethodTime = System.currentTimeMillis();
                        long invokeMethodTime = calledMethodTime - currentTime;
                        // 接口方法的執行時間,便於檢測性能
                        Log.e("InvocationHandler", "方法執行性能時間:" + invokeMethodTime);
                        return returnValue;
                    }
                });

        // 老王幫忙找妹子,妹子叫Tom 22歲
        proxyLaoWan.findGirl("Tom", 22);

        // 老王幫Jerry買了價值8288元的iPhone X手機
        proxyLaoWan.buyPhone("iPhone X", 8288);
    }複製代碼

Jerry若是還要委託老王給買手機,只要給目標接口加入buyPhone方法,而後Jerry實現這個方法,而代理者老王,不須要管都有什麼具體的目標接口,經過Proxy.newProxyInstance建立的代理對象,就能夠調用目標接口的方法。
介紹下Proxy.newProxyInstance方法:

// Proxy.java
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);複製代碼

第一個參數:目標對象的類加載器。由於這個代理對象是運行時才建立的,沒有編譯時候預先準備的字節碼文件提供,因此須要一個類加載器來加載產生Proxy代理裏的類類型,便於建立代理對象。
第二個參數:interfaces是目標接口數組
第三個參數:是代理對象當調用目標接口方法的時候,會先回調InvocationHandler接口的實現方法invoke。

到目前爲止仍是看不出來Proxy.newProxyInstance是怎麼給咱們建立代理對象的,下面分析下它的源碼實現:

動態代理的源碼實現

class Proxy{
  private final static Class[] constructorParams = { InvocationHandler.class };

  public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        if (h == null) {  throw new NullPointerException(); }
        // 獲取到Proxy類的 類類型Class
        Class<?> cl = getProxyClass0(loader, interfaces);
        // 經過Proxy類的類類型對象獲取InvocationHandler做爲參數的構造方法
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            // 經過構造方法對象建立一個代理對象
            return newInstance(cons, h);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        return cons.newInstance(new Object[] {h} );
    }
}

// Constructor.java
class Constructor{
  public T newInstance(Object... args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (serializationClass == null) {
            // 看最終的實現是Native方法,使用底層NDK來實現建立的代理實例對象
            return newInstance0(args);
        } else {
            return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
        }
    }

    // 底層NDK實現建立代理對象
    private static native Object newInstanceFromSerialization(Class<?> ctorClass, Class<?> allocClass) throws InstantiationException, IllegalArgumentException, InvocationTargetException;

    // 底層NDK實現建立代理對象
    private native T newInstance0(Object... args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}複製代碼

從上面代碼中咱們能夠看出,最終的代理對象的建立是底層NDK來建立返回的,具體就不去看底層的實現了,大致瞭解到動態代理對象是經過這個構造方法來建立的。

protected Proxy(InvocationHandler h) {
  this.h = h;
}複製代碼

通過上門對動態代理模式的一番學習和解釋,如今回過頭來看

mApiService = retrofit.create(ApiService.class);

public <T> T create(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 {
           }
    }
}複製代碼

create方法建立返回的正是ApiService接口的代理對象,每當代理對象調用目標接口裏的方法時,動態代理對象就會回調InvocationHandler接口的invoke實現方法。
在Retrofit中,動態代理模式的角色劃分:
角色一:目標接口(委託方法),ApiService接口方法
角色二:目標對象(委託方),ApiService.class
角色三:代理對象,create建立的mApiService對象。

至此就把Retrofit中,動態代理業務的網絡通信接口講清楚了,好處就是非入侵的方式,把網絡通信api調用代理出來,而後在調用回調的invoke方法裏統一處理和準備網絡框架須要構建的請求體,做爲後續加入到請求隊列任務池中進行具體的網絡請求。動態代理模式也是AOP的一種實現方式,切片思想的一種。作過Java EE服務端開發的對於Spring的AOP應該深有體會,動態代理與Annotation的結合真是完美,這一點在Retrofit的各類請求方式、參數、url路徑等等的註解就體現了。


文章有些長,這是第一篇,後面還會持續更新關於Retrofit的解讀,不僅僅是讓你懂的Retrofit的原理,還讓你學會感覺設計模式的美妙。感謝你的閱讀。

相關文章
相關標籤/搜索