從架構(gòu)角度看Retrofit的作用贿讹、原理和啟示

Retrofit是squareup公司的開源力作吠裆,和同屬squareup公司開源的OkHttp伐谈,一個(gè)負(fù)責(zé)網(wǎng)絡(luò)調(diào)度烂完,一個(gè)負(fù)責(zé)網(wǎng)絡(luò)執(zhí)行,為Android開發(fā)者提供了即方便又高效的網(wǎng)絡(luò)訪問框架诵棵。

不過抠蚣,對(duì)于Retrofit這樣設(shè)計(jì)精妙、代碼簡(jiǎn)潔履澳、使用方便的優(yōu)秀開源項(xiàng)目嘶窄,不能僅知道如何擴(kuò)展和使用,或者僅研究它采用的技術(shù)或模式奇昙,“技”當(dāng)然重要护侮,但不能忽視了背后的“道”。

對(duì)于Retrofit储耐,我們還應(yīng)該看到的羊初,是她在優(yōu)化App架構(gòu)方面的努力,以及她在提升開發(fā)效率方面的借鑒和啟示什湘。

本文試圖通過一個(gè)具體場(chǎng)景长赞,先總結(jié)Retrofit在架構(gòu)中起到的作用,再分析其實(shí)現(xiàn)原理闽撤,最后探討Retrofit給我們帶來的啟示得哆。

我們先通過一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景來回顧Retrofit的使用過程。

基本場(chǎng)景

通常來說哟旗,使用Retrofit要經(jīng)過這樣幾個(gè)步驟

  1. 引用
    在gradle文件中引用retrofit
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:retrofit-converters:2.3.0'
    compile 'com.squareup.retrofit2:retrofit-adapters:2.3.0'

如果需要使用更多擴(kuò)展功能贩据,比如gson轉(zhuǎn)換,rxjava適配等闸餐,可以視自己需要繼續(xù)添加引用

    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

如果現(xiàn)有的擴(kuò)展包不能滿足需要饱亮,還可以自己擴(kuò)展converter,adapter等舍沙。

  1. 定義接口
    Retrofit要求定義一個(gè)網(wǎng)絡(luò)請(qǐng)求的接口近上,接口函數(shù)里要定義url路徑、請(qǐng)求參數(shù)拂铡、返回類型壹无。
public interface INetApiService {
    @GET("/demobiz/api.php")
    Call<BizEntity> getBizInfo(@Query("id") String id);
}

在這個(gè)接口定義中,用注解@GET("/demobiz/api.php")聲明了url路徑感帅,用注解@Query("id") 聲明了請(qǐng)求參數(shù)斗锭。
最重要的是,用Call<BizEntity>聲明了返回值是一個(gè)Retrofit的Call對(duì)象留瞳,并且聲明了這個(gè)對(duì)象處理的數(shù)據(jù)類型為BizEntity拒迅,BizEntity是我們自定義的數(shù)據(jù)模型。

  1. 依次獲得Retrofit對(duì)象、接口實(shí)例對(duì)象璧微、網(wǎng)絡(luò)工作對(duì)象
    首先作箍,需要新建一個(gè)retrofit對(duì)象。
    然后前硫,根據(jù)上一步的接口胞得,實(shí)現(xiàn)一個(gè)retrofit加工過的接口對(duì)象。
    最后屹电,調(diào)用接口函數(shù)阶剑,得到一個(gè)可以執(zhí)行網(wǎng)絡(luò)訪問的網(wǎng)絡(luò)工作對(duì)象。
//新建一個(gè)Retrofit對(duì)象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)//要訪問的網(wǎng)絡(luò)地址域名危号,如http://www.zhihu.com
.addConverterFactory(GsonConverterFactory.create())
.build();
...

//用retrofit加工出對(duì)應(yīng)的接口實(shí)例對(duì)象
INetApiService netApiService= retrofit.create(INetApiService.class);
//可以繼續(xù)加工出其他接口實(shí)例對(duì)象
IOtherService otherService= retrofit.create(IOtherService.class);
···

//調(diào)用接口函數(shù)牧愁,獲得網(wǎng)絡(luò)工作對(duì)象
Call<BizEntity> callWorker= netApiService.getBizInfo("id001");

這個(gè)復(fù)雜的過程下來,最終得到的callWorker對(duì)象外莲,才可以執(zhí)行網(wǎng)絡(luò)訪問猪半。

  1. 訪問網(wǎng)絡(luò)數(shù)據(jù)
    用上一步獲取的worker對(duì)象,執(zhí)行網(wǎng)絡(luò)請(qǐng)求
callWorker.enqueue(new Callback<BizEntity>() {
            @Override
            public void onResponse(Call<BizEntity> call, Response<BizEntity> response) {...}
            @Override
            public void onFailure(Call<BizEntity> call, Throwable t) {...}
        });

在回調(diào)函數(shù)里偷线,取得我們需要的BizEntity數(shù)據(jù)對(duì)象磨确。
網(wǎng)絡(luò)訪問結(jié)束。

角色與作用

我們從上面的應(yīng)用場(chǎng)景可以看出声邦,Retrofit并不做網(wǎng)絡(luò)請(qǐng)求乏奥,只是生成一個(gè)能做網(wǎng)絡(luò)請(qǐng)求的對(duì)象。
Retrofit的作用是按照接口去定制Call網(wǎng)絡(luò)工作對(duì)象

什么意思亥曹?就是說:
Retrofit不直接做網(wǎng)絡(luò)請(qǐng)求
Retrofit不直接做網(wǎng)絡(luò)請(qǐng)求
Retrofit不直接做網(wǎng)絡(luò)請(qǐng)求

重要的事情說三遍邓了。

網(wǎng)絡(luò)請(qǐng)求的目標(biāo)雖然是數(shù)據(jù),但是我們需要為這個(gè)數(shù)據(jù)寫大量的配套代碼媳瞪,發(fā)起請(qǐng)求的對(duì)象Call驶悟,接收數(shù)據(jù)的對(duì)象CallBack,做數(shù)據(jù)轉(zhuǎn)換的對(duì)象Converter材失,以及檢查和處理異常的對(duì)象等。
這對(duì)于一個(gè)項(xiàng)目的開發(fā)硫豆、擴(kuò)展和維護(hù)來說龙巨,都是成本和風(fēng)險(xiǎn)。

而Retrofit做的事情熊响,就是為開發(fā)者節(jié)省這部分的工作量旨别,Retrofit一方面從底層統(tǒng)一用OkHttp去做網(wǎng)絡(luò)處理;另一方面在外層靈活提供能直接融入業(yè)務(wù)邏輯的Call網(wǎng)絡(luò)訪問對(duì)象汗茄。

具體來說秸弛,Retrofit只負(fù)責(zé)生產(chǎn)對(duì)象,生產(chǎn)能做網(wǎng)絡(luò)請(qǐng)求的工作對(duì)象,他有點(diǎn)像一個(gè)工廠递览,只提供產(chǎn)品叼屠,工廠本身不處理網(wǎng)絡(luò)請(qǐng)求,產(chǎn)品才能處理網(wǎng)絡(luò)請(qǐng)求绞铃。
Retrofit在網(wǎng)絡(luò)請(qǐng)求中的作用大概可以這樣理解:


Retrofit的作用

我們看到镜雨,從一開始,Retrofit要提供的就是個(gè)Call工作對(duì)象儿捧。
換句話說荚坞,對(duì)于給Retrofit提供的那個(gè)接口

public interface INetApiService {
    @GET("/demobiz/api.php")
    Call<BizEntity> getBizInfo(@Query("id") String id);
}

這個(gè)接口并不是傳統(tǒng)意義上的網(wǎng)絡(luò)請(qǐng)求接口,這個(gè)接口不是用來獲取數(shù)據(jù)的接口菲盾,而是用來生產(chǎn)對(duì)象的接口颓影,這個(gè)接口相當(dāng)于一個(gè)工廠,接口中每個(gè)函數(shù)的返回值不是網(wǎng)絡(luò)數(shù)據(jù)懒鉴,而是一個(gè)能進(jìn)行網(wǎng)絡(luò)請(qǐng)求的工作對(duì)象诡挂,我們要先調(diào)用函數(shù)獲得工作對(duì)象,再用這個(gè)工作對(duì)象去請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)疗我。

所以Retrofit的實(shí)用價(jià)值意義在于咆畏,他能根據(jù)你的接口定義,靈活地生成對(duì)應(yīng)的網(wǎng)絡(luò)工作對(duì)象吴裤,然后你再擇機(jī)去調(diào)用這個(gè)對(duì)象訪問網(wǎng)絡(luò)旧找。
理解了這一點(diǎn),我們才能去擴(kuò)展Retrofit麦牺,并理解Retrofit的設(shè)計(jì)思想钮蛛。

功能擴(kuò)展

我們先來看Retrofit能擴(kuò)展哪些功能,然后再去理解Retrofit的工作原理剖膳。
Retrofit主要可以擴(kuò)展三個(gè)地方:

  1. OkHttpClient
    Retrofit使用OkHttpClient來實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求魏颓,這個(gè)OkHttpClient雖然不能替換為其他的網(wǎng)絡(luò)執(zhí)行框架比如Volley,但是Retrofit允許我們使用自己擴(kuò)展OkHttpClient吱晒,一般最常擴(kuò)展的就是Interceptor攔截器了
OkHttpClient mClient = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        try {
                            Request.Builder builder = chain.request().newBuilder();
                            builder.addHeader("Accept-Charset", "UTF-8");
                            builder.addHeader("Accept", " application/json");
                            builder.addHeader("Content-type", "application/json");
                            Request request = builder.build();
                            return chain.proceed(request);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                }).build();

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Config.DOMAIN)
                .addConverterFactory(GsonConverterFactory.create())
                .client(mClient)
                .build();
  1. addConverterFactory

擴(kuò)展的是對(duì)返回的數(shù)據(jù)類型的自動(dòng)轉(zhuǎn)換甸饱,把一種數(shù)據(jù)對(duì)象轉(zhuǎn)換為另一種數(shù)據(jù)對(duì)象。
在上述場(chǎng)景中仑濒,GsonConverterFactory可以把Http訪問得到的json字符串轉(zhuǎn)換為Java數(shù)據(jù)對(duì)象BizEntity叹话,這個(gè)BizEntity是在INetApiService接口中要求的的。
這種轉(zhuǎn)換我們自己也經(jīng)常做墩瞳,很好理解驼壶。
如果現(xiàn)有的擴(kuò)展包不能滿足需要,可以繼承Retrofit的接口喉酌。retrofit2.Converter<F,T>热凹,自己實(shí)現(xiàn)Converter和ConverterFactory泵喘。
在創(chuàng)建Retrofit對(duì)象時(shí),可以插入我們自定義的ConverterFactory般妙。

//retrofit對(duì)象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(YourConverterFactory.create())//添加自定義Converter
.build();
  1. addCallAdapterFactory

擴(kuò)展的是對(duì)網(wǎng)絡(luò)工作對(duì)象callWorker的自動(dòng)轉(zhuǎn)換纪铺,把Retrofit中執(zhí)行網(wǎng)絡(luò)請(qǐng)求的Call對(duì)象,轉(zhuǎn)換為接口中定義的Call對(duì)象股冗。
這個(gè)轉(zhuǎn)換不太好理解霹陡,我們可以對(duì)照下圖來理解:


callAdapter轉(zhuǎn)換Call對(duì)象

Retrofit本身用一個(gè)OkHttpCall的類負(fù)責(zé)處理網(wǎng)絡(luò)請(qǐng)求,而我們?cè)诮涌谥卸x需要定義很多種Call止状,例如Call<BizEntity>烹棉,或者Flowable<BizEntity>等,接口里的Call和Retrofit里的OkHttpCall并不一致怯疤,所以我們需要用一個(gè)CallAdapter去做一個(gè)適配轉(zhuǎn)換浆洗。
(Retrofit底層雖然使用了OkHttpClient去處理網(wǎng)絡(luò)請(qǐng)求,但她并沒有使用okhttp3.call這個(gè)Call接口集峦,而是自己又建了一個(gè)retrofit2.Call接口伏社,OkHttpCall繼承的是retrofit2.Call,與okhttp3.call只是引用關(guān)系塔淤。
這樣的設(shè)計(jì)符合依賴倒置原則摘昌,可以盡可能的與OkHttpClient解耦。)

這其實(shí)是Retrofit非常核心高蜂,也非常好用的一個(gè)設(shè)計(jì)聪黎,如果我們?cè)诮涌谥幸蟮暮瘮?shù)返回值是個(gè)RxJava的Flowable對(duì)象

public interface INetApiService {
    @GET("/demobiz/api.php")
    Flowable<BizEntity> getBizInfo(@Query("id") String id);
}

那么我們只需要為Retrofit添加對(duì)應(yīng)的擴(kuò)展

//retrofit對(duì)象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

就能得到Flowable類型的callWorker對(duì)象

//用retrofit加工出對(duì)應(yīng)的接口實(shí)例對(duì)象
INetApiService netApiService= retrofit.create(INetApiService.class);
···
//調(diào)用接口函數(shù),獲得網(wǎng)絡(luò)工作對(duì)象
Flowable<BizEntity> callWorker= netApiService.getBizInfo("id001");

在這里备恤,callAdapter做的事情就是把retrofit2.Call對(duì)象適配轉(zhuǎn)換為Flowable<T>對(duì)象稿饰。
同樣,如果現(xiàn)有的擴(kuò)展包不能滿足需要露泊,可以繼承Retrofit的接口retrofit2.CallAdapter<R,T>喉镰,自己實(shí)現(xiàn)CallAdapter和CallAdapterFactory。

Retrofit實(shí)現(xiàn)原理

Retrofit固然設(shè)計(jì)精妙惭笑,代碼簡(jiǎn)潔侣姆,使用方便,但相應(yīng)的沉噩,我們要理解Retrofit的實(shí)現(xiàn)原理也不太容易铺敌,這么精妙的設(shè)計(jì)是極佳的研究素材,我們不能僅僅停留在知道怎么使用屁擅,怎么擴(kuò)展的階段,那實(shí)在是對(duì)這個(gè)優(yōu)秀開源項(xiàng)目的浪費(fèi)产弹。
其實(shí)派歌,Retrofit使用的弯囊,就是動(dòng)態(tài)代理,方法注解胶果、建造者和適配器等成熟的技術(shù)或模式匾嘱,但是由于她的設(shè)計(jì)緊湊,而且動(dòng)態(tài)代理屏蔽了很多過程上的細(xì)節(jié)早抠,所以比較難以理解霎烙。

Retrofit實(shí)現(xiàn)原理——從動(dòng)態(tài)代理開始

從前面的使用場(chǎng)景可知,retrofit會(huì)生成一個(gè)接口實(shí)例蕊连。

//用retrofit加工出對(duì)應(yīng)的接口實(shí)例對(duì)象
INetApiService netApiService= retrofit.create(INetApiService.class);

到Retrofit源碼里看create函數(shù)悬垃,是一個(gè)動(dòng)態(tài)代理。

 public <T> T create(final Class<T> service) {
    ...
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            ...
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

要理解動(dòng)態(tài)代理甘苍,最好要看到動(dòng)態(tài)生成的代理類尝蠕。

由于動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成的代理類,用常規(guī)的反編譯方法無法查看载庭,一般要使用Java提供的sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces)函數(shù)生成代理類看彼,函數(shù)會(huì)返回byte[]字節(jié)碼,然后對(duì)字節(jié)碼反編譯得到Java代碼囚聚。
有一個(gè)小問題是靖榕,AndroidStudio并不提供sun.misc這個(gè)包,我們需要用IntelliJ或者Eclipse建立一個(gè)Java工程顽铸,在Java環(huán)境里調(diào)用這個(gè)函數(shù)茁计。

拿到的代理類,大概是這樣的:

public final class INetApiService extends Proxy implements INetApiService {
  ...//一些Object自帶方法
  private static Method m3;//接口定義的方法
  static {
    try {
      //Object自帶方法的初始化
      m0,m1,m2 = ...
      //接口中定義的方法
      m3 = Class.forName("com.demo.net$INetApiService")//反射接口類
          .getMethod("getBizInfo",//反射函數(shù)
              new Class[] { Class.forName("java.lang.String") });//反射參數(shù)
      //接口中定義的其他方法
      ...
    } 
    ...
  }
//返回接口實(shí)例對(duì)象
public INetApiService (InvocationHandler invocationHandler){
  super(invocationHandler);
}
//
public final Call getBizInfo(String str){
  ...
  try{//用Handler去調(diào)用
    return (Call)this.h.invoke(this, m3, new Object[]{str});
  }
}

}

我們可以看到跋破,代理類生成的是一個(gè)INetApiService接口的實(shí)例對(duì)象簸淀,該對(duì)象的getBizInfo函數(shù)返回的是接口中定義的Call網(wǎng)絡(luò)工作對(duì)象,這也體現(xiàn)了Retrofit的核心價(jià)值毒返,生成接口定義的Call網(wǎng)絡(luò)工作對(duì)象租幕。

那么,這個(gè)Call網(wǎng)絡(luò)工作對(duì)象是如何生成的呢拧簸,上面動(dòng)態(tài)代理生成的代碼是這樣的:

 return (Call)this.h.invoke(this, m3, new Object[]{str});

也就是說劲绪,這個(gè)Call網(wǎng)絡(luò)工作對(duì)象是在InvocationHandler中實(shí)現(xiàn)的,也就是在Retrofit.create函數(shù)中盆赤,由InvocationHandler實(shí)現(xiàn)的贾富。

這樣我們就明白了,Retrofit使用動(dòng)態(tài)代理牺六,其實(shí)是為了開發(fā)者在寫代碼時(shí)方便調(diào)用颤枪,而真正負(fù)責(zé)生產(chǎn)Call網(wǎng)絡(luò)工作對(duì)象的,還是Retrofit.create函數(shù)中定義的這個(gè)InvocationHandler淑际,這個(gè)InvocationHandler的代碼我們?cè)儋N一遍:

        new InvocationHandler() {
            ...
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }

ServiceMethod能讓我們準(zhǔn)確解析到INetApiService中定義的函數(shù)畏纲,為最后的適配轉(zhuǎn)換提供轉(zhuǎn)換目標(biāo)扇住,詳細(xì)分析我們后面再說,先看適配轉(zhuǎn)換的過程盗胀。

我們看到艘蹋,Retrofit內(nèi)部默認(rèn)使用OkHttpCall對(duì)象去處理網(wǎng)絡(luò)請(qǐng)求,但是返回的網(wǎng)絡(luò)工作對(duì)象是經(jīng)過適配器轉(zhuǎn)換的票灰,轉(zhuǎn)換成接口定義的那種Call網(wǎng)絡(luò)工作對(duì)象女阀。

這個(gè)適配轉(zhuǎn)換,就是Retrofit能按照接口去定制Call網(wǎng)絡(luò)工作對(duì)象的秘密屑迂。

Retrofit實(shí)現(xiàn)原理——適配轉(zhuǎn)換Call對(duì)象

我們?cè)诔跏蓟疪etrofit對(duì)象時(shí)浸策,好像不添加CallAdapterFactory也能實(shí)現(xiàn)適配轉(zhuǎn)換。

//retrofit對(duì)象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
//可以不添加CallAdapterFactory
.build();

這是怎么回事呢屈糊,我們知道Retrofit使用了建造者模式的榛,建造者模式的特定就是實(shí)現(xiàn)了建造和使用的分離,所以建造者模式的建造函數(shù)里逻锐,一般會(huì)有很復(fù)雜的對(duì)象創(chuàng)建和初始化過程夫晌,所以我們要看一下Retrofit的build函數(shù)。

public Retrofit build() {
      ...
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();//使用OkHttpClient處理網(wǎng)絡(luò)請(qǐng)求
      }
      ...
      //根據(jù)當(dāng)前運(yùn)行平臺(tái)昧诱,設(shè)置默認(rèn)的callAdapterFactory
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
      ...
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

這段代碼里晓淀,我們看到Retrofit使用OkHttpClient處理網(wǎng)絡(luò)請(qǐng)求,并且會(huì)添加默認(rèn)的callAdapterFactory盏档,這個(gè)platform是一個(gè)簡(jiǎn)單工廠凶掰,能根據(jù)當(dāng)前系統(tǒng)平臺(tái)去生成對(duì)應(yīng)的callAdapterFactory

  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();//根據(jù)當(dāng)前系統(tǒng)平臺(tái)返回相應(yīng)的對(duì)象
      }
    ...
  }
  ...
  static class Android extends Platform {
    ...
    @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
      if (callbackExecutor == null) throw new AssertionError();
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }
    ...
  }

這個(gè)Platform是Retrofit在Builder的構(gòu)造函數(shù)里初始化的。

所以蜈亩,在Retrofit.build()函數(shù)中懦窘,我們?yōu)镽etrofit默認(rèn)添加的callAdapterFactory,是在Platform中為Android系統(tǒng)設(shè)定的ExecutorCallAdapterFactory稚配。
我們看ExecutorCallAdapterFactory的代碼畅涂,這是一個(gè)工廠類,可以返回CallAdapter對(duì)象:

  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    return new CallAdapter<Object, Call<?>>() {
      ...
      //               轉(zhuǎn)換后              轉(zhuǎn)換前道川,也就是OkHttpCall
      @Override public Call<Object> adapt(Call<Object> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

在adapt函數(shù)中午衰,適配器會(huì)把Retrofit中用來訪問網(wǎng)絡(luò)的OkHttpCall,轉(zhuǎn)換為一個(gè)ExecutorCallbackCall(繼承了INetApiService接口里要求返回的網(wǎng)絡(luò)工作對(duì)象retrofit2.Call),
這個(gè)例子里面,由于OkHttpCall和ExecutorCallbackCall都實(shí)現(xiàn)了retrofit2.Call接口红伦,結(jié)果出現(xiàn)了從Call<Object>轉(zhuǎn)換為Call<Object>的情況,這可能不容易理解帅戒,我們換個(gè)RxJava2CallAdapterFactory來看看

  //RxJava2CallAdapterFactory中
  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
        isSingle, isMaybe, false);
}
  //RxJava2CallAdapter中
  //               轉(zhuǎn)換后        轉(zhuǎn)換前,也就是OkHttpCall
  @Override public Object adapt(Call<R> call) {
   ...
   Observable<?> observable;
   ...
   return observable;
  }

這個(gè)CallAdapter的轉(zhuǎn)換就比較明顯了崖技,把retrofit2.Call對(duì)象通過適配器轉(zhuǎn)換為了一個(gè)實(shí)為Observable<?>的Object對(duì)象逻住。

至此施流,我們可以理解Retrofit根據(jù)接口定義動(dòng)態(tài)生產(chǎn)Call網(wǎng)絡(luò)請(qǐng)求工作對(duì)象的原理了,其實(shí)就是通過適配器把retrofit2.Call對(duì)象轉(zhuǎn)換為目標(biāo)對(duì)象鄙信。

至于適配器轉(zhuǎn)換過程中,如何實(shí)現(xiàn)的對(duì)象轉(zhuǎn)換忿晕,就可以根據(jù)需求來自由實(shí)現(xiàn)了装诡,比如利用靜態(tài)代理等,如有必要践盼,我們可以自行開發(fā)擴(kuò)展鸦采,Retrofit框架并不限制我們對(duì)于適配器的實(shí)現(xiàn)方式。

Retrofit實(shí)現(xiàn)原理——函數(shù)解析咕幻、網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)轉(zhuǎn)換

在前面分析中渔伯,我們知道了Retrofit的整體工作流程,就是Retrofit用動(dòng)態(tài)代理生成Call網(wǎng)絡(luò)請(qǐng)求對(duì)象肄程,在這個(gè)過程中锣吼,用適配器把Retrofit底層的retrofit2.Call對(duì)象轉(zhuǎn)換為INetApiService中定義的Call網(wǎng)絡(luò)請(qǐng)求對(duì)象(如Flowable)。

問題是蓝厌,Retrofit具體是如何知道了INetApiService中定義的Call網(wǎng)絡(luò)請(qǐng)求對(duì)象玄叠,如何實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求,以及如何執(zhí)行的數(shù)據(jù)轉(zhuǎn)換呢拓提?

具體過程如下读恃;
首先,根據(jù)INetApiService中定義的函數(shù)代态,解析函數(shù)寺惫,得到函數(shù)的具體定義,并生成對(duì)應(yīng)的ServiceMethod蹦疑。
然后西雀,根據(jù)這個(gè)ServiceMethod,實(shí)現(xiàn)一個(gè)OkHttpCall的Call對(duì)象必尼,負(fù)責(zé)在Retrofit底層實(shí)現(xiàn)網(wǎng)絡(luò)訪問蒋搜。
其中,在網(wǎng)絡(luò)訪問返回了網(wǎng)絡(luò)數(shù)據(jù)時(shí)判莉,根據(jù)ServiceMethod實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換豆挽。
最后,利用上一小節(jié)中匹配的適配器券盅,把OkHttpCall對(duì)象轉(zhuǎn)換為INetApiService要求的Call網(wǎng)絡(luò)請(qǐng)求對(duì)象帮哈。

所以,我們要了解的就是函數(shù)解析锰镀、網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)轉(zhuǎn)換這三個(gè)動(dòng)作娘侍,至于最后的適配轉(zhuǎn)換咖刃,在上一節(jié)中已經(jīng)分析過了。

1. 函數(shù)解析
在接口函數(shù)里憾筏,用注解描述了輸入?yún)?shù)嚎杨,用Java對(duì)象定義了返回值類型,所以對(duì)輸入?yún)?shù)和返回值氧腰,ServiceMethod采取了不同的方式去處理枫浙。
輸入?yún)?shù)
輸入?yún)?shù)是用來描述url的,它的處理相對(duì)簡(jiǎn)單古拴,ServiceMethod會(huì)根據(jù)反射得到的Method箩帚,取得Annotation注解信息,這些注解是Retrofit自己預(yù)定義好的(retrofit2.http.*)黄痪,ServiceMethod根據(jù)預(yù)先的定義紧帕,直接判斷注解所屬的邏輯分支,在有網(wǎng)絡(luò)請(qǐng)求時(shí)分情況進(jìn)行處理桅打,就能得到目標(biāo)url是嗜,http請(qǐng)求頭等數(shù)據(jù)。
返回值
返回值是需要用CallAdapter去適配的油额,所以核心在于生成對(duì)應(yīng)的CallAdapter叠纷。
在Retrofit生成Call網(wǎng)絡(luò)工作對(duì)象時(shí),她通過動(dòng)態(tài)代理獲取到了接口函數(shù)的Method定義潦嘶,從這個(gè)Method中可以獲取函數(shù)定義的返回對(duì)象類型涩嚣,由于這個(gè)轉(zhuǎn)換是需要CallAdapterFactory生產(chǎn)CallAdapter對(duì)象去實(shí)現(xiàn),而Retrofit事先并不知道要使用哪個(gè)Factory掂僵,所以她是遍歷所有的CallAdapterFactory航厚,根據(jù)目標(biāo)函數(shù)的返回值類型,讓每個(gè)Factory都去嘗試生產(chǎn)一個(gè)CallAdapter锰蓬,哪個(gè)成功就用哪個(gè)幔睬。

2. 網(wǎng)絡(luò)請(qǐng)求
OkHttpCall繼承的retrofit2.Call接口是為了依賴倒置解耦的,真正的網(wǎng)絡(luò)請(qǐng)求是由OkHttpCall內(nèi)部引用的okhttp3.call處理的芹扭,這個(gè)okhttp3.call是
借道ServiceMethod獲取的Retrofit中的callFactory麻顶,也就是Retrofit中的OkHttpClient。

整個(gè)引用鏈條是這樣的:
OkHttpCall--okhttp3.call
-->
ServiceMethod--callFactory
-->
Retrofit.build()--callFactory//(如未擴(kuò)展賦值)new OkHttpClient();
-->
Retrofit.Builder().client(mClient)//(可能有擴(kuò)展賦值)擴(kuò)展過的OkHttpClient

最終的網(wǎng)絡(luò)請(qǐng)求是由OkHttpCall調(diào)用OkHttpClient發(fā)出的舱卡,調(diào)用和回調(diào)等過程辅肾,也就是在OkHttpCall中處理的。

網(wǎng)絡(luò)請(qǐng)求的生成過程中轮锥,為了使用接口函數(shù)中定義的參數(shù)矫钓,OkHttpCall會(huì)調(diào)用ServiceMethod來生成Request請(qǐng)求對(duì)象,再交給OkHttpCall去處理。

3. 數(shù)據(jù)轉(zhuǎn)換
因?yàn)榛卣{(diào)是在OkHttpCall中處理的新娜,所以對(duì)回調(diào)數(shù)據(jù)的轉(zhuǎn)換也在OkHttpCall中觸發(fā)赵辕,為了符合接口函數(shù)中定義的返回?cái)?shù)據(jù)類型,OkHttpCall會(huì)調(diào)用ServiceMethod來轉(zhuǎn)換Response返回?cái)?shù)據(jù)對(duì)象概龄。

OkHttpCall對(duì)返回的網(wǎng)絡(luò)數(shù)據(jù)还惠,會(huì)調(diào)用一個(gè)serviceMethod.toResponse(ResponseBody body)函數(shù),函數(shù)中執(zhí)行的是:

  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

這個(gè)函數(shù)可以把原始的okhttp3. ResponseBody數(shù)據(jù)轉(zhuǎn)換為INetApiService接口中要求的數(shù)據(jù)類型(如BizEntity類型)私杜。
從代碼可以看出吸重,實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換的核心對(duì)象其實(shí)是responseConverter,這個(gè)Converter實(shí)際上要依次經(jīng)過Retrofit的建造和ServiceMethod的建造后歪今,才能確定下來的。

Retrofit建造時(shí)添加數(shù)據(jù)轉(zhuǎn)換工廠
Retrofit里有converterFactries列表颜矿,這是在我們初始化Retrofit實(shí)例時(shí)添加的

//retrofit對(duì)象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(YourConverterFactory.create())//添加自定義Converter
.build();

ServiceMethod建造時(shí)設(shè)定數(shù)據(jù)轉(zhuǎn)換器
ServiceMethod在建造時(shí)寄猩,就已經(jīng)確定了對(duì)應(yīng)的是INetApiService中的哪個(gè)函數(shù),所以需要明確設(shè)定自己的Converter<R,T>轉(zhuǎn)換對(duì)象

  public ServiceMethod build() {
      ...
      responseConverter = createResponseConverter();
      ...
  }

這需要調(diào)用Retrofit

    private Converter<ResponseBody, T> createResponseConverter() {
      ...
      retrofit.responseBodyConverter(responseType, annotations);
    }

Retrofit會(huì)在自己的轉(zhuǎn)換器工廠列表中遍歷每個(gè)ConverterFactory骑疆,嘗試根據(jù)ServiceMethod所對(duì)應(yīng)的目標(biāo)數(shù)據(jù)類型田篇,找到Converter數(shù)據(jù)轉(zhuǎn)換類

    for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter<ResponseBody, ?> converter =
          converterFactories.get(i).responseBodyConverter(type, annotations, this);
      if (converter != null) {
        //noinspection unchecked
        return (Converter<ResponseBody, T>) converter;
      }
    }

以Gson轉(zhuǎn)換為例,GsonConverterFactory會(huì)通過getAdapter來嘗試匹配目標(biāo)數(shù)據(jù)類型:

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {...}

如果可以匹配箍铭,那么前面調(diào)用serviceMethod.toResponse(ResponseBody body)函數(shù)時(shí)泊柬,會(huì)調(diào)用

  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

在調(diào)用這段代碼時(shí),其實(shí)就是調(diào)用了Gson中最終執(zhí)行數(shù)據(jù)轉(zhuǎn)換的代碼:

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }

總結(jié)來說诈火,Retrofit在類的單一職責(zé)方面分隔的很好兽赁,OkHttpCall類只負(fù)責(zé)網(wǎng)絡(luò)交互,凡是需要知道函數(shù)定義的冷守,都交給ServiceMethod類去處理刀崖,而ServiceMethod類對(duì)使用者不公開,因?yàn)镽etrofit是個(gè)外觀模式拍摇,而所有需要擴(kuò)展的都在Retrofit的建造者中實(shí)現(xiàn)亮钦,他們的分工大概是這樣的:


三個(gè)類的分工

這三個(gè)類分工合作,共同實(shí)現(xiàn)了函數(shù)解析充活、網(wǎng)絡(luò)訪問和數(shù)據(jù)轉(zhuǎn)換蜂莉,并保留了良好的可擴(kuò)展性。

Retrofit實(shí)現(xiàn)原理——整體結(jié)構(gòu)與分工實(shí)現(xiàn)

至此混卵,Retrofit的實(shí)現(xiàn)細(xì)節(jié)就已經(jīng)基本清楚了映穗,他用動(dòng)態(tài)代理去定制接口定義的Call網(wǎng)絡(luò)工作對(duì)象,用適配器去把底層的Call對(duì)象轉(zhuǎn)換為目標(biāo)Call對(duì)象淮菠,用函數(shù)解析/OkHttpClient/數(shù)據(jù)轉(zhuǎn)換等實(shí)現(xiàn)對(duì)Call對(duì)象的適配轉(zhuǎn)換男公,并能處理真正的網(wǎng)絡(luò)請(qǐng)求。
這里面涉及的整體結(jié)構(gòu)和角色分工,大概可以這樣表示:


整體結(jié)構(gòu)與角色分工

其中枢赔,擴(kuò)展適配器澄阳、擴(kuò)展數(shù)據(jù)轉(zhuǎn)換和擴(kuò)展OkHttpClient,雖然都是通過Retrofit實(shí)現(xiàn)擴(kuò)展踏拜,但真正的使用者是Retrofit內(nèi)部的ServiceMethod碎赢、OkHttpCall和okhttp3.call等類或?qū)ο蟆?/p>

反推Retrofit的設(shè)計(jì)過程

如果我們不直接正面分析Retrofit的結(jié)構(gòu)設(shè)計(jì)和技術(shù)細(xì)節(jié),而是先從Retrofit的功能和作用入手速梗,倒過來推測(cè)Retrofit的目標(biāo)肮塞,進(jìn)而分析其架構(gòu)和搭建細(xì)節(jié),Retrofit為什么會(huì)設(shè)計(jì)成這樣就很好理解了姻锁。

Retrofit的功能是按照接口定義枕赵,自動(dòng)定制Call網(wǎng)絡(luò)工作對(duì)象,所以Retrofit的目標(biāo)應(yīng)該就是避免為網(wǎng)絡(luò)訪問開發(fā)大量的配套代碼位隶。

為了實(shí)現(xiàn)這一目標(biāo)拷窜,Retrofit需要分析哪些是易變的,哪些是不變的涧黄,然后分別處理篮昧。

由于Retrofit提供網(wǎng)絡(luò)訪問的工作對(duì)象,又是服務(wù)于具體業(yè)務(wù)笋妥,所以可以分網(wǎng)絡(luò)訪問和具體業(yè)務(wù)兩部分來分析懊昨。

網(wǎng)絡(luò)訪問的不變性
對(duì)于網(wǎng)絡(luò)訪問來說,不變的是一定有一個(gè)實(shí)現(xiàn)網(wǎng)絡(luò)訪問的對(duì)象春宣,Retrofit選用了自家的OkHttpClient酵颁,不過為了把Retrofit和OkHttp兩個(gè)項(xiàng)目解耦合,Retrofit根據(jù)依賴倒置原則月帝,定義了Retrofit自己的Call即retrofit2.call材义,并定義了操作網(wǎng)絡(luò)請(qǐng)求的OkHttpCall

網(wǎng)絡(luò)訪問的易變性
對(duì)于網(wǎng)絡(luò)訪問來說嫁赏,易變的是網(wǎng)絡(luò)訪問的url其掂、請(qǐng)求方式(get/post等)、Http請(qǐng)求的Header設(shè)置與安全設(shè)置等潦蝇,以及返回的數(shù)據(jù)類型款熬。

針對(duì)易變的url和請(qǐng)求方式,Retrofit使用了方法注解的方式攘乒,可讀性良好贤牛,擴(kuò)展性優(yōu)異,但這需要實(shí)現(xiàn)對(duì)接口函數(shù)中注解的解析则酝,這樣就有了ServiceMethod殉簸。
針對(duì)Http請(qǐng)求的各種設(shè)置,其實(shí)Retrofit沒做什么,因?yàn)镽etrofit使用的OkHttp有攔截器機(jī)制般卑,可以應(yīng)付這種變化武鲁。
針對(duì)返回的數(shù)據(jù)類型,由于目標(biāo)數(shù)據(jù)類型與業(yè)務(wù)有關(guān)蝠检,是不確定的沐鼠,Retrofit無法提供一個(gè)萬能的轉(zhuǎn)換類,所以Retrofit提供了擴(kuò)展接口叹谁,允許開發(fā)者自己定義ConverterFactory和Converter饲梭,去實(shí)現(xiàn)潛在的數(shù)據(jù)類型轉(zhuǎn)換。

具體業(yè)務(wù)的不變性
對(duì)于具體業(yè)務(wù)來說焰檩,不變的是一定要有一個(gè)Call網(wǎng)絡(luò)工作對(duì)象憔涉,所以Retrofit可以有一個(gè)生產(chǎn)對(duì)象的機(jī)制(像工廠一樣)

具體業(yè)務(wù)的易變性
對(duì)于具體業(yè)務(wù)來說,易變的就是這個(gè)Call網(wǎng)絡(luò)工作對(duì)象的類型析苫,不僅有CallBacl回調(diào)监氢、可能還有Flowable工作流、或者其他潛在的對(duì)象類型藤违。

針對(duì)這種Call對(duì)象的易變性,Retrofit也是無法提供一個(gè)萬能的實(shí)現(xiàn)類纵揍,所以也是提供了擴(kuò)展解耦顿乒,允許開發(fā)者自己定義CallAdapterFactory和CallAdapter,去實(shí)現(xiàn)潛在的Call類型轉(zhuǎn)換泽谨。

因?yàn)檫@種Call對(duì)象的生產(chǎn)需要有大量的配套代碼璧榄,為了簡(jiǎn)化代碼,Retrofit使用動(dòng)態(tài)代理來生產(chǎn)這個(gè)對(duì)象吧雹。

最后骨杂,因?yàn)樾枰幚淼姆椒ê蛯?duì)象太多太復(fù)雜,需要使用建造者模式來把建造過程和使用過程分離開雄卷。

這樣倒著走一遍之后搓蚪,我們?cè)倏碦etrofit的設(shè)計(jì)和實(shí)現(xiàn)原理,就會(huì)覺得水到渠成丁鹉,對(duì)于Retrofit精妙的設(shè)計(jì)更會(huì)有一種切身體會(huì)妒潭。

借鑒與啟示

在上文的反推過程中,我們可窺見(瞎猜)Jake大神的一些思路:

  1. 萬物皆對(duì)象
    網(wǎng)絡(luò)訪問后揣钦,回調(diào)數(shù)據(jù)是個(gè)對(duì)象雳灾;網(wǎng)絡(luò)訪問本身也是個(gè)對(duì)象。
  2. 依賴倒置
    哪怕是使用自家的OkHttp冯凹,哪怕底層調(diào)用的始終是OkHttpClient谎亩,也需要依賴一個(gè)抽象的retrofit2.Call接口,依賴于抽象,而不是依賴于具體匈庭。
  3. 單一職責(zé)
    類的職責(zé)需要維持單一夫凸,流程需要但是超出自己職責(zé)的功能,去調(diào)用相關(guān)的類實(shí)現(xiàn)嚎花,比如OkHttpClient和ServiceMethod的各自職責(zé)與調(diào)用關(guān)系寸痢。
  4. 迪米特法則
    內(nèi)部實(shí)現(xiàn)再復(fù)雜,對(duì)于外部調(diào)用者也只展示他需要的那些功能紊选,例如Retrofit啼止。
  5. 自動(dòng)>人工
    動(dòng)態(tài)代理的使用,可以用自動(dòng)生成的模板代碼兵罢,減輕人工編寫配套代碼的工作量献烦,成本更低,風(fēng)險(xiǎn)更低卖词。
  6. 利用工廠類開放擴(kuò)展
    對(duì)于流程確定巩那,但方法不能確定的,利用工廠類此蜈,對(duì)調(diào)用者開放擴(kuò)展能力即横。
  7. 利用多個(gè)工廠類組成擴(kuò)展列表
    如果1個(gè)工廠類不能實(shí)現(xiàn)兼得,何不設(shè)置一個(gè)工廠類列表裆赵,在多個(gè)工廠類中东囚,看哪個(gè)工廠類能解決問題。
  8. 利用建造者模式把建造和使用分離
    這樣使用者不需要關(guān)系復(fù)雜的建造過程战授,例如Retrofit和ServiceMethod页藻。
  9. 利用外觀模式減少對(duì)復(fù)雜子系統(tǒng)的操作
    雖然有復(fù)雜的子系統(tǒng)協(xié)同工作,調(diào)用者只需要調(diào)用最外層的Retrofit即可植兰。
  10. 其他
    開放封閉份帐、接口隔離、里式替換楣导、靜態(tài)代理等設(shè)計(jì)原則或設(shè)計(jì)模式都有體現(xiàn)也都很熟悉了废境,就不再啰嗦。

最后感嘆一下筒繁。

對(duì)于網(wǎng)絡(luò)訪問的抽象與優(yōu)化彬坏,實(shí)際上是個(gè)非常難的課題,在Retrofit之前膝晾,大家努力的方向基本上都是Volley/OkHttp這種圍繞底層網(wǎng)絡(luò)訪問的工作栓始。
因?yàn)樵降讓拥臇|西越容易抽象,越上升到接近業(yè)務(wù)層血当,就越容易在紛擾的業(yè)務(wù)層中迷失幻赚。
Retrofit能精準(zhǔn)地抓到Call網(wǎng)絡(luò)工作對(duì)象這個(gè)關(guān)鍵點(diǎn)禀忆,并能通過一系列精巧的設(shè)計(jì)實(shí)現(xiàn)對(duì)這種類型“飄忽不定”的對(duì)象的自動(dòng)化定制生產(chǎn),著實(shí)令人贊嘆落恼。

參考

Retrofit
你真的會(huì)用Retrofit2嗎?Retrofit2完全教程
Retrofit2 源碼解析
Retrofit 框架源碼學(xué)習(xí)
拆輪子系列:拆 Retrofit
Android 動(dòng)態(tài)代理以及利用動(dòng)態(tài)代理實(shí)現(xiàn) ServiceHook

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末箩退,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子佳谦,更是在濱河造成了極大的恐慌戴涝,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钻蔑,死亡現(xiàn)場(chǎng)離奇詭異啥刻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咪笑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門可帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窗怒,你說我怎么就攤上這事映跟。” “怎么了扬虚?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵努隙,是天一觀的道長。 經(jīng)常有香客問我辜昵,道長荸镊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任路鹰,我火速辦了婚禮,結(jié)果婚禮上收厨,老公的妹妹穿的比我還像新娘晋柱。我一直安慰自己,他們只是感情好诵叁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布雁竞。 她就那樣靜靜地躺著,像睡著了一般拧额。 火紅的嫁衣襯著肌膚如雪碑诉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天侥锦,我揣著相機(jī)與錄音进栽,去河邊找鬼。 笑死恭垦,一個(gè)胖子當(dāng)著我的面吹牛快毛,可吹牛的內(nèi)容都是我干的格嗅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼唠帝,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼屯掖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起襟衰,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤贴铜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瀑晒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绍坝,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年瑰妄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陷嘴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡间坐,死狀恐怖灾挨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竹宋,我是刑警寧澤劳澄,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站蜈七,受9級(jí)特大地震影響秒拔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜飒硅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一砂缩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧三娩,春花似錦庵芭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至会前,卻和暖如春好乐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓦宜。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工蔚万, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人临庇。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓笛坦,卻偏偏與公主長得像区转,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子版扩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 在Android開發(fā)中废离,網(wǎng)絡(luò)請(qǐng)求十分常用 而在Android網(wǎng)絡(luò)請(qǐng)求庫中,Retrofit是當(dāng)下最熱的一個(gè)網(wǎng)...
    Carson帶你學(xué)安卓閱讀 70,604評(píng)論 48 393
  • 適配器模式上一篇文章我們已經(jīng)分析了Retrofit解析注解封裝進(jìn)ServiceMethod的流程礁芦,讀者在這里要記住...
    andcoder閱讀 643評(píng)論 0 2
  • Retrofit這個(gè)開源庫出來也有一定年頭了蜻韭,記得之前還是在V1.0的版本的時(shí)候,之前在三月份也寫過一個(gè)Retro...
    lovejjfg閱讀 1,427評(píng)論 0 5
  • 如果你還沒了解Retrofit如何使用柿扣,可以先查看這篇文章:Retrofit使用指南 一般分析源碼都習(xí)慣從使用開始...
    一只小雞仔閱讀 721評(píng)論 1 4
  • 《法句經(jīng)》204樂品:“無病最上利肖方,知足最上財(cái),信賴最上親未状,涅盤最上樂俯画。” 簡(jiǎn)白的譯文是:健康是最大的利益司草,滿足是...
    兂吢菿処禪閱讀 1,867評(píng)論 0 0