Android學習筆記——Retrofit2源碼淺分析

標簽: Android

先看看這張圖


制圖簡陋請多包涵

Retrofit

在學習Android的時候,我最熟悉的網(wǎng)絡請求框架莫過于OKHttp + Retrofit肋坚,反反復復用了很多次卓鹿,個人感覺Retrofit的兼容和解耦做的太好了重荠,這里想要試著分析一下Retrofit的源碼。

RESTful 原則

在我們使用Retrofit這個框架的時候挡篓,我們都需要后端的接口遵循RESTful原則婉陷,那什么是RESTful原則呢帚称?
RESTful是Representational State Transfer的縮寫,翻譯過來就是“表現(xiàn)層狀態(tài)轉(zhuǎn)化”秽澳,他是基于HTTP協(xié)議的闯睹。

  1. 表現(xiàn)層:我們訪問服務器是為了“增、刪担神、改楼吃、查”服務器上面的資源(數(shù)據(jù)),資源的對應的是一段文本(text)妄讯、一張圖片孩锡、一首歌曲或者一段音頻等,那么這里我們就說這些資源的實體就是表現(xiàn)層亥贸。
  2. 狀態(tài)轉(zhuǎn)化:當我們通過用“增浮创、刪、改砌函、查”的方式訪問服務器的時候,服務器的資源實體可能會發(fā)生變化溜族,對應的狀態(tài)也會相映的變化讹俊,這就是狀態(tài)轉(zhuǎn)化
  3. 客戶端怎么進行“增煌抒、刪仍劈、改、查”寡壮? 通過HTTP協(xié)議贩疙,具體就是用HTTP提供的GET、POST况既、DELETE这溅、PUT的方式

Retrofit分層

我們導入Retrofit的時候棒仍,發(fā)現(xiàn)我們導入的包分為了三個部分:包裝OKHttp的Retrofit部分悲靴,兼容RxJava的部分和兼容Gson的部分。

一莫其、包裝OKHttp

我們來看看這個部分的目錄:


image.png
  1. 首先這里有一個子目錄http癞尚,進去看我們就可以發(fā)現(xiàn)里面定義了若干的關于http協(xié)議的注解(包括GET、POST乱陡、PUT和DELETE等)浇揩。
  2. 接下來就是Retrofit以及它包裝的網(wǎng)絡請求執(zhí)行器,請求的構(gòu)建器憨颠,響應胳徽、轉(zhuǎn)化器、線程池和工具等等(以上沒按照順序)

二、兼容Gson

我們來看看這個部分的目錄:


image.png

這個部分很簡單膜廊,只包含了三個部分:Gson轉(zhuǎn)換器的工廠乏沸、Gson請求體的轉(zhuǎn)換器和Gson響應體的轉(zhuǎn)換器。

三爪瓜、兼容RxJava

我們再來看看這個部分的目錄:


image.png

這個部分我們可以看到很多繼承自RxJava的Observable對象蹬跃,都由RxJava2CallAdapter封裝,最后由它的工廠類RxJava2CallAdapterFactory創(chuàng)建出來铆铆。

Retrofit 流程源碼分析

一般情況下我們使用Retrofit的代碼如下:

    mApiService = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(BASE_URL)
                .client(getClient())
                .build()
                .create(ApiService.class);

先是通過Retrofit的構(gòu)建器構(gòu)建一個Retrofit對象蝶缀,前面的幾個鏈式調(diào)用都是為了支持其他三方庫的功能,我們直接去看Retrofit的構(gòu)建器的build()方法薄货。

Retrofit.Builder.java

    //成員
    //當前的平臺
    private final Platform platform;
    //網(wǎng)絡請求執(zhí)行器的工廠
    private @Nullable okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    //可能會用到的轉(zhuǎn)換器工廠的集合
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    //可能會用到的適配器工廠的集合
    private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    //可能會用到的線程池翁都,默認為null
    private @Nullable Executor callbackExecutor;
    //是否在調(diào)用create()方法時,驗證傳入的接口中的所有方法
    private boolean validateEagerly;
    
    .......
    
    
 public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // 制作適配器的防御副本并添加默認的呼叫適配器.
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // 制作轉(zhuǎn)換器的防御副本.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

我們可以看見谅猾,這里和簡單的構(gòu)建者模式?jīng)]什么區(qū)別柄慰,初始化需要用到的成員變量,然后再直接通過Retrofit的構(gòu)造函數(shù)創(chuàng)建一個實例對象税娜,接下來我們?nèi)タ纯碿reate()方法坐搔。

public <T> T create(final Class<T> service) {
    //驗證服務接口,服務接口必須是一個interface對象敬矩,且它沒有任何父接口
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    //通過動態(tài)代理創(chuàng)建服務接口的實現(xiàn)類
    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的方法概行,不做任何處理
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            //這個條件判斷在Retrofit3.2.0時一直為false,其他版本不知道
            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.callAdapter.adapt(okHttpCall);
          }
        });
  }

在create()方法中,首先驗證了我們傳入的服務接口類弧岳,然后通過動態(tài)代理拿到方法和參數(shù)凳忙,通過拿到的方法加載ServiceMethod對象,那么ServiceMethod是干什么的呢禽炬?注釋是這么說的:將接口的方法調(diào)整為Http調(diào)用涧卵。我們先保留疑問,去看一下loadServiceMethod()方法做了什么:

 ServiceMethod<?, ?> loadServiceMethod(Method method) {
    //通過緩存拿到
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        // 沒有緩存就通過構(gòu)建器構(gòu)建一個新的ServiceMethod
        result = new ServiceMethod.Builder<>(this, method).build();
        //緩存
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

這個方法的干的事情就是腹尖,判斷是否有緩存艺演,有就直接返回,沒有就通過構(gòu)建器構(gòu)建一個新的ServiceMethod對象桐臊,這里的緩存是通過CurrentHashMap胎撤,鍵為方法,值為ServiceMethod對象断凶。這里模擬第一次調(diào)用伤提,沒有緩存,所以我們器看看ServiceMethod的構(gòu)建器:

   Builder(Retrofit retrofit, Method method) {
   //配置成員變量
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }

    public ServiceMethod build() {
      //拿到適配器
      callAdapter = createCallAdapter();
      //通過響應類型
      responseType = callAdapter.responseType();
      if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
      }
      //拿到轉(zhuǎn)換器
      responseConverter = createResponseConverter();

      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      if (httpMethod == null) {
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
              + "request body (e.g., @POST).");
        }
      }

      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }

        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      if (relativeUrl == null && !gotUrl) {
        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError("Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError("Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError("Multipart method must contain at least one @Part.");
      }
      return new ServiceMethod<>(this);
    }
  1. 這里先拿到適配器认烁,判斷適配器的響應類型是否合法
  2. 然后再拿到轉(zhuǎn)換器
  3. 循環(huán)解析——服務接口中被調(diào)用的方法上的注解
  4. 檢查各項數(shù)據(jù)肿男,new出ServiceMethod實例

下面我們依次分析這幾步:
第一步: 拿到適配器

private CallAdapter<T, R> createCallAdapter() {
      //獲取通用的返回類型
      Type returnType = method.getGenericReturnType();
      //返回類型通配符或者變量類型
      if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(
            "Method return type must not include a type variable or wildcard: %s", returnType);
      }
      //返回類型不能是無返回類型
      if (returnType == void.class) {
        throw methodError("Service methods cannot return void.");
      }
      //拿到傳入該方法的的注解
      Annotation[] annotations = method.getAnnotations();
      try {
        //noinspection unchecked
        return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create call adapter for %s", returnType);
      }
    }

這里先是判斷了服務接口中介汹,被用戶調(diào)用的方法的返回類型是否合法,合法之后拿到該方法的注解舶沛,轉(zhuǎn)入Retrofit中調(diào)用callAdapter()方法嘹承,下面我們?nèi)タ纯催@個方法:

public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
  }

這里直接就調(diào)用了nextCallAdapter()方法,我們繼續(xù)跟進:

 public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {
    // 判斷參數(shù)是否為空
    checkNotNull(returnType, "returnType == null");
    checkNotNull(annotations, "annotations == null");
    //找到與傳入?yún)?shù)值相等的變量在List中的索引
    int start = adapterFactories.indexOf(skipPast) + 1;
    //循環(huán)遍歷找到需要的適配器
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }
    ......
  }

這里進行了參數(shù)的非空判斷如庭,然后循環(huán)遍歷List叹卷,我們看向第十行這里調(diào)用了CallAdapter.Factory的實現(xiàn)類(應該是RxJava2CallAdapterFactory)的get()方法,這個方法會判斷調(diào)用的服務接口里面的方法的返回類型坪它,是否為Completable骤竹、Flowable、Maybe往毡,Single蒙揣、Obervable,之后會直接new出來需要的適配器(Adapter)开瞭。

我們在去看看第二步:拿到轉(zhuǎn)換器

  private Converter<ResponseBody, T> createResponseConverter() {
      //獲取方法的注解
      Annotation[] annotations = method.getAnnotations();
      try {
      //轉(zhuǎn)到Retrofit中去拿到轉(zhuǎn)換器
        return retrofit.responseBodyConverter(responseType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create converter for %s", responseType);
      }
    }

我們跟到Retrofit中去看看responseBodyConverter()方法怎么拿到轉(zhuǎn)換器的懒震。

 public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations){
    return nextResponseBodyConverter(null, type, annotations);
  }

這里和拿到適配器的流程很像,也調(diào)用了一個nextxxx()方法嗤详。

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    //檢查參數(shù)是否為空
    checkNotNull(type, "type == null");
    checkNotNull(annotations, "annotations == null");

    //獲取參數(shù)的值獲取在List對應的索引
    int start = converterFactories.indexOf(skipPast) + 1;
    //循環(huán)遍歷挎狸,找到轉(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;
      }
    }
    ......
  }

這里可以看見,拿到轉(zhuǎn)換器的過程和拿到適配器的過程十分相似断楷,重點還是第12行的responseBodyConverter()。根據(jù)我們平時的用法崭别,這個方法直接new出來了一個GsonResponseBodyConverter(Gson響應體轉(zhuǎn)換器)冬筒。

我們?nèi)タ纯吹谌剑?解析注解

private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        if (!Void.class.equals(responseType)) {
          throw methodError("HEAD method must use Void as response type.");
        }
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError("@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

這里解析注解主要有分為兩類:

  • 1 通過parseHttpMethodAndPath()方法解析出網(wǎng)絡請求的方法URL相對路徑
  • 2 通過parseHeaders()方法解析出——需要添加的請求頭

這里具體的解析方式我們就不深究了。
接下來我們看看第四步:檢查各項數(shù)據(jù)茅主,拿到ParameterHandler舞痰,new出ServiceMethod實例。這里就不挖這些代碼了诀姚。
我們回到Retrofit的create()方法:

public <T> T create(final Class<T> service) {
    //驗證服務接口响牛,服務接口必須是一個interface對象,且它沒有任何父接口
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    //通過動態(tài)代理創(chuàng)建服務接口的實現(xiàn)類
    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的方法赫段,不做任何處理
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            //這個條件判斷在Retrofit3.2.0時一直為false,其他版本不知道
            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.callAdapter.adapt(okHttpCall);
          }
        });
  }

ServiceMethod的loadServiceMethod()加載完了呀打,接下來就是直接new出OkHttpCall(網(wǎng)絡執(zhí)行器),并把OkHttpCall通過RxJava2CallAdapter的adapt()方法傳過去糯笙,接下里我們看看adapt()方法:

public Object adapt(Call<R> call) {
    Observable<Response<R>> responseObservable = isAsync
        ? new CallEnqueueObservable<>(call)
        : new CallExecuteObservable<>(call);

    Observable<?> observable;
    if (isResult) {
      observable = new ResultObservable<>(responseObservable);
    } else if (isBody) {
      observable = new BodyObservable<>(responseObservable);
    } else {
      observable = responseObservable;
    }

    if (scheduler != null) {
      observable = observable.subscribeOn(scheduler);
    }

    if (isFlowable) {
      return observable.toFlowable(BackpressureStrategy.LATEST);
    }
    if (isSingle) {
      return observable.singleOrError();
    }
    if (isMaybe) {
      return observable.singleElement();
    }
    if (isCompletable) {
      return observable.ignoreElements();
    }
    return observable;
  }

首先通過服務接口中的方法的返回類型中的泛型贬丛,判斷被觀察者的種類(一般情況下我們都是responseObservable),然后通過服務接口中的方法的返回類型判斷是Flowable给涕、Single豺憔、Maybe或者就是Observable额获。

到這里我們的Retrofit一套簡單流程就基本就分析完了,當然這里是淺分析恭应,當然還有頗多存疑抄邀。

 mApiService = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(BASE_URL)
                .client(getClient())
                .build()
                .create(ApiService.class);

前面分析總結(jié):create()中使用了動態(tài)代理,在實際調(diào)用服務接口中的方法時昼榛,把方法和參數(shù)傳給了ServiceMethod境肾,ServiceMethod通過構(gòu)建者模式創(chuàng)建,在build()方法中通過Retrofit拿到了適配器和轉(zhuǎn)換器褒纲,并且對方法的注解和參數(shù)進行了解析准夷,可以說ServiceMethod做了很多事情,最后ServiceMethod通過調(diào)用它拿到的適配器莺掠,進過判斷衫嵌,返回網(wǎng)絡請求中的被觀察者。

有些童鞋可能會想彻秆,網(wǎng)絡請求到底是在什么時候執(zhí)行的呢楔绞?
通過我們拿到的被觀察者,訂閱我們自己寫的觀察者時,進行的網(wǎng)絡請求透硝。具體的五垮,RxJava的訂閱方法subscrib()最終會調(diào)用subscribeActual()這個方法,這個方法是個抽象方法蔫耽,在Retrofit的取得適配器中的adapt()方法(我們上面分析過這個方法)會new出新的被觀察者,這個新的觀察者會覆寫subscribeActual()這個方法留夜,通過傳入的網(wǎng)絡執(zhí)行器發(fā)起網(wǎng)絡請求匙铡。

本片文章謝謝童鞋們的觀看。

本人Andorid小白碍粥,水平有限鳖眼,如果有不對的地方,希望大家提點一下我嚼摩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钦讳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子枕面,更是在濱河造成了極大的恐慌愿卒,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潮秘,死亡現(xiàn)場離奇詭異掘猿,居然都是意外死亡,警方通過查閱死者的電腦和手機唇跨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門稠通,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衬衬,“玉大人,你說我怎么就攤上這事改橘∽涛荆” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵飞主,是天一觀的道長狮惜。 經(jīng)常有香客問我,道長碌识,這世上最難降的妖魔是什么碾篡? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮筏餐,結(jié)果婚禮上开泽,老公的妹妹穿的比我還像新娘。我一直安慰自己魁瞪,他們只是感情好穆律,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著导俘,像睡著了一般峦耘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旅薄,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天辅髓,我揣著相機與錄音,去河邊找鬼少梁。 笑死洛口,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的猎莲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼技即,長吁一口氣:“原來是場噩夢啊……” “哼著洼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起而叼,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤身笤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后葵陵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體液荸,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年脱篙,在試婚紗的時候發(fā)現(xiàn)自己被綠了娇钱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伤柄。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖文搂,靈堂內(nèi)的尸體忽然破棺而出适刀,到底是詐尸還是另有隱情,我是刑警寧澤煤蹭,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布笔喉,位于F島的核電站,受9級特大地震影響硝皂,放射性物質(zhì)發(fā)生泄漏常挚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一稽物、第九天 我趴在偏房一處隱蔽的房頂上張望奄毡。 院中可真熱鬧,春花似錦姨裸、人聲如沸秧倾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽那先。三九已至,卻和暖如春赡艰,著一層夾襖步出監(jiān)牢的瞬間售淡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工慷垮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揖闸,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓料身,卻偏偏與公主長得像汤纸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子芹血,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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

  • 前言 注解式的框架非持ⅲ火,注解以其輕量幔烛,簡潔等特性被人們所喜愛者啃擦,關鍵是它解藕。網(wǎng)絡請求的框架非常多饿悬,比較受歡迎的...
    薩達哈魯醬閱讀 565評論 0 5
  • 本文將順著構(gòu)建請求對象->構(gòu)建請求接口->發(fā)起同步/異步請求的流程令蛉,分析Retrofit是如何實現(xiàn)的。 開始之前狡恬,...
    zhuhf閱讀 1,607評論 0 10
  • 安卓開發(fā)領域中珠叔,很多重要的問題都有很好的開源解決方案蝎宇,例如Square公司提供網(wǎng)絡請求 OkHttp , Retr...
    aaron688閱讀 1,903評論 1 20
  • Retrofit 是目前作為網(wǎng)絡請求的主流框架,使用起來很方便运杭,僅需在接口中定義方法夫啊,打上注解,而且和 Rxjav...
    Kip_Salens閱讀 560評論 0 3
  • 今天下午我去上書法課辆憔,因此撇眯,我非常開心,因為我又能見到我書法老師和同學們了虱咧。 到了書法班之后熊榛,我迫...
    豪好學習天天向上閱讀 101評論 0 0