細(xì)細(xì)品讀Retrofit的設(shè)計之美一

本篇文章已授權(quán)為微信公眾號 code小生
發(fā)布轉(zhuǎn)載請注明出處:http://www.itdecent.cn/p/2ff63eb587cf

1.細(xì)細(xì)品讀Retrofit的設(shè)計之美一
2. 細(xì)細(xì)品讀Retrofit的設(shè)計之美二


引言

Retrofit是這兩年比較流行的網(wǎng)絡(luò)通訊庫,之所以流行自然有它的優(yōu)點,首先是大廠Square公司開源之作,俗話說大廠出品,必須精品。
作為網(wǎng)絡(luò)通訊框架,基本的組成部分就是三大塊:1. 請求體部分。2. 響應(yīng)體部分。3. 與UI層回調(diào)部分。


Retrofit的使用這里就不細(xì)說了,我們從構(gòu)建Retrofit對象談起。

private Retrofit buildRetrofit(){
        return new Retrofit.Builder()
                .baseUrl(AppConst.BASE_URL)      // 設(shè)置基礎(chǔ)請求地址
                .client(buildHttpClient())       // 構(gòu)建自定義的httpClient 對象
                .addConverterFactory(FastJsonConverterFactory.create())  // 添加數(shù)據(jù)解析工廠
                .build();  // 構(gòu)建
    }

首先:構(gòu)建者Builder模式

整體看很明顯有個構(gòu)建者Builder模式,Builder模式在Android中是很常見的一種設(shè)計模式,它的優(yōu)點很明顯一般情況一個框架都是需要靈活自由的配置參數(shù)屬性的,如果不用Builder模式,都改成setter、getter,那初始化一個Retrofit對象就顯得復(fù)雜和臃腫了。而這里Builder模式加上鏈?zhǔn)秸{(diào)用方式,為Retrofit框架的參數(shù)配置增添了不少靈活和自由,而且代碼可讀性也增強了。
其實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); 
  }
  ....//  省略一大坨代碼, 其實就是上面參數(shù)屬性的一些獲取方法
  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的對象的構(gòu)建最終是在build()方法new出來返回。
Retrofit框架內(nèi)部有好多地方都用到了Builder模式,也是為了方便自由配置參數(shù)的。
Builder模式在Android開發(fā)中最常見的就是AlertDialog.Builder,可以自由的配置對話框的標(biāo)題、內(nèi)容、內(nèi)容設(shè)置來源、確認(rèn)取消等按鈕事件等等。有興趣的可以去了解下AlertDialog的源碼,基本也是上面模板的套路。

代理模式

構(gòu)建好Retrofit對象后,大家都知道這個框架網(wǎng)絡(luò)請求的通訊接口api都是Interface接口中聲明的,框架本身為了與網(wǎng)絡(luò)請求業(yè)務(wù)做解耦用了動態(tài)代理的方式,為業(yè)務(wù)通訊接口生成代理對象,當(dāng)代理對象調(diào)用業(yè)務(wù)接口方法api的時候,動態(tài)代理類就能監(jiān)測到并回調(diào),這時候就可以做網(wǎng)絡(luò)框架該有的功能:解析通訊業(yè)務(wù)接口,生成網(wǎng)絡(luò)請求體便于供應(yīng)給底層OkHttp做具體的網(wǎng)絡(luò)請求工作。其實就是框架本身沒有辦法直接使用業(yè)務(wù)接口,所以請了一個代理中介對象去間接的訪問業(yè)務(wù)接口,來完成相關(guān)的業(yè)務(wù)功能。

public ApiWrapper() {
        // 構(gòu)建生成retrofit對象
        Retrofit retrofit = buildRetrofit();
        // ApiService是網(wǎng)絡(luò)通過create方法創(chuàng)建出的代理對象mApiService
        mApiService = retrofit.create(ApiService.class);
}

來看看create方法是如何創(chuàng)建出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方法動態(tài)創(chuàng)建了代理對象,也就是上文create方法返回的mApiService對象。是不是很懵逼,為什么Proxy.newProxyInstance方法就能創(chuàng)建出代理對象,而且又正好是ApiService.class這個接口對象呢,是怎么創(chuàng)建出來的?帶著這些問題,我們來聊聊“代理模式”。

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

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

Jerry是個程序員年紀(jì)大了眼看就要30歲了還是單身,家里人非常的著急,于是找到了隔壁老王(老王認(rèn)識的妹紙比較多,為人熱情)叫著幫忙介紹妹子給Jerry認(rèn)識。

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

這里創(chuàng)建三個文件:目標(biāo)接口 IFindGirl、目標(biāo)類Jerry、代理類ProxyLaoWan

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

// Jerry.java
/**
 * 目標(biāo)對象:單身汪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();
// 創(chuàng)建代理人, 然后把Jerry委托給老王
ProxyLaoWan laoWan = new ProxyLaoWan(service);
// 老王幫Jerry去找妹子
laoWan.findGirl("Tom", 22);

這個例子中Jerry沒有直接去找妹子“Tom”,而是通過了老王,這是一個典型的靜態(tài)代理模式,Jerry把找妹子的事情委托代理給了老王,同樣Jerry如果還有其它的事情,比如買最新的腎phone手機,可是國行的很貴,剛好老王要去香港,又委托老王買港版的iPhone X,于是就要IService目標(biāo)接口中加入新的要干的事情buyPhone(),同樣老王的類、Jerry類都需要實現(xiàn)相應(yīng)的方法。如果Jerry不斷的有新的事情要做,新的功能要擴展那需要修改的地方就比較多了不利于項目的擴展和團隊開發(fā)。為此這樣的需求就產(chǎn)生了動態(tài)代理模式。
先來看看套路模板:

// 動態(tài)代理
    public void testDynamicProxy(){
        // 目標(biāo)對象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 {
                        // 調(diào)用目標(biāo)接口方法的時候,就調(diào)用invoke方法
                        long currentTime = System.currentTimeMillis();
                        Object returnValue = method.invoke(jerryService, args);
                        long calledMethodTime = System.currentTimeMillis();
                        long invokeMethodTime = calledMethodTime - currentTime;
                        // 接口方法的執(zhí)行時間,便于檢測性能
                        Log.e("InvocationHandler", "方法執(zhí)行性能時間:" + invokeMethodTime);
                        return returnValue;
                    }
                });

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

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

Jerry如果還要委托老王給買手機,只要給目標(biāo)接口加入buyPhone方法,然后Jerry實現(xiàn)這個方法,而代理者老王,不需要管都有什么具體的目標(biāo)接口,通過Proxy.newProxyInstance創(chuàng)建的代理對象,就可以調(diào)用目標(biāo)接口的方法。
介紹下Proxy.newProxyInstance方法:

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

第一個參數(shù):目標(biāo)對象的類加載器。因為這個代理對象是運行時才創(chuàng)建的,沒有編譯時候預(yù)先準(zhǔn)備的字節(jié)碼文件提供,所以需要一個類加載器來加載產(chǎn)生Proxy代理里的類類型,便于創(chuàng)建代理對象。
第二個參數(shù):interfaces是目標(biāo)接口數(shù)組
第三個參數(shù):是代理對象當(dāng)調(diào)用目標(biāo)接口方法的時候,會先回調(diào)InvocationHandler接口的實現(xiàn)方法invoke。

到目前為止還是看不出來Proxy.newProxyInstance是怎么給我們創(chuàng)建代理對象的,下面分析下它的源碼實現(xiàn):

動態(tài)代理的源碼實現(xiàn)

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作為參數(shù)的構(gòu)造方法
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            //  通過構(gòu)造方法對象創(chuàng)建一個代理對象
            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) {
            // 看最終的實現(xiàn)是Native方法,使用底層NDK來實現(xiàn)創(chuàng)建的代理實例對象
            return newInstance0(args);
        } else {
            return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
        }
    }
    
    // 底層NDK實現(xiàn)創(chuàng)建代理對象
    private static native Object newInstanceFromSerialization(Class<?> ctorClass, Class<?> allocClass)
        throws InstantiationException, IllegalArgumentException, InvocationTargetException;

    // 底層NDK實現(xiàn)創(chuàng)建代理對象
    private native T newInstance0(Object... args) throws InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}

從上面代碼中我們可以看出,最終的代理對象的創(chuàng)建是底層NDK來創(chuàng)建返回的,具體就不去看底層的實現(xiàn)了,大體了解到動態(tài)代理對象是通過這個構(gòu)造方法來創(chuàng)建的。

protected Proxy(InvocationHandler h) {
  this.h = h;
}

經(jīng)過上門對動態(tài)代理模式的一番學(xué)習(xí)和解釋,現(xiàn)在回過頭來看

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方法創(chuàng)建返回的正是ApiService接口的代理對象,每當(dāng)代理對象調(diào)用目標(biāo)接口里的方法時,動態(tài)代理對象就會回調(diào)InvocationHandler接口的invoke實現(xiàn)方法。
在Retrofit中,動態(tài)代理模式的角色劃分:
角色一:目標(biāo)接口(委托方法),ApiService接口方法
角色二:目標(biāo)對象(委托方),ApiService.class
角色三:代理對象,create創(chuàng)建的mApiService對象。

至此就把Retrofit中,動態(tài)代理業(yè)務(wù)的網(wǎng)絡(luò)通訊接口講清楚了,好處就是非入侵的方式,把網(wǎng)絡(luò)通訊api調(diào)用代理出來,然后在調(diào)用回調(diào)的invoke方法里統(tǒng)一處理和準(zhǔn)備網(wǎng)絡(luò)框架需要構(gòu)建的請求體,作為后續(xù)加入到請求隊列任務(wù)池中進行具體的網(wǎng)絡(luò)請求。動態(tài)代理模式也是AOP的一種實現(xiàn)方式,切片思想的一種。做過Java EE服務(wù)端開發(fā)的對于Spring的AOP應(yīng)該深有體會,動態(tài)代理與Annotation的結(jié)合真是完美,這一點在Retrofit的各種請求方式、參數(shù)、url路徑等等的注解就體現(xiàn)了。


文章有些長,這是第一篇,后面還會持續(xù)更新關(guān)于Retrofit的解讀,不單單是讓你懂的Retrofit的原理,還讓你學(xué)會感受設(shè)計模式的美妙。


我是JerryloveEmily,感謝您的閱讀,

喜歡就點個贊唄,“?喜歡”,

鼓勵又不花錢,您在看,我就繼續(xù)寫~

非簡書用戶,可以點右上角的三個“...”,然后"在Safari中打開”,就可以點贊咯~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容