Android 網(wǎng)絡框架 Retrofit 源碼解析

在之前的文章 《Andriod 網(wǎng)絡框架 OkHttp 源碼解析》 中我們分析了 OkHttp 的源代碼。現(xiàn)在我們就來分析一下 OkHttp 的兄弟框架 Retrofit居灯。關于 Retrofit 的注解的使用德挣,可以參考其官方文檔:https://square.github.io/retrofit/不撑。

Retrofit 也是 Square 發(fā)布的一個開源的庫,它是一個類型安全的 Http 客戶端邻吭,適用于 Android 和 Java餐弱。本質上,Retrofit 使用了 Java 的動態(tài)代理囱晴,內部使用 OkHttp 來進行網(wǎng)絡訪問膏蚓,并且可以通過指定 “請求適配器” 和 “類型轉換器” 來完成:請求的適配,方法參數(shù)到 OkHttp 請求的轉換畸写,以及響應到 Java 類型的轉換驮瞧。

1、基本使用

Retrofit 設計的一個好的地方就是它把我們上面提到的 “請求適配器” 和 “類型轉換器” 使用策略模式解耦出來枯芬。用戶可以根據(jù)自己的需求通過實現(xiàn)指定的接口來自定義自己的類型轉換器论笔。所以,當我們使用 Gson 進行轉換和 RxJava2 進行適配的時候千所,就需要指定下面三個依賴:

api 'com.squareup.retrofit2:retrofit:2.4.0'
api 'com.squareup.retrofit2:converter-gson:2.4.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'

然后狂魔,我們需要根據(jù)自己的 API 接口的信息,在代碼里用一個接口來對該 API 進行聲明:

public interface WXInfoService {
    @GET("/sns/userinfo")
    Observable<WXUserInfo> getWXUserInfo(
        @Query("access_token") String accessToken, @Query("openid") String openId);
}

這里的 WXUserInfo 是由該 API 接口返回的 Json 生成的 Java 對象淫痰。然后毅臊,我們可以像下面這樣獲取一個該接口的代理對象:

WXInfoService wXInfoService = new Retrofit.Builder()
    .baseUrl("https://api.weixin.qq.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClient)
    .build().create(WXInfoService.class);

然后,我們就可以使用該對象并調用其方法來獲取接口返回的信息了:

Disposable disposable = wxInfoService.getWXUserInfo(accessToken, openId)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(wxUserInfo -> { /*...拿到結果之后進行處理...*/ });

上面我們只使用了 Retrofit 最基礎的 GET 接口黑界。當然,Retrofit 本身的功能遠比這要豐富得多皂林,關于其更多的使用朗鸠,可以參考其官方的文檔。

2础倍、動態(tài)代理:魔力發(fā)生的地方

上面我們使用 Retrofit 進行網(wǎng)絡請求烛占,實際其內部使用 OkHttp 來完成網(wǎng)絡請求的。僅定義了一個接口并調用了該接口的方法,就拿到了請求的結果忆家,這看上去非常簡潔犹菇,而這其中的功不可沒的就是動態(tài)代理

當我們使用 Retrofit.Buildercreate() 方法獲取一個 WXInfoService 實例的時候芽卿,實際返回的是經(jīng)過代理之后的對象揭芍。該方法內部會調用 Proxy 的靜態(tài)方法 newProxyInstance() 來得到一個代理之后的實例。為了說明這個方法的作用卸例,我們寫了一個例子:

public static void main(String...args) {
    Service service = getProxy(Service.class);
    String aJson = service.getAInfo();
    System.out.println(aJson);
    String bJson = service.getBInfo();
    System.out.println(bJson);
}

private static <T> T getProxy(final Class<T> service) {
    InvocationHandler h = (proxy, method, args) -> {
        String json = "{}";
        if (method.getName().equals("getAInfo")) {
            json = "{A請求的結果}";
        } else if (method.getName().equals("getBInfo")) {
            json = "{B請求的結果}";
        }
        return json;
    };
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, h);
}

該程序的輸出結果:

 {A請求的結果}
 {B請求的結果}

在上面的這個例子中称杨,我們先使用 getProxy() 獲取一個代理之后的實例,然后依次調用它的 getAInfo()getBInfo() 方法筷转,來模擬調用 A 接口和 B 接口的情形姑原,并依次得到了 A 請求的結果和 B 請求的結果。

上面的效果近似于我們使用 Retrofit 訪問接口的過程呜舒。為了說明這個過程中發(fā)生了什么锭汛,我們需要先了解一下這里的 newProxyInstance() 方法:

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

該方法接收三個參數(shù):

  1. 第一個是類加載器;
  2. 第二個是接口的 Class 類型袭蝗;
  3. 第三個是一個處理器唤殴,你可以將其看作一個用于回調的接口。當我們的代理實例觸發(fā)了某個方法的時候呻袭,就會觸發(fā)該回調接口的方法眨八。

InvocationHandler 是一個接口,它內部定義了一個方法如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

該方法也接收三個參數(shù)左电,第一個是觸發(fā)該方法的代理實例廉侧;第二個是觸發(fā)的方法;第三個是觸發(fā)的方法的參數(shù)篓足。invoke() 方法的返回結果會作為代理類的方法執(zhí)行的結果段誊。

所以,當了解了 newProxyInstance() 方法的定義之后栈拖,我們可以做如下總結:當我們使用 newProxyInstance() 方法獲取了一個代理實例 service 并調用其 getAInfo() 方法之后连舍,該方法的信息和參數(shù)信息會分別通過 methodargs 傳入到 hinvoke() 中。所以涩哟,最終的效果就是索赏,當我們調用 servicegetAInfo() 時候會觸發(fā) hinvoke()。在 invoke() 方法中我們根據(jù) method 得知觸發(fā)的方法是 getAInfo贴彼。于是潜腻,我們把它對應的請求從 invoke() 方法中返回,并作為 service.getAInfo() 的返回結果器仗。

所以融涣,我們可以總結 Retrofit 的大致工作流程:當我們獲取了接口的代理實例童番,并調用它的 getWXUserInfo() 方法之后,該方法的請求參數(shù)會傳遞到代理類的 InvocationHandler.invoke() 方法中威鹿。然后在該方法中剃斧,我們將這些信息轉換成 OkHttp 的 Request 并使用 OkHttp 進行訪問。從網(wǎng)絡中拿到結果之后忽你,我們使用 “轉換器” 將響應轉換成接口指定的 Java 類型幼东。

上面是 Retrofit 請求處理的基本流程,下面我們看一下 Retrofit 的代理方法內部究竟發(fā)生了什么檀夹。

3筋粗、Retrofit 的源碼解析

3.1 創(chuàng)建 Retrofit

根據(jù)上面的例子,當使用 Retrofit 的時候炸渡,首先我們需要使用 Retrofit 的構建者來創(chuàng)建 Retrofit 的實例娜亿。這里的構建者有幾個重要的方法需要提及一下:

3.1.1 addConverterFactory 方法

該方法用來向 Retrofit 中添加一個 Converter.FactoryConverter.Factory蚌堵,顧名思義是一種工廠模式买决。它是一個接口需要,實現(xiàn)兩個重要的方法吼畏。每個方法需要返回一個轉換器:某種數(shù)據(jù)類型到請求體的轉換器督赤,響應體到我們需要的數(shù)據(jù)類型的轉換器。當我們使用 Gson 來完成這個轉換泻蚊,那么我們就需要使用 GsonConverterFactory.create() 來得到一個適用于 Gson 的 Converter.Factory躲舌。

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

3.1.2 addCallAdapterFactory 方法

CallAdapter.Factory 用于獲取 CallAdapter 對象, CallAdapter 對象用于把原生的 OkHttp 的 Call 轉換成我們指定的請求類型性雄。比如没卸,上面的例子中,我們用來將其轉換成 Observable<WXUserInfo>秒旋。下面是該方法的定義:

public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
  callAdapterFactories.add(checkNotNull(factory, "factory == null"));
  return this;
}

3.1.3 build 方法

當根據(jù)用戶的自定義設置完了參數(shù)之后约计,就可以調用 build() 方法,來獲取一個 Retrofit 的實例迁筛。在該方法中會將上述方法傳入的 “適配器” 和 “轉換器” 添加到各自的列表中煤蚌,然后 new 出一個 Retrofit 的實例并返回。

3.1.4 小結

為了說明適配器 CallAdapter 和轉換器 Converter 的作用细卧,我們繪制了下圖:

Retrofit類圖

從上面我們看出尉桩,CallAdapter 主要用來將某個請求轉換成我們指定的類型。比如贪庙,在我們最開始的例子中魄健,要將請求轉換成 Observable<WXUserInfo>。如果轉換之后的請求是 Observable 類型的插勤,那么當我們對轉換后的請求進行訂閱的時候,就啟動了 OkHttp 的網(wǎng)絡請求過程。

在進行網(wǎng)絡請求之前會先使用 Converter 將請求的參數(shù)轉換成一個 RequestBody农尖。這里將其作為一個接口的好處是便于解耦析恋。比如,上面我們用 Gson 來完成轉換過程盛卡,你也可以通過自定義轉換器來使用其他的框架助隧,比如 Moshi 等。當拿到了響應之后滑沧,我們又會再次使用 Converter 來將響應體 ResponseBody 轉換成我們要求的類型并村。比如 WXUserInfo

從上面我們看出滓技,Retrofit 設計的非常妙的地方就在于上面的兩個過程的解耦(策略模式+工廠模式+適配器模式)哩牍。一次是將請求轉換成 Observable 的過程,一次是將請求體和響應體轉換成 OkHttp 要求的 RequestBodyResponseBody 的過程令漂。對于前者膝昆,不論我們使用的是 RxJava 1 還是 RxJava 2,只要傳入一個 CallAdapter 即可叠必。對于后者荚孵,不論我們使用哪種 Json 轉換框架,只要實現(xiàn)了 Converter 接口皆可纬朝。

3.2 獲取代理實例

3.2.1 劃分平臺:Platform

創(chuàng)建了 Retrofit 的實例之后收叶,我們就可以使用它的 create() 方法來獲取代理之后的服務實例。下面是這個方法的定義共苛。在這里判没,我們會先根據(jù) validateEagerly 變量來判斷是否立即對傳入的服務接口的方法進行解析。然后俄讹,我們使用 Proxy 的靜態(tài)方法獲取一個代理實例哆致。

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    // 這里的 validateEagerly 在 Retrofit 構建的時候設置
    if (validateEagerly) {
        // 是否立即對 Service 方法的內容進行解析
        eagerlyValidateMethods(service);
    }
    // 獲取代理實例
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
                private final Platform platform = Platform.get();
                @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                        throws Throwable {
                    // 該方法是 Object 的方法,直接觸發(fā)該方法
                    if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(this, args);
                    }
                    // 如果是 default 方法患膛,那么使用該 Java8 平臺的方法執(zhí)行
                    if (platform.isDefaultMethod(method)) {
                        return platform.invokeDefaultMethod(method, service, proxy, args);
                    }
                    // 獲取服務方法的信息摊阀,并將其包裝成 ServiceMethod
                    ServiceMethod<Object, Object> serviceMethod =
                            (ServiceMethod<Object, Object>) loadServiceMethod(method);
                    OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                    return serviceMethod.adapt(okHttpCall);
                }
            });
}

這里的 eagerlyValidateMethods() 方法定義如下:

private void eagerlyValidateMethods(Class<?> service) {
    // 獲取程序當前運行的平臺
    Platform platform = Platform.get();
    for (Method method : service.getDeclaredMethods()) {
        // 判斷該方法是否是 default 方法
        if (!platform.isDefaultMethod(method)) {
            loadServiceMethod(method);
        }
    }
}

它的作用是立即對服務接口的方法進行解析,并將解析之后的結果放進一個緩存中踪蹬。這樣胞此,當這個服務方法被觸發(fā)的時候,直接從緩存當中獲取解析之后的 ServiceMethod 來使用即可跃捣。該方法會先會根據(jù)當前程序運行的平臺來決定是否應該加載服務的方法漱牵。因為,Java 8 之后疚漆,我們可以為接口增加 default 類型的方法酣胀,所以刁赦,如果是 default 類型的話,我們不會調用 loadServiceMethod() 進行解析闻镶,而是調用 Java8 平臺的 invokeDefaultMethod() 來處理甚脉。在 invokeDefaultMethod() 中,會根據(jù)傳入的信息創(chuàng)建一個實例并使用反射觸發(fā)它的方法。此時,就間接地觸發(fā)了該 default 方法蜒滩。

判斷平臺的時候善涨,使用了如下這段代碼:

platform.isDefaultMethod(Method)

這里的 platform 是調用 Platform.get() 的時候得到的。它會在 get() 方法中嘗試使用反射去獲取一個只有 Java8 平臺才具有的類,以此來判斷是否是 Java8 的環(huán)境。在 Retrofit 中,提供了 Java8Android 兩個類來區(qū)分所在的平臺郊霎,并會根據(jù)運行環(huán)境來決定返回哪個實例。

所以蒲障,Platform 應用了策略模式歹篓,以對不同的平臺做不同的處理。在當前的版本中揉阎,它的主要作用是對 default 類型的方法進行處理庄撮。

3.2.2 解析服務方法:ServiceMethod

上面我們提到過 loadServiceMethod() 方法,它的主要作用:首先會嘗試從緩存當中獲取該方法對應的 ServiceMethod 實例毙籽,如果取到的話洞斯,就將其返回;否則坑赡,就使用構建者模式創(chuàng)建一個并放進緩存中烙如,然后將其返回。

ServiceMethod<?, ?> loadServiceMethod(Method method) {
    // 從緩存中進行獲取
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
            // 創(chuàng)建ServiceMethod實例
            result = new ServiceMethod.Builder<>(this, method).build();
            serviceMethodCache.put(method, result);
        }
    }
    return result;
}

ServiceMethod 的構建過程比較簡單毅否,只需要把當前的 Retrofit 實例和服務方法 method 傳入進去亚铁,然后調用它的 build() 方法就完成了整個創(chuàng)建過程。在 build() 方法中螟加,會完成對 method 的解析徘溢,比如根據(jù)注解判斷是什么類型的請求,根據(jù)方法的參數(shù)來解析請求的請求體等等捆探。

所以然爆,ServiceMethod 的作用是緩存服務方法對應的請求信息,這樣下次我們就不需要再次解析了黍图。同時曾雕,它提供了以下幾個方法。它們的主要作用是用來從 ServiceMethod 中獲取請求相關的信息:

toCall() 用來獲取用于 OkHttp 請求的 Call 對象:

okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
            contentType, hasBody, isFormEncoded, isMultipart);
    // ...
    return callFactory.newCall(requestBuilder.build());
}

toResponse(ResponseBody) 用來把 OkHttp 得到的響應體轉換成 Java 對象等(在示例中是WXUserInfo):

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

adapt(Call<R>) 用來將 OkHttp 的請求轉換成我們的服務方法的返回類型(在示例中是Observable<WXUserInfo>):

T adapt(Call<R> call) {
    return callAdapter.adapt(call);
}

3.2.3 請求封裝:OkHttpCall

解析完畢服務方法之后助被,我們得到了 ServiceMethod 實例剖张。然后切诀,我們使用它來創(chuàng)建 OkHttpCall 實例。這里的 OkHttpCall 實現(xiàn)了 Retrofit 中定義的 Call 接口搔弄,會在方法內調用 ServiceMethodtoCall() 方法來獲取 OkHttp 中的 Call 對象趾牧,然后使用它進行網(wǎng)絡訪問。當拿到了請求的結果之后又使用 ServiceMethodtoResponse() 把響應轉換成我們指定的類型肯污。下面是該類中的幾個比較重要的方法:

  1. execute() 方法,用來同步執(zhí)行網(wǎng)絡請求:
    @Override
    public Response<T> execute() throws IOException {
        okhttp3.Call call;
        synchronized (this) {
            // ...
            call = rawCall;
            if (call == null) {
                try {
                    // 創(chuàng)建 OkHttp 的 Call 實例
                    call = rawCall = createRawCall();
                } catch (IOException | RuntimeException | Error e) {
                    throwIfFatal(e);
                    creationFailure = e;
                    throw e;
                }
            }
        }
        if (canceled) {
            call.cancel();
        }
        // 同步執(zhí)行請求吨枉,并解析結果
        return parseResponse(call.execute());
    }
  1. createRawCall() 用來創(chuàng)建 OkHttp 的 Call 實例:
    // 使用 serviceMethod 的 toCall 方法獲取 OkHttp 的 Call 實例
    private okhttp3.Call createRawCall() throws IOException {
        okhttp3.Call call = serviceMethod.toCall(args);
        if (call == null) {
            throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
    }
  1. parseResponse() 用來將 OkHttp 的響應轉換成我們接口中定義的類型蹦渣。比如,在我們的例子中貌亭,返回的是 Observable<WXUserInfo>:
    // 使用 serviceMethod 的 toResponse 方法獲取 OkHttp 的 Response 實例
    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();
        rawResponse = rawResponse.newBuilder()
                .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
                .build();
        // ...
        ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
        try {
            // 使用 serviceMethod 的 toResponse 方法獲取 OkHttp 的 Response 實例
            T body = serviceMethod.toResponse(catchingBody);
            return Response.success(body, rawResponse);
        } catch (RuntimeException e) {
            catchingBody.throwIfCaught();
            throw e;
        }
    }

3.3 Retrofit 的工作過程

上面是 Retrofit 框架設計中幾個關鍵的部分的功能的解析柬唯。下面,我們再來具體看一下圃庭,從觸發(fā)代理類的方法到拿到響應的結果锄奢,這一整個過程中,都有哪些類的哪些方法參與剧腻,以及它們在什么時候拘央,扮演什么樣的角色。這里我們仍然使用最初的示例:

Retrofit的執(zhí)行過程

上圖中书在,我們將 Retrofit 的請求的過程分成三個過程來進行說明:

  1. 創(chuàng)建代理實例的過程:在這個過程中主要是調用 Proxy.newProxyInstance() 來獲取一個代理實例灰伟。相關的主要參數(shù)是 validateEagerly,我們會使用它來決定是否立即對傳入的接口的方法進行解析儒旬。不論我們什么時候進行解析栏账,都會把解析的結果緩存起來。
  2. 觸發(fā)代理方法的過程:觸發(fā)代理方法是整個請求的第二過程栈源。這個時候挡爵,我們調用了 WXInfoService 代理實例的 getWXUserInfo() 方法。此時甚垦,會觸發(fā) InvocationHandler.invoke() 方法茶鹃。在該方法內部會調用 ServiceMethod 的構建者模式來創(chuàng)建 serviceMethod 實例。當調用構建者模式的 build() 方法的時候制轰,會對方法 getWXUserInfo() 的信息進行解析前计。然后,使用 serviceMethod 創(chuàng)建 okHttpCall垃杖。最后男杈,調用 serviceMethod.adapt() 方法將 okHttpCall 實例轉換成 Observable<WXUserInfo>。在轉換的過程中會使用 CallAdapteradapt() 方法來完成適配调俘。
  3. 執(zhí)行網(wǎng)絡請求的過程:拿到了 Observable<WXUserInfo> 之后伶棒,需要對其進行訂閱才能觸發(fā)網(wǎng)絡請求旺垒。相關的邏輯在 CallAdapter 中完成。首先肤无,它會根據(jù)你使用同步還是異步的來決定使用哪個執(zhí)行器先蒋。這里存在兩個執(zhí)行器,它們的區(qū)別是一個會在內部調用 OkHttpCallenqueue()宛渐,另一個會在執(zhí)行器中調用 OkHttpCallexecute() 方法竞漾。不論調用 enqueue() 還是 execute(),都會先使用 OkHttpCalltoCall() 方法獲取一個 Call 請求窥翩。獲取請求的過程中會使用 Converter 來將某個實例轉換成請求體业岁。拿到了請求之后,使用該請求來進行網(wǎng)絡訪問寇蚊。當從網(wǎng)絡中拿到了響應之后笔时,會使用 Converter 來將響應體轉換成對象。這樣仗岸,拿到了實際的結果之后允耿,就會調用 ObserveronNext() 方法把結果通知給觀察者。

4扒怖、總結

在這篇文章中较锡,我們先簡單介紹了 Retrofit 的使用,然后姚垃,因為 Retrofit 內部使用動態(tài)代理來實現(xiàn)的念链,所以,我們對動態(tài)代理相關內容進行了介紹积糯。最后掂墓,我們對 Retrofit 的源碼進行了分析,先從設計思路看成,后從各個環(huán)節(jié)的執(zhí)行過程進行了說明君编。最后的最后,我們將兩者結合起來用一個時序圖做了說明川慌。

從上文中可以看出來吃嘿,Retrofit 設計的幾個值得我們借鑒的地方:

  1. 使用運行時注解和反射簡化請求描述,但是考慮到反射的效率比較低梦重,所以將一次反射之后的結果緩存起來兑燥,以便于下次使用。
  2. 動態(tài)代理:使用接口描述請求的好處是它簡潔琴拧,而且 “描述” 本來就是它的責任降瞳。但是,一般我們需要去實現(xiàn)接口才能使用。而這里告訴我們挣饥,使用動態(tài)代理一樣可以使用接除师。
  3. 解耦:從我們上面的圖中也可以看出來,Retrofit 的設計的思路是比較清晰的扔枫。它將一個請求的幾個過程解耦出來汛聚。首先是我們 Observable 到請求的轉換,這里使用適配器來完成短荐;然后是請求體和響應體的轉換倚舀,基本就是 Json 的轉換,使用轉換器來完成忍宋。這樣瞄桨,不論你使用 RxJava 1 還是 RxJava 2,不論是 Gson 還是 FastXml 都可以和 Retrifut 配合使用讶踪。

以上就是我們對 Retrofit 的源碼的分析。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末泊交,一起剝皮案震驚了整個濱河市乳讥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌廓俭,老刑警劉巖云石,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異研乒,居然都是意外死亡汹忠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門雹熬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宽菜,“玉大人,你說我怎么就攤上這事竿报∏ο纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵烈菌,是天一觀的道長阵幸。 經(jīng)常有香客問我,道長芽世,這世上最難降的妖魔是什么挚赊? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮济瓢,結果婚禮上荠割,老公的妹妹穿的比我還像新娘。我一直安慰自己葬荷,他們只是感情好涨共,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布纽帖。 她就那樣靜靜地躺著,像睡著了一般举反。 火紅的嫁衣襯著肌膚如雪懊直。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天火鼻,我揣著相機與錄音室囊,去河邊找鬼。 笑死魁索,一個胖子當著我的面吹牛融撞,可吹牛的內容都是我干的。 我是一名探鬼主播粗蔚,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尝偎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鹏控?” 一聲冷哼從身側響起致扯,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎当辐,沒想到半個月后抖僵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡缘揪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年耍群,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片找筝。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹈垢,死狀恐怖,靈堂內的尸體忽然破棺而出袖裕,到底是詐尸還是另有隱情耘婚,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布陆赋,位于F島的核電站沐祷,受9級特大地震影響,放射性物質發(fā)生泄漏攒岛。R本人自食惡果不足惜赖临,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灾锯。 院中可真熱鬧兢榨,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吟逝,卻和暖如春帽蝶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背块攒。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工励稳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人囱井。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓驹尼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庞呕。 傳聞我的和親對象是個殘疾皇子新翎,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容

  • 目錄介紹 1.首先回顧Retrofit簡單使用方法 2.Retrofit的創(chuàng)建流程源碼分析2.1 Retrofit...
    楊充211閱讀 1,061評論 0 16
  • Retrofit 2 源碼解析 關于Retrofit 2的使用請看上一篇https://www.jianshu.c...
    gogoingmonkey閱讀 517評論 0 1
  • Retrofit是一個Android網(wǎng)絡框架。是一個對OKHttp框架的簡單封裝住练。所以其內部實現(xiàn)原理實際上也是...
    龍城_閱讀 13,016評論 0 16
  • 《彳亍行.嘆歸》 文/亍頔 不喜江南雨料祠,猶戀北國寒; 一夕身感澎羞,十年未還。 三十無祿塵歸土敛苇,千里玄冰夜伴寒妆绞。 .....
    亍頔閱讀 481評論 1 8
  • 愛和喜歡是不一樣的: 淡淡的愛是喜歡来涨,深深的喜歡是愛图焰! 喜歡的后來是愛,愛的后來是親情蹦掐! 世間有種情感叫“喜歡”技羔,...
    和果閱讀 285評論 0 4