Retrofit 源碼簡要解析(一) Retrofit是怎樣將接口轉(zhuǎn)變?yōu)轭悂硎褂玫?/h1>
image

大家好,我是Nixo,一枚剛畢業(yè)的Android小生贵试,這是我開坑源碼系列的第一篇文章,可能我的文章跟其他人不同攀操,我的文章并不像別人那樣的細枝末節(jié)所以我將本系列的文章稱為"簡要解析"主要是讓大家明白整個框架它的實現(xiàn)流程院仿,用了什么樣的技術(shù)去實現(xiàn)什么功能之所以用Retrofit作為我的第一個閱讀的框架是因為他的設(shè)計模式用的如火純情,非常值得我們學(xué)習(xí)速和。
因為本人技術(shù)有限歹垫,如果有不好的地方或者錯誤的地方希望大家指正。

Retrofit源碼簡要解析系列共有3篇文章加上番外篇2篇總共5篇 :

  • Retrofit源碼簡要解析(一)Retrofit是怎樣將接口轉(zhuǎn)變?yōu)轭悂硎褂玫?動態(tài)代理)

  • Retrofit源碼簡要解析(二)Retrofit是怎樣將接口抽象方法轉(zhuǎn)變?yōu)檎埱篌w的(注解處理器)

  • Retrofit源碼簡要解析(三)Retrofit是怎樣將返回體轉(zhuǎn)變?yōu)閷嶓w類的(反射颠放、CallAdapted排惨、CallAdapterFactory)

  • Retrofit源碼簡要解析番外(一)CallAdapterFactory對RxJava的支持

  • Retrofit源碼簡要解析番外(二)CallAdapterFactory對Kotlin協(xié)程的支持

今天我們來講一下Retrofit是怎么將接口轉(zhuǎn)為類來供我們使用的,其實這里說的不夠準(zhǔn)確碰凶,因為這個類已經(jīng)不是我們聲明的接口類了暮芭,可能我這么一說接口和類大家可能一時反應(yīng)不過來我貼一個代碼大家就會恍然大悟,原來你指的是這個欲低!

        //GitHubApi.class 就是我所述的接口辕宏,里面定義了Retrofit的網(wǎng)絡(luò)請求抽象方法
        //GitHubApi就是我所述的生成類
        GitHubApi gitHubApi = retrofit.create(GitHubApi.class);
        Call<List<GitHubApi.Contributors>> contributors =
                gitHubApi.contributors("square", "retrofit",new Date());

如上代碼Retrofit是怎么一番操作將我們的接口變?yōu)榫唧w的類呢?這里用到了Java的動態(tài)代理(Proxy)砾莱,帶著這個懸念我們點開create的方法瑞筐,走進Retrofit類當(dāng)中,點開源碼發(fā)現(xiàn)我們來到了Retrofit.java的第130行代碼如下:

public <T> T create(final Class<T> service) {
    //做一些安全判斷腊瑟,判斷是否為接口聚假,不是接口java原生的動態(tài)代理會報錯
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }

    //create其實就是通過Java的動態(tài)代理將接口中定義的方法轉(zhuǎn)給了InvocationHandler的invoke方法
    //在通過loadServiceMethod調(diào)用invoke,來發(fā)起網(wǎng)絡(luò)請求(下篇文章會講)
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }
            , new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method,
                                             @Nullable Object[] args) throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                //如果該方法是Object自帶的方法闰非,那么我們直接反射出來這些方法就可以了
                //比如說是.equest() 膘格、 toString()
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                // .isDefaultMethod是Java8的特性
                //它的意思是接口中定義的方法,因為java8支持了接口定義默認方法(default)這一特性
                //也就是說河胎,這里的if是判斷上述特性闯袒,如果是就直接調(diào)用
                //如果不是,就使用loadServiceMethod來獲取方法
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                //返回了 封裝了請求參數(shù)的一個接口游岳,它知道怎么發(fā)起網(wǎng)絡(luò)請求
                return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
              }
            });
  }

我們可以看到政敢,這里使用了Proxy.newProxyInstance 典型的動態(tài)代理,今天解析的重點就是這里胚迫,我們點開newProxyInstance 看看它到底是怎么將接口進行代理喷户,生成接口的代理類吧,點開后我們來到Proxy.java的719行

//newProxyInstance傳入了ClassLoader 還有接口访锻,我們可以想象得到
//他是通過ClassLoader來生成代理類的褪尝。
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ...
        /*
         * Look up or generate the designated proxy class.
            具體實現(xiàn)類代理的方法 getProxyClass0
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        return cons.newInstance(new Object[]{h});
       ...
    }

然后我們點開getProxyClass0來進一步探尋真正實現(xiàn)了代理的方法闹获,點開后來到了Proxy的410行,我們看到了如下的代碼:

   private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

在這里河哑,我們可以看到getProxyClass0返回了ProxyClassCache.get 避诽,我們思考一下,也就是說我們的Class是從proxyClassCache里獲取到的璃谨,距離真相并不遙遠沙庐,我們繼續(xù)往下點開ProxyClassCache我們可以看到proxyClassCache其實是一個 WeakCache,而WeakCache其實是ConcurrentMap佳吞,這里就不過多說明了拱雏,我們點開proxyClassCache后來到Proxy的239行

//注意我們這里的第二個參數(shù)ProxyClassFactory
  private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

是的,這個ProxyClassFactory就是我們的代理類的創(chuàng)造類了底扳,距離真想只有一步之遙铸抑,我們繼續(xù)點進ProxyClassFactory 為了大家方便理解,我這里將一些異常處理等非關(guān)鍵代碼進行了簡化處理衷模,大家可以對照源碼找到相應(yīng)的代碼來加深大家的理解鹊汛。

 private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        //生成的代理類的名字$proxy
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        //這里我也沒看懂,應(yīng)該是生成的第幾個代理類
        //這樣算芯,我們生成的代理類名字就是com.sun.proxy.$proxy0
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
          
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                   Class<?> interfaceClass = null柒昏;
                    //這里將我們的類名設(shè)置上去
                    interfaceClass = Class.forName(intf.getName(), false, loader);
            }
            //初始化包名
            String proxyPkg = null;     // package to define proxy class in
            //設(shè)置類的Modifier 我們的代理類為public final的類型
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                   //將方法設(shè)置為final的靜態(tài)方法
                    accessFlags = Modifier.FINAL;
                    //獲取接口名
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        //包名賦值
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }
            //設(shè)置代理類名 也就是com.sun.proxy.$proxy0
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
                構(gòu)建我們的代理類熙揍,這里返回byte數(shù)組也就是字節(jié)碼
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
           
            //通過defineClass0使用我們的classLoader將數(shù)組構(gòu)建成真正的class
           return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
    }

以上就是Retrofit的接口代理類實現(xiàn)的簡要流程了职祷,大家是不是還有些略有發(fā)懵?不要緊届囚,我們可以自己實現(xiàn)ProxyGenerator.generateProxyClass方法有梆,生成byte[]然后通過IO流寫出一個class文件,你會發(fā)現(xiàn)這正是我們接口的代理類意系,來加深我們對java動態(tài)代理的理解泥耀,最后return的defineClass0便是我們一開始構(gòu)造出的GitHub.class啦,本文只是簡要的概述了動態(tài)代理的流程蛔添,Retrofit當(dāng)然還做了很多東西痰催,本文并沒有講到,這個并不是作為簡要解析的重點迎瞧,重點是讓大家用短暫的時間理解著去看懂整個流程夸溶,而非細枝末節(jié)。
最后感謝您看完本篇文章凶硅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末缝裁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子足绅,更是在濱河造成了極大的恐慌捷绑,老刑警劉巖韩脑,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異粹污,居然都是意外死亡段多,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門壮吩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衩匣,“玉大人,你說我怎么就攤上這事粥航。” “怎么了生百?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵递雀,是天一觀的道長。 經(jīng)常有香客問我蚀浆,道長缀程,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任市俊,我火速辦了婚禮杨凑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摆昧。我一直安慰自己撩满,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布绅你。 她就那樣靜靜地躺著伺帘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忌锯。 梳的紋絲不亂的頭發(fā)上伪嫁,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機與錄音偶垮,去河邊找鬼张咳。 笑死,一個胖子當(dāng)著我的面吹牛似舵,可吹牛的內(nèi)容都是我干的脚猾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼啄枕,長吁一口氣:“原來是場噩夢啊……” “哼婚陪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起频祝,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤泌参,失蹤者是張志新(化名)和其女友劉穎脆淹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沽一,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡盖溺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铣缠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烘嘱。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蝗蛙,靈堂內(nèi)的尸體忽然破棺而出蝇庭,到底是詐尸還是另有隱情,我是刑警寧澤捡硅,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布哮内,位于F島的核電站,受9級特大地震影響壮韭,放射性物質(zhì)發(fā)生泄漏北发。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一喷屋、第九天 我趴在偏房一處隱蔽的房頂上張望琳拨。 院中可真熱鬧,春花似錦屯曹、人聲如沸狱庇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僵井。三九已至,卻和暖如春驳棱,著一層夾襖步出監(jiān)牢的瞬間批什,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工社搅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驻债,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓形葬,卻偏偏與公主長得像合呐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子笙以,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354