日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Retrofit2 完全解析 探索与okhttp之间的关系

發布時間:2025/4/16 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Retrofit2 完全解析 探索与okhttp之间的关系 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://blog.csdn.net/lmj623565791/article/details/51304204; 本文出自:【張鴻洋的博客】

1. 概述

之前寫了個okhttputils的工具類,然后有很多同學詢問這個工具類和retrofit什么區別,于是上了下官網,發現其底層對網絡的訪問默認也是基于okhttp,不過retrofit非常適合于restful url格式的請求,更多使用注解的方式提供功能。

既然這樣,我們本篇博文首先研究其所提供的常用的用法:

  • 一般的get、post請求
  • 動態url,動態參數設置,各種注解的使用
  • 上傳文件(單文件,多文件上傳等)
  • 下載文件等(這個不推薦retrofit去做,具體看下文)

此外,由于其內部提供了ConverterFactory用于對返回的requestBody進行轉化和特殊的requestBody的構造,所以本文也包含:

  • 如何自定義ConverterFactory

最后呢,因為其源碼并不復雜,本文將對源碼進行整體的介紹,即

  • Retrofit源碼分析

ok,說這么多,既然需要restful url,我只能撿起我那個半桶水的spring mvc 搭建一個服務端的小例子~~

最后本文使用版本:

compile 'com.squareup.retrofit2:retrofit:2.0.2'11

主要是源碼解析,自定義Converter.Factory等一些細節的探索。

恩,寫完后,發現本文很長,中途請沒事站起來走兩步。

retrofit2官網地址:https://github.com/square/retrofit/

2. Retrofit用法示例

2.1 一般的get請求

retrofit在使用的過程中,需要定義一個接口對象,我們首先演示一個最簡單的get請求,接口如下所示:

public interface IUserBiz {@GET("users")Call<List<User>> getUsers(); }

可以看到有一個getUsers()方法,通過@GET注解標識為get請求,@GET中所填寫的value和baseUrl組成完整的路徑,baseUrl在構造retrofit對象時給出。

下面看如何通過retrofit完成上述的請求:

Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.31.242:8080/springmvc_users/user/").addConverterFactory(GsonConverterFactory.create()).build(); IUserBiz userBiz = retrofit.create(IUserBiz.class); Call<List<User>> call = userBiz.getUsers();call.enqueue(new Callback<List<User>>(){@Overridepublic void onResponse(Call<List<User>> call, Response<List<User>> response){Log.e(TAG, "normalGet:" + response.body() + "");}@Overridepublic void onFailure(Call<List<User>> call, Throwable t){}});

依然是構造者模式,指定了baseUrl和Converter.Factory,該對象通過名稱可以看出是用于對象轉化的,本例因為服務器返回的是json格式的數組,所以這里設置了GsonConverterFactory完成對象的轉化。

ok,這里可以看到很神奇,我們通過Retrofit.create就可以拿到我們定義的IUserBiz的實例,調用其方法即可拿到一個Call對象,通過call.enqueue即可完成異步的請求。

具體retrofit怎么得到我們接口的實例的,以及對象的返回結果是如何轉化的,我們后面具體分析。

這里需要指出的是:

  • 接口中的方法必須有返回值,且比如是Call<T>類型

  • .addConverterFactory(GsonConverterFactory.create())這里如果使用gson,需要額外導入:

  • compile 'com.squareup.retrofit2:converter-gson:2.0.2'

    當然除了gson以外,還提供了以下的選擇:

    • Gson: com.squareup.retrofit2:converter-gson
    • Jackson: com.squareup.retrofit2:converter-jackson
    • Moshi: com.squareup.retrofit2:converter-moshi
    • Protobuf: com.squareup.retrofit2:converter-protobuf
    • Wire: com.squareup.retrofit2:converter-wire
    • Simple XML: com.squareup.retrofit2:converter-simplexml
    • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

    當然也支持自定義,你可以選擇自己寫轉化器完成數據的轉化,這個后面將具體介紹。

  • 既然call.enqueue是異步的訪問數據,那么同步的訪問方式為call.execute,這一點非常類似okhttp的API,實際上默認情況下內部也是通過okhttp3.Call實現。
  • 那么,通過這么一個簡單的例子,應該對retrofit已經有了一個直觀的認識,下面看更多其支持的特性。

    2.2 動態的url訪問@PATH

    文章開頭提過,retrofit非常適用于restful url的格式,那么例如下面這樣的url:

    //用于訪問zhy的信息 http://192.168.1.102:8080/springmvc_users/user/zhy //用于訪問lmj的信息 http://192.168.1.102:8080/springmvc_users/user/lmj12341234

    即通過不同的username訪問不同用戶的信息,返回數據為json字符串。

    那么可以通過retrofit提供的@PATH注解非常方便的完成上述需求。

    我們再定義一個方法:

    public interface IUserBiz {@GET("{username}")Call<User> getUser(@Path("username") String username); }1234512345

    可以看到我們定義了一個getUser方法,方法接收一個username參數,并且我們的@GET注解中使用{username}聲明了訪問路徑,這里你可以把{username}當做占位符,而實際運行中會通過@PATH("username")所標注的參數進行替換。

    那么訪問的代碼很類似:

    //省略了retrofit的構建代碼 Call<User> call = userBiz.getUser("zhy"); //Call<User> call = userBiz.getUser("lmj"); call.enqueue(new Callback<User>() {@Overridepublic void onResponse(Call<User> call, Response<User> response){Log.e(TAG, "getUsePath:" + response.body());}@Overridepublic void onFailure(Call<User> call, Throwable t){} });

    2.3 查詢參數的設置@Query

    看下面的url

    http://baseurl/users?sortby=username http://baseurl/users?sortby=id1212

    即一般的傳參,我們可以通過@Query注解方便的完成,我們再次在接口中添加一個方法:

    public interface IUserBiz {@GET("users")Call<List<User>> getUsersBySort(@Query("sortby") String sort); }

    訪問的代碼,其實沒什么寫的:

    //省略retrofit的構建代碼 Call<List<User>> call = userBiz.getUsersBySort("username"); //Call<List<User>> call = userBiz.getUsersBySort("id"); //省略call執行相關代碼12341234

    ok,這樣我們就完成了參數的指定,當然相同的方式也適用于POST,只需要把注解修改為@POST即可。

    對了,我能剛才學了@PATH,那么會不會有這樣嘗試的沖動,對于剛才的需求,我們這么寫:

    @GET("users?sortby={sortby}")Call<List<User>> getUsersBySort(@Path("sortby") String sort);1212

    乍一看別說好像有點感覺,哈,實際上運行是不支持的~估計是@ Path的定位就是用于url的路徑而不是參數,對于參數還是選擇通過@Query來設置。

    2.4 POST請求體的方式向服務器傳入json字符串@Body

    大家都清楚,我們app很多時候跟服務器通信,會選擇直接使用POST方式將json字符串作為請求體發送到服務器,那么我們看看這個需求使用retrofit該如何實現。

    再次添加一個方法:

    public interface IUserBiz {@POST("add")Call<List<User>> addUser(@Body User user); }1234512345

    提交的代碼其實基本都是一致的:

    //省略retrofit的構建代碼Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com")); //省略call執行相關代碼123123

    ok,可以看到其實就是使用@Body這個注解標識我們的參數對象即可,那么這里需要考慮一個問題,retrofit是如何將user對象轉化為字符串呢?下文將詳細解釋~

    下面對應okhttp,還有兩種requestBody,一個是FormBody,一個是MultipartBody,前者以表單的方式傳遞簡單的鍵值對,后者以POST表單的方式上傳文件可以攜帶參數,retrofit也二者也有對應的注解,下面繼續~

    2.5 表單的方式傳遞鍵值對@FormUrlEncoded

    這里我們模擬一個登錄的方法,添加一個方法:

    public interface IUserBiz {@POST("login")@FormUrlEncodedCall<User> login(@Field("username") String username, @Field("password") String password); }

    訪問的代碼:

    //省略retrofit的構建代碼 Call<User> call = userBiz.login("zhy", "123"); //省略call執行相關代碼123123

    ok,看起來也很簡單,通過@POST指明url,添加FormUrlEncoded,然后通過@Field添加參數即可。

    2.6 單文件上傳@Multipart

    下面看一下單文件上傳,依然是再次添加個方法:

    public interface IUserBiz {@Multipart@POST("register")Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password); }

    這里@MultiPart的意思就是允許多個@Part了,我們這里使用了3個@Part,第一個我們準備上傳個文件,使用了MultipartBody.Part類型,其余兩個均為簡單的鍵值對。

    使用:

    File file = new File(Environment.getExternalStorageDirectory(), "icon.png"); RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file); MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);Call<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));1234512345

    ok,這里感覺略為麻煩。不過還是蠻好理解~~多個@Part,每個Part對應一個RequestBody。

    這里插個實驗過程,其實我最初對于文件,也是嘗試的@Part RequestBody,因為@Part("key"),然后傳入一個代表文件的RequestBody,我覺得更加容易理解,后來發現試驗無法成功,而且查了下issue,給出了一個很奇怪的解決方案,這里可以參考:retrofit#1063。

    給出了一個類似如下的方案:

    public interface ApiInterface {@Multipart@POST ("/api/Accounts/editaccount")Call<User> editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);}

    可以看到對于文件的那個@Partvalue竟然寫了這么多奇怪的東西,而且filename竟然硬編碼了~~這個不好吧,我上傳的文件名竟然不能動態指定。

    為了文件名不會被寫死,所以給出了最上面的上傳單文件的方法,ps:上面這個方案經測試也是可以上傳成功的。

    恩,這個奇怪方案,為什么這么做可行,下文會給出非常詳細的解釋。

    最后看下多文件上傳~

    2.7 多文件上傳@PartMap

    再添加一個方法~~~

    public interface IUserBiz{@Multipart@POST("register")Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password); }

    這里使用了一個新的注解@PartMap,這個注解用于標識一個Map,Map的key為String類型,代表上傳的鍵值對的key(與服務器接受的key對應),value即為RequestBody,有點類似@Part的封裝版本。

    執行的代碼:

    File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");RequestBody photo = RequestBody.create(MediaType.parse("image/png", file); Map<String,RequestBody> photos = new HashMap<>(); photos.put("photos\"; filename=\"icon.png", photo); photos.put("username", RequestBody.create(null, "abc"));Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));

    可以看到,可以在Map中put進一個或多個文件,鍵值對等,當然你也可以分開,單獨的鍵值對也可以使用@Part,這里又看到設置文件的時候,相對應的key很奇怪,例如上例"photos\"; filename=\"icon.png",前面的photos就是與服務器對應的key,后面filename是服務器得到的文件名,ok,參數雖然奇怪,但是也可以動態的設置文件名,不太影響使用~~

    2.8 下載文件

    這個其實我覺得直接使用okhttp就好了,使用retrofit去做這個事情真的有點瞎用的感覺~~

    增加一個方法:

    @GET("download") Call<ResponseBody> downloadTest();1212

    調用:

    Call<ResponseBody> call = userBiz.downloadTest(); call.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){InputStream is = response.body().byteStream();//save file}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t){} });

    可以看到可以返回ResponseBody,那么很多事都能干了~~

    but,也看出這種方式下載感覺非常雞肋,并且onReponse回調雖然在UI線程,但是你還是要處理io操作,也就是說你在這里還要另外開線程操作,或者你可以考慮同步的方式下載。

    最后還是建議使用okhttp去下載,例如使用okhttputils.

    有人可能會問,使用okhttp,和使用retrofit會不會造成新建多個OkHttpClient對象呢,其實是可設置的,參考下文。

    ok,上面就是一些常用的方法,當然還涉及到一些沒有介紹的注解,但是通過上面這么多方法的介紹,再多一二個注解的使用方式,相信大家能夠解決。

    3. 配置OkHttpClient

    這個需要簡單提一下,很多時候,比如你使用retrofit需要統一的log管理,給每個請求添加統一的header等,這些都應該通過okhttpclient去操作,比如addInterceptor

    例如:

    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,統一的header等 {@Overridepublic okhttp3.Response intercept(Chain chain) throws IOException{return null;} }).build();

    或許你需要更多的配置,你可以單獨寫一個OkhttpClient的單例生成類,在這個里面完成你所需的所有的配置,然后將OkhttpClient實例通過方法公布出來,設置給retrofit。

    設置方式:

    Retrofit retrofit = new Retrofit.Builder().callFactory(OkHttpUtils.getClient()).build();123123

    callFactory方法接受一個okhttp3.Call.Factory對象,OkHttpClient即為一個實現類。

    4. Retrofit源碼解析

    ok,接下來我們隊retrofit的源碼做簡單的分析,首先我們看retrofit如何為我們的接口實現實例;然后看整體的執行流程;最后再看詳細的細節;

    4.1 Retrofit如何為我們的接口實現實例

    通過上文的學習,我們發現使用retrofit需要去定義一個接口,然后可以通過調用retrofit.create(IUserBiz.class);方法,得到一個接口的實例,最后通過該實例執行我們的操作,那么retrofit如何實現我們指定接口的實例呢?

    其實原理是:動態代理。但是不要被動態代理這幾個詞嚇唬到,Java中已經提供了非常簡單的API幫助我們來實現動態代理。

    看源碼前先看一個例子:

    public interface ITest {@GET("/heiheihei")public void add(int a, int b);} public static void main(String[] args) {ITest iTest = (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), new Class<?>[]{ITest.class}, new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{Integer a = (Integer) args[0];Integer b = (Integer) args[1];System.out.println("方法名:" + method.getName());System.out.println("參數:" + a + " , " + b);GET get = method.getAnnotation(GET.class);System.out.println("注解:" + get.value());return null;}});iTest.add(3, 5); }

    輸出結果為:

    方法名:add 參數:3 , 5 注解:/heiheihei123123

    可以看到我們通過Proxy.newProxyInstance產生的代理類,當調用接口的任何方法時,都會調用InvocationHandler#invoke方法,在這個方法中可以拿到傳入的參數,注解等。

    試想,retrofit也可以通過同樣的方式,在invoke方法里面,拿到所有的參數,注解信息然后就可以去構造RequestBody,再去構建Request,得到Call對象封裝后返回。

    ok,下面看retrofit#create的源碼:

    public <T> T create(final Class<T> service) {return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object... args) throws Throwable {});}12345671234567

    哈,和上面對應。到這里,你應該明白retrofit為我們接口生成實例對象并不神奇,僅僅是使用了Proxy這個類的API而已,然后在invoke方法里面拿到足夠的信息去構建最終返回的Call而已。

    哈,其實真正的動態代理一般是有具體的實現類的,只是在這個類調用某個方法的前后去執行一些別的操作,比如開事務,打log等等。當然,本博文并不需要涉及這些詳細的內容,如果你希望詳細去了解,可以搜索關鍵字:Proxy InvocationHandler。

    4.2 retrofit整體實現流程

    4.2.1 Retrofit的構建

    這里依然是通過構造者模式進行構建retrofit對象,好在其內部的成員變量比較少,我們直接看build()方法。

    public Builder() {this(Platform.get()); }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();}// Make a defensive copy of the adapters and add the default Call adapter.List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));// Make a defensive copy of the converters.List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,callbackExecutor, validateEagerly); }
    • baseUrl必須指定,這個是理所當然的;
    • 然后可以看到如果不著急設置callFactory,則默認直接new OkHttpClient(),可見如果你需要對okhttpclient進行詳細的設置,需要構建OkHttpClient對象,然后傳入;
    • 接下來是callbackExecutor,這個想一想大概是用來將回調傳遞到UI線程了,當然這里設計的比較巧妙,利用platform對象,對平臺進行判斷,判斷主要是利用Class.forName("")進行查找,具體代碼已經被放到文末,如果是Android平臺,會自定義一個Executor對象,并且利用Looper.getMainLooper()實例化一個handler對象,在Executor內部通過handler.post(runnable),ok,整理憑大腦應該能構思出來,暫不貼代碼了。
    • 接下來是adapterFactories,這個對象主要用于對Call進行轉化,基本上不需要我們自己去自定義。
    • 最后是converterFactories,該對象用于轉化數據,例如將返回的responseBody轉化為對象等;當然不僅僅是針對返回的數據,還能用于一般備注解的參數的轉化例如@Body標識的對象做一些操作,后面遇到源碼詳細再描述。

    ok,總體就這幾個對象去構造retrofit,還算比較少的~~

    4.2.2 具體Call構建流程

    我們構造完成retrofit,就可以利用retrofit.create方法去構建接口的實例了,上面我們已經分析了這個環節利用了動態代理,而且我們也分析了具體的Call的構建流程在invoke方法中,下面看代碼:

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

    主要也就三行代碼,第一行是根據我們的method將其包裝成ServiceMethod,第二行是通過ServiceMethod和方法的參數構造retrofit2.OkHttpCall對象,第三行是通過serviceMethod.callAdapter.adapt()方法,將OkHttpCall進行代理包裝;

    下面一個一個介紹:

    • ServiceMethod應該是最復雜的一個類了,包含了將一個method轉化為Call的所有的信息。
    // Retrofit class ServiceMethod loadServiceMethod(Method method) {ServiceMethod result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {result = new ServiceMethod.Builder(this, method).build();serviceMethodCache.put(method, result);}}return result;}// ServiceMethod 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?");}responseConverter = createResponseConverter();for (Annotation annotation : methodAnnotations) {parseMethodAnnotation(annotation);}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);}return new ServiceMethod<>(this);}

    直接看build方法,首先拿到這個callAdapter最終拿到的是我們在構建retrofit里面時adapterFactories時添加的,即為:new ExecutorCallbackCall<>(callbackExecutor, call),該ExecutorCallbackCall唯一做的事情就是將原本call的回調轉發至UI線程。

    接下來通過callAdapter.responseType()返回的是我們方法的實際類型,例如:Call<User>,則返回User類型,然后對該類型進行判斷。

    接下來是createResponseConverter拿到responseConverter對象,其當然也是根據我們構建retrofit時,addConverterFactory添加的ConverterFactory對象來尋找一個合適的返回,尋找的依據主要看該converter能否處理你編寫方法的返回值類型,默認實現為BuiltInConverters,僅僅支持返回值的實際類型為ResponseBody和Void,也就說明了默認情況下,是不支持Call<User>這類類型的。

    接下來就是對注解進行解析了,主要是對方法上的注解進行解析,那么可以拿到httpMethod以及初步的url(包含占位符)。

    后面是對方法中參數中的注解進行解析,這一步會拿到很多的ParameterHandler對象,該對象在toRequest()構造Request的時候調用其apply方法。

    ok,這里我們并沒有去一行一行查看代碼,其實意義也不太大,只要知道ServiceMethod主要用于將我們接口中的方法轉化為一個Request對象,于是根據我們的接口返回值確定了responseConverter,解析我們方法上的注解拿到初步的url,解析我們參數上的注解拿到構建RequestBody所需的各種信息,最終調用toRequest的方法完成Request的構建。

    • 接下來看OkHttpCall的構建,構造函數僅僅是簡單的賦值
    OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {this.serviceMethod = serviceMethod;this.args = args;}12341234
    • 最后一步是serviceMethod.callAdapter.adapt(okHttpCall)

    我們已經確定這個callAdapter是ExecutorCallAdapterFactory.get()對應代碼為:

    final class ExecutorCallAdapterFactory extends CallAdapter.Factory {final Executor callbackExecutor;ExecutorCallAdapterFactory(Executor callbackExecutor) {this.callbackExecutor = callbackExecutor;}@Overridepublic CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {if (getRawType(returnType) != Call.class) {return null;}final Type responseType = Utils.getCallResponseType(returnType);return new CallAdapter<Call<?>>() {@Override public Type responseType() {return responseType;}@Override public <R> Call<R> adapt(Call<R> call) {return new ExecutorCallbackCall<>(callbackExecutor, call);}};}

    可以看到adapt返回的是ExecutorCallbackCall對象,繼續往下看:

    static final class ExecutorCallbackCall<T> implements Call<T> {final Executor callbackExecutor;final Call<T> delegate;ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.callbackExecutor = callbackExecutor;this.delegate = delegate;}@Override public void enqueue(final Callback<T> callback) {if (callback == null) throw new NullPointerException("callback == null");delegate.enqueue(new Callback<T>() {@Override public void onResponse(Call<T> call, final Response<T> response) {callbackExecutor.execute(new Runnable() {@Override public void run() {if (delegate.isCanceled()) {// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}}});}@Override public void onFailure(Call<T> call, final Throwable t) {callbackExecutor.execute(new Runnable() {@Override public void run() {callback.onFailure(ExecutorCallbackCall.this, t);}});}});}@Override public Response<T> execute() throws IOException {return delegate.execute();}}

    可以看出ExecutorCallbackCall僅僅是對Call對象進行封裝,類似裝飾者模式,只不過將其執行時的回調通過callbackExecutor進行回調到UI線程中去了。

    4.2.3 執行Call

    在4.2.2我們已經拿到了經過封裝的ExecutorCallbackCall類型的call對象,實際上就是我們實際在寫代碼時拿到的call對象,那么我們一般會執行enqueue方法,看看源碼是怎么做的

    首先是ExecutorCallbackCall.enqueue方法,代碼在4.2.2,可以看到除了將onResponse和onFailure回調到UI線程,主要的操作還是delegate完成的,這個delegate實際上就是OkHttpCall對象,我們看它的enqueue方法

    @Override public void enqueue(final Callback<T> callback) {okhttp3.Call call;Throwable failure;synchronized (this){if (executed) throw new IllegalStateException("Already executed.");executed = true;try{call = rawCall = createRawCall();} catch (Throwable t){failure = creationFailure = t;}}if (failure != null){callback.onFailure(this, failure);return;}if (canceled){call.cancel();}call.enqueue(new okhttp3.Callback(){@Overridepublic void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)throws IOException{Response<T> response;try{response = parseResponse(rawResponse);} catch (Throwable e){callFailure(e);return;}callSuccess(response);}@Overridepublic void onFailure(okhttp3.Call call, IOException e){try{callback.onFailure(OkHttpCall.this, e);} catch (Throwable t){t.printStackTrace();}}private void callFailure(Throwable e){try{callback.onFailure(OkHttpCall.this, e);} catch (Throwable t){t.printStackTrace();}}private void callSuccess(Response<T> response){try{callback.onResponse(OkHttpCall.this, response);} catch (Throwable t){t.printStackTrace();}}}); }

    沒有任何神奇的地方,內部實際上就是okhttp的Call對象,也是調用okhttp3.Call.enqueue方法。

    中間對于okhttp3.Call的創建代碼為:

    private okhttp3.Call createRawCall() throws IOException {Request request = serviceMethod.toRequest(args);okhttp3.Call call = serviceMethod.callFactory.newCall(request);if (call == null){throw new NullPointerException("Call.Factory returned null.");}return call; }

    可以看到,通過serviceMethod.toRequest完成對request的構建,通過request去構造call對象,然后返回.

    中間還涉及一個parseResponse方法,如果順利的話,執行的代碼如下:

    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody = rawResponse.body();ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);T body = serviceMethod.toResponse(catchingBody);return Response.success(body, rawResponse);

    通過serviceMethod對ResponseBody進行轉化,然后返回,轉化實際上就是通過responseConverter的convert方法。

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

    ok,關于responseConverter后面還會細說,不用擔心。

    到這里,我們整個源碼的流程分析就差不多了,目的就掌握一個大體的原理和執行流程,了解下幾個核心的類。

    那么總結一下:

    • 首先構造retrofit,幾個核心的參數呢,主要就是baseurl,callFactory(默認okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
    • 然后通過create方法拿到接口的實現類,這里利用Java的Proxy類完成動態代理的相關代理
    • 在invoke方法內部,拿到我們所聲明的注解以及實參等,構造ServiceMethod,ServiceMethod中解析了大量的信息,最痛可以通過toRequest構造出okhttp3.Request對象。有了okhttp3.Request對象就可以很自然的構建出okhttp3.call,最后calladapter對Call進行裝飾返回。
    • 拿到Call就可以執行enqueue或者execute方法了

    ok,了解這么多足以。

    下面呢,有幾個地方需要注意,一方面是一些特殊的細節;另一方面就是Converter。

    5. retrofit中的各類細節

    5.1 上傳文件中使用的奇怪value值

    第一個問題涉及到文件上傳,還記得我們在單文件上傳那里所說的嗎?有種類似于hack的寫法,上傳文件是這么做的?

    public interface ApiInterface {@Multipart@POST ("/api/Accounts/editaccount")Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username);}

    首先我們一點明確,因為這里使用了@ Multipart,那么我們認為@Part應當支持普通的key-value,以及文件。

    對于普通的key-value是沒問題的,只需要這樣@Part("username") String username。

    那么對于文件,為什么需要這樣呢?@Part("file_key\"; filename=\"pp.png")

    這個value設置的值不用看就會覺得特別奇怪,然而卻可以正常執行,原因是什么呢?

    原因是這樣的:

    當上傳key-value的時候,實際上對應這樣的代碼:

    builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),RequestBody.create(null, params.get(key)));1212

    也就是說,我們的@Part轉化為了

    Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")11

    這么一看,很隨意,只要把key放進去就可以了。

    但是,retrofit2并沒有對文件做特殊處理,文件的對應的字符串應該是這樣的

    Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");11

    與鍵值對對應的字符串相比,多了個;filename="filename.png,就因為retrofit沒有做特殊處理,所以你現在看這些hack的做法

    @Part("file_key\"; filename=\"pp.png") 拼接:==> Content-Disposition", "form-data; name=\"" + key + "\" 結果:==> Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"1234512345

    ok,到這里我相信你已經理解了,為什么要這么做,而且為什么這么做可以成功!

    恩,值得一提的事,因為這種方式文件名寫死了,我們上文使用的的是@Part MultipartBody.Part file,可以滿足文件名動態設置,這個方式貌似也是2.0.1的時候支持的。

    上述相關的源碼:

    // ServiceMethod if (annotation instanceof Part) {if (!isMultipart) {throw parameterError(p, "@Part parameters can only be used with multipart encoding.");}Part part = (Part) annotation;gotPart = true;String partName = part.value();Headers headers =Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"","Content-Transfer-Encoding", part.encoding()); }

    可以看到呢,并沒有對文件做特殊處理,估計下個版本說不定@Part會多個isFile=true|false屬性,甚至修改對應形參,然后在這里做簡單的處理。

    ok,最后來到關鍵的ConverterFactory了~

    6. 自定義Converter.Factory

    6.1 responseBodyConverter

    關于Converter.Factory,肯定是通過addConverterFactory設置的

    Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).build();123123

    該方法接受的是一個Converter.Factory factory對象

    該對象呢,是一個抽象類,內部包含3個方法:

    abstract class Factory {public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,Retrofit retrofit) {return null;}public Converter<?, RequestBody> requestBodyConverter(Type type,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {return null;}public Converter<?, String> stringConverter(Type type, Annotation[] annotations,Retrofit retrofit) {return null;}}

    可以看到呢,3個方法都是空方法而不是抽象的方法,也就表明了我們可以選擇去實現其中的1個或多個方法,一般只需要關注requestBodyConverter和responseBodyConverter就可以了。

    ok,我們先看如何自定義,最后再看GsonConverterFactory.create的源碼。

    先來個簡單的,實現responseBodyConverter方法,看這個名字很好理解,就是將responseBody進行轉化就可以了。

    ok,這里呢,我們先看一下上述中我們使用的接口:

    package com.zhy.retrofittest.userBiz;public interface IUserBiz {@GET("users")Call<List<User>> getUsers();@POST("users")Call<List<User>> getUsersBySort(@Query("sort") String sort);@GET("{username}")Call<User> getUser(@Path("username") String username);@POST("add")Call<List<User>> addUser(@Body User user);@POST("login")@FormUrlEncodedCall<User> login(@Field("username") String username, @Field("password") String password);@Multipart@POST("register")Call<User> registerUser(@Part("photos") RequestBody photos, @Part("username") RequestBody username, @Part("password") RequestBody password);@Multipart@POST("register")Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);@GET("download")Call<ResponseBody> downloadTest();}

    不知不覺,方法還蠻多的,假設哈,我們這里去掉retrofit構造時的GsonConverterFactory.create,自己實現一個Converter.Factory來做數據的轉化工作。

    首先我們解決responseBodyConverter,那么代碼很簡單,我們可以這么寫:

    public class UserConverterFactory extends Converter.Factory {@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){//根據type判斷是否是自己能處理的類型,不能的話,return null ,交給后面的Converter.Factoryreturn new UserConverter(type);}}public class UserResponseConverter<T> implements Converter<ResponseBody, T> {private Type type;Gson gson = new Gson();public UserResponseConverter(Type type){this.type = type;}@Overridepublic T convert(ResponseBody responseBody) throws IOException{String result = responseBody.string();T users = gson.fromJson(result, type);return users;} }

    使用的時候呢,可以

    Retrofit retrofit = new Retrofit.Builder() .callFactory(new OkHttpClient()) .baseUrl("http://example/springmvc_users/user/") //.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(new UserConverterFactory()).build();1234512345

    ok,這樣的話,就可以完成我們的ReponseBody到List<User>或者User的轉化了。

    可以看出,我們這里用的依然是Gson,那么有些同學肯定不希望使用Gson就能實現,如果不使用Gson的話,一般需要針對具體的返回類型,比如我們針對返回List<User>或者User

    你可以這么寫:

    package com.zhy.retrofittest.converter; /*** Created by zhy on 16/4/30.*/ public class UserResponseConverter<T> implements Converter<ResponseBody, T> {private Type type;Gson gson = new Gson();public UserResponseConverter(Type type){this.type = type;}@Overridepublic T convert(ResponseBody responseBody) throws IOException{String result = responseBody.string();if (result.startsWith("[")){return (T) parseUsers(result);} else{return (T) parseUser(result);}}private User parseUser(String result){JSONObject jsonObject = null;try{jsonObject = new JSONObject(result);User u = new User();u.setUsername(jsonObject.getString("username"));return u;} catch (JSONException e){e.printStackTrace();}return null;}private List<User> parseUsers(String result){List<User> users = new ArrayList<>();try{JSONArray jsonArray = new JSONArray(result);User u = null;for (int i = 0; i < jsonArray.length(); i++){JSONObject jsonObject = jsonArray.getJSONObject(i);u = new User();u.setUsername(jsonObject.getString("username"));users.add(u);}} catch (JSONException e){e.printStackTrace();}return users;} }

    這里簡單讀取了一個屬性,大家肯定能看懂,這樣就能滿足返回值是Call<List<User>>或者Call<User>.

    這里鄭重提醒:如果你針對特定的類型去寫Converter,一定要在UserConverterFactory#responseBodyConverter中對類型進行檢查,發現不能處理的類型return null,這樣的話,可以交給后面的Converter.Factory處理,比如本例我們可以按照下列方式檢查:

    public class UserConverterFactory extends Converter.Factory {@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){//根據type判斷是否是自己能處理的類型,不能的話,return null ,交給后面的Converter.Factoryif (type == User.class)//支持返回值是User{return new UserResponseConverter(type);}if (type instanceof ParameterizedType)//支持返回值是List<User>{Type rawType = ((ParameterizedType) type).getRawType();Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];if (rawType == List.class && actualType == User.class){return new UserResponseConverter(type);}}return null;}}

    好了,到這呢responseBodyConverter方法告一段落了,謹記就是將reponseBody->返回值返回中的實際類型,例如Call<User>中的User;還有對于該converter不能處理的類型一定要返回null。

    6.2 requestBodyConverter

    ok,上面接口一大串方法呢,使用了我們的Converter之后,有個方法我們現在還是不支持的。

    @POST("add") Call<List<User>> addUser(@Body User user);1212

    ok,這個@Body需要用到這個方法,叫做requestBodyConverter,根據參數轉化為RequestBody,下面看下我們如何提供支持。

    public class UserRequestBodyConverter<T> implements Converter<T, RequestBody> {private Gson mGson = new Gson();@Overridepublic RequestBody convert(T value) throws IOException{String string = mGson.toJson(value);return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string);} }

    然后在UserConverterFactory中復寫requestBodyConverter方法,返回即可:

    public class UserConverterFactory extends Converter.Factory {@Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){return new UserRequestBodyConverter<>();} }

    這里偷了個懶,使用Gson將對象轉化為json字符串了,如果你不喜歡使用框架,你可以選擇拼接字符串,或者反射寫一個支持任何對象的,反正就是對象->json字符串的轉化。最后構造一個RequestBody返回即可。

    ok,到這里,我相信如果你看的細致,自定義Converter.Factory是干嘛的,但是我還是要總結下:

    • responseBodyConverter 主要是對應@Body注解,完成ResponseBody到實際的返回類型的轉化,這個類型對應Call<XXX>里面的泛型XXX,其實@Part等注解也會需要responseBodyConverter,只不過我們的參數類型都是RequestBody,由默認的converter處理了。
    • requestBodyConverter 完成對象到RequestBody的構造。
    • 一定要注意,檢查type如果不是自己能處理的類型,記得return null (因為可以添加多個,你不能處理return null ,還會去遍歷后面的converter).

    7. 值得學習的API

    其實一般情況下看源碼呢,可以讓我們更好的去使用這個庫,當然在看的過程中如果發現了一些比較好的處理方式呢,是非常值得記錄的。如果每次看別人的源碼都能吸取一定的精華,比你單純的去理解會好很多,因為你的記憶力再好,源碼解析你也是會忘的,而你記錄下來并能夠使用的優越的代碼,可能用久了就成為你的代碼了。

    我舉個例子:比如retrofit2中判斷當前運行的環境代碼如下,如果下次你有這樣的需求,你也可以這么寫,甚至源碼中根據不同的運行環境還提供了不同的Executor都很值得記錄:

    class Platform {private static final Platform PLATFORM = findPlatform();static Platform get() {return PLATFORM;}private static Platform findPlatform() {try {Class.forName("android.os.Build");if (Build.VERSION.SDK_INT != 0) {return new Android();}} catch (ClassNotFoundException ignored) {}try {Class.forName("java.util.Optional");return new Java8();} catch (ClassNotFoundException ignored) {}try {Class.forName("org.robovm.apple.foundation.NSObject");return new IOS();} catch (ClassNotFoundException ignored) {}return new Platform();}

    ok,文章結束,最后打個廣告,個人微信公眾號目前關注大概9000人左右,無奈個人博文更新頻率無法做到很好的為這么多人服務,所以本公眾號歡迎大家投稿,如果你希望你的文章可以被更多人看到,直接將md、doc等格式的文章發我郵箱即可(623565791@qq.com),也可以加我好友,需要注明(投稿),謝謝。

    文章要求:原創+你覺得通過文章你能學到一些有價值的東西。

    福利:您的文章還可以發到csdn,簡書等其他平臺,但不能投稿至其他微信公眾號了;更多人能夠發現你的文章(好文我會幫你發送至我的各大群)+您的署名+您的原文鏈接,如果后期有打賞收益均歸投稿人所有。

    總結

    以上是生活随笔為你收集整理的Retrofit2 完全解析 探索与okhttp之间的关系的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    国产亚洲aⅴaaaaaa毛片 | 欧美久久影院 | 综合在线观看 | 午夜一级免费电影 | 三级av免费观看 | 亚洲在线激情 | 久久观看免费视频 | 色姑娘综合天天 | 国产色拍拍拍拍在线精品 | 在线看av的网址 | 日本一区二区高清不卡 | 婷婷色网| 久久久影院一区二区三区 | 中文字幕在线播放日韩 | 免费久久片| 国产黄a三级三级 | 日本久久精品视频 | 日韩高清一二区 | 欧美日本国产在线观看 | 欧美精彩视频在线观看 | 黄色影院在线免费观看 | 国产一卡二卡在线 | 国产中文字幕视频在线 | 欧美资源在线观看 | 丁香色综合 | 日韩啪啪小视频 | 久久综合久久综合这里只有精品 | 一区二区三区四区五区在线视频 | 国产又粗又硬又长又爽的视频 | 中文字幕资源站 | 99久久婷婷 | 91久久精品一区二区三区 | 亚洲成 人精品 | 天天综合网~永久入口 | 日韩精品电影在线播放 | 中文字幕在线看视频国产中文版 | 天天爱天天色 | 爱射综合 | 国内免费的中文字幕 | 日日天天狠狠 | 天天射天天干天天 | 久久综合射 | 91黄色免费网站 | 99在线精品免费视频九九视 | 蜜臀久久99精品久久久久久网站 | 亚洲aⅴ免费在线观看 | 日本中文字幕在线免费观看 | 国产精品久久久久久一区二区 | 日本韩国精品一区二区在线观看 | 最近最新最好看中文视频 | 精品亚洲欧美一区 | 狠狠精品 | 国产一级一片免费播放放 | 免费在线观看一区 | 日韩色中色 | 色综合久久综合 | 久草99| 九九久久电影 | 特级黄色电影 | 91高清视频免费 | 一本一道久久a久久综合蜜桃 | 国产精品自产拍在线观看网站 | 激情中文在线 | 国产综合在线观看视频 | 中文字幕免费看 | www.国产视频 | 成人黄色在线观看视频 | www日日| 久草国产在线观看 | 色综合久久久久综合体桃花网 | 亚洲视频在线观看网站 | 天天干天天干天天色 | 中文字幕中文字幕在线中文字幕三区 | 丰满少妇在线观看网站 | 精品国产aⅴ一区二区三区 在线直播av | 99视频免费 | 国产视频一区二区在线播放 | 国产原创在线 | 色婷婷视频网 | 中文字幕免费观看视频 | 亚洲成人资源在线观看 | 久久国产综合视频 | 国产精品毛片一区视频播 | 国产高清不卡在线 | 日韩成人免费电影 | 黄色官网在线观看 | 天天插伊人| 欧美成人xxxx| 国产精品专区h在线观看 | 欧美极品xxx| 九九热在线播放 | 国产精品99久久久久人中文网介绍 | 久久精品亚洲综合专区 | 日本精品久久久久 | 91资源在线免费观看 | 久久精品久久精品 | 很黄很黄的网站免费的 | 91资源在线| 国产亲近乱来精品 | 免费观看全黄做爰大片国产 | 精品1区2区 | 蜜臀av性久久久久蜜臀aⅴ流畅 | avove黑丝| 亚洲区视频在线观看 | 久久成人综合视频 | 不卡av在线 | 久久av免费观看 | 国产色视频一区二区三区qq号 | 亚洲国产精品人久久电影 | 国产一区 在线播放 | 成人a视频片观看免费 | 成片人卡1卡2卡3手机免费看 | 亚洲黄网址 | 亚洲波多野结衣 | 亚洲精品系列 | 中文字幕电影网 | 国产不卡在线观看 | 久草在在线视频 | 色综合天天综合网国产成人网 | 久久视频国产精品免费视频在线 | 免费观看成人av | 亚洲精品乱码久久久久久 | 狠狠操狠狠干2017 | 国产精品乱码一区二三区 | 911香蕉 | 天天夜夜狠狠操 | 国产在线播放一区二区 | 超级碰99 | 五月天视频网站 | 久久国产电影院 | 欧美日韩国产亚洲乱码字幕 | 黄网站色视频免费观看 | 99精品国产一区二区三区不卡 | 婷婷在线观看视频 | av天天色 | 中文字幕在线一二 | 国产亚洲激情视频在线 | 日日干 天天干 | 久久久久中文字幕 | 婷婷在线精品视频 | 久久精品国产亚洲aⅴ | 伊人狠狠色丁香婷婷综合 | 精品亚洲网 | 久久国产色 | 国产精品久久一区二区无卡 | 午夜在线免费观看 | 天无日天天操天天干 | 少妇超碰在线 | 亚洲专区视频在线观看 | 日韩一级黄色av | 三级av免费看 | 五月开心网 | 亚洲精品理论片 | 69国产盗摄一区二区三区五区 | 精品亚洲va在线va天堂资源站 | 亚洲午夜av久久乱码 | 成人av高清 | 国产精品女人久久久久久 | 国产99久久久国产精品成人免费 | 免费观看www小视频的软件 | 成年人av在线播放 | 成人91在线 | 日韩理论电影在线 | 91精品国产成人观看 | 中文在线天堂资源 | 99视频精品免费观看, | 美女黄久久 | 亚洲一区动漫 | 2024国产精品视频 | 精品国产伦一区二区三区观看方式 | 国产麻豆精品传媒av国产下载 | 97精品视频在线播放 | 狠狠色香婷婷久久亚洲精品 | 久久精品久久99精品久久 | 亚洲黄色app | 在线观看中文字幕一区二区 | 久久精品看| 亚洲国产精品成人av | 国产一区电影在线观看 | 久久露脸国产精品 | 97超碰网 | 久草视频在线看 | 久久夜夜爽 | 午夜精品久久 | 青青久草在线 | 国产精品免费视频久久久 | 天天操夜夜叫 | 国产精品九九九 | 国产在线看一区 | 国产日韩一区在线 | 久久艹在线 | 免费男女羞羞的视频网站中文字幕 | 亚洲精品456在线播放第一页 | 国产在线国偷精品产拍 | 一区二区视频电影在线观看 | 国产免费观看久久黄 | 天天激情 | 亚洲天堂精品视频 | 激情片av | 精品美女久久久久久免费 | 久久伦理电影网 | 中文字幕在线观看免费 | 美女网站黄在线观看 | 中文视频在线播放 | 天天操天天操天天操天天操天天操 | 国产一区欧美在线 | 免费 在线 中文 日本 | 成人午夜毛片 | 中文字幕999 | 久久精精品视频 | 色婷婷综合久久久久中文字幕1 | 成人黄色在线 | 久久免费视频这里只有精品 | 精品成人在线 | 五月婷婷综合在线视频 | 久久久影院一区二区三区 | 日韩综合在线观看 | 五月婷婷色播 | 不卡电影免费在线播放一区 | 久久久久久久av麻豆果冻 | 中文字幕av有码 | 国产麻豆精品久久一二三 | 婷婷综合在线 | 最近中文字幕完整视频高清1 | 91在线视频导航 | 国产不卡视频在线播放 | 精品一区 精品二区 | 麻豆一精品传二传媒短视频 | 成 人 免费 黄 色 视频 | 精品中文字幕在线播放 | 在线免费观看麻豆 | 欧美成年人在线观看 | 99热在线国产精品 | 国产专区视频在线 | 97影视| 亚洲成人精品在线 | av成人免费在线 | 97热视频 | 六月丁香色婷婷 | 在线精品视频免费播放 | 奇米网777| 国产高清免费在线观看 | 人人看人人做人人澡 | 99热这里是精品 | 天天干视频在线 | 久久久久久久综合色一本 | 亚洲黄色成人网 | 午夜精品久久久久久99热明星 | 天天射天天舔天天干 | 天天久久夜夜 | www.av小说 | 伊人超碰在线 | 99久久超碰中文字幕伊人 | 激情五月婷婷综合 | 国产一区在线精品 | 午夜10000| www.国产精品 | 黄色免费网| 最新av电影网址 | 97超碰国产精品女人人人爽 | 狠狠狠狠狠狠干 | 韩国av在线 | 伊人亚洲精品 | 欧美乱熟臀69xxxxxx | 国内精品视频在线播放 | 国产a国产a国产a | 午夜12点 | 一级片免费视频 | 九色精品免费永久在线 | 在线观看理论 | 天天爱天天射 | 四虎影视成人精品国库在线观看 | 日韩免费在线观看网站 | 中文字幕一区二区三区四区久久 | 精品久久久久久综合 | 日本精品xxxx| 国产成人亚洲在线电影 | 国产精品成人国产乱一区 | 国产成人精品久 | 91麻豆看国产在线紧急地址 | 国产精品嫩草在线 | 国产免费人成xvideos视频 | 精品一区二区电影 | 亚洲人片在线观看 | a在线一区 | 麻豆视频免费播放 | 国产一级片观看 | 国产精品18久久久久久vr | 国产成人精品午夜在线播放 | 在线亚洲激情 | 日韩一区二区免费在线观看 | 久久久福利视频 | 狠狠狠狠狠狠干 | 亚洲精品久久久久久久蜜桃 | 一区二区三区免费在线观看视频 | 亚洲理论在线 | 婷婷色综合 | av在线精品 | 91成人亚洲| 久久99国产精品久久 | 精品视频免费久久久看 | 国产一级免费在线观看 | 四虎成人精品在永久免费 | 天天射天天色天天干 | 亚洲精品视频www | 国产日韩欧美在线一区 | 玖玖视频在线 | www欧美xxxx| 香蕉在线播放 | 久久久高清视频 | 欧美成人精品三级在线观看播放 | 婷婷色六月天 | 亚洲精品美女视频 | 国产99一区 | 中文字幕在线观看免费高清电影 | 婷婷激情在线 | 99视屏| 99久久国产免费,99久久国产免费大片 | 在线观看视频97 | 欧美男同视频网站 | 精品视频免费观看 | 最近日本韩国中文字幕 | 久久九九免费 | 91成人网在线观看 | 激情久久五月 | 欧美午夜理伦三级在线观看 | 天天色天天射天天综合网 | 精品久久久免费视频 | 美女网站在线免费观看 | 国内精品在线看 | 美女黄久久 | 五月激情六月丁香 | 欧美精品久久久久久久 | 最新高清无码专区 | 麻豆国产在线视频 | 九九热免费视频在线观看 | 亚洲欧美日韩精品久久久 | 人人爽人人爽人人爽人人爽 | 欧美日韩另类视频 | 激情综合网五月 | 蜜桃久久久 | av经典在线 | 91完整版| 精品久久久一区二区 | 天天伊人狠狠 | 国产在线探花 | 欧美日韩在线免费观看 | 天天操天天艹 | 丝袜+亚洲+另类+欧美+变态 | 亚洲黄色免费网站 | 免费视频久久久久 | 国产日韩精品一区二区 | 欧美激情xxxx性bbbb | 国产视频欧美视频 | 欧美一级久久久久 | 在线高清一区 | 日日躁天天躁 | 又色又爽又黄 | 久久精品综合一区 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 西西人体www444 | 亚洲成人软件 | 九九精品毛片 | 欧美日韩免费在线观看视频 | 中文字幕日韩高清 | 在线香蕉视频 | 天天操天天色天天 | 欧美一区二区精美视频 | 亚洲精品日韩一区二区电影 | 亚洲国产精品500在线观看 | 黄色三级av | 天天射天天干天天爽 | 国产97在线看 | 久久欧美综合 | 日韩伦理片hd | 国产 日韩 在线 亚洲 字幕 中文 | 一区二区中文字幕在线 | 视频 国产区 | 亚洲国产小视频在线观看 | av资源中文字幕 | 久久成年人视频 | 久久香蕉影视 | 97av在线视频| 国产视频精品网 | 国产高清黄 | 日本一区二区高清不卡 | 日日日操| 天天天天天天操 | 久久免费视频这里只有精品 | 99久免费精品视频在线观看 | 国产精品久久精品国产 | 久久久免费看视频 | 国产精品黄色 | 激情丁香月 | 日韩在线首页 | 国产日本三级 | 国产尤物在线 | 国产亚洲视频在线免费观看 | 香蕉视频在线免费 | 最近2019年日本中文免费字幕 | 久久精品久久精品久久39 | 中文字幕日本在线 | 亚洲精品视频在线观看网站 | 日日夜夜精品免费观看 | 欧美日韩aaaa | 久久黄色网址 | 欧美日韩二区三区 | 国产不卡一 | 日韩av不卡在线观看 | 精品999久久久 | 91成人免费在线视频 | 国内久久久久 | 91中文字幕在线观看 | 中文有码在线 | 天天操天天添 | 精精国产xxxx视频在线播放 | 91丨九色丨首页 | 久久99操| 996久久国产精品线观看 | 91精品国产99久久久久久久 | 黄色片免费在线 | 久久99精品视频 | 九九热只有这里有精品 | 欧美天天干 | 久久综合九色综合97婷婷女人 | 欧美一区二区在线刺激视频 | 日韩和的一区二在线 | 国产精品久久久久久影院 | 999久久a精品合区久久久 | 国产精品入口传媒 | 久久这里精品视频 | 91麻豆网站| 国产精品久久电影网 | 97人人爽| 久草国产在线观看 | 成人av网站在线播放 | 久久久久久久av | 国产一区二区在线观看免费 | 欧美日韩国产亚洲乱码字幕 | 91网址在线 | 国产精品高潮呻吟久久av无 | 综合天天 | 美女视频黄的免费的 | 日本一区二区免费在线观看 | 久久精品欧美日韩精品 | 在线免费视频a | 精品免费久久久久 | 国产精品美女久久久久久久 | 久久资源在线 | 日韩免费一区二区在线观看 | 韩国av免费在线 | 精品一区二区综合 | 97在线看 | 欧美日韩中文视频 | 国产69精品久久99的直播节目 | 久久久 精品 | 日韩免费视频播放 | 中文字幕一区在线观看视频 | 欧美精品一区在线发布 | 日韩成人精品在线观看 | 亚洲精品乱码久久久久久蜜桃不爽 | 欧美另类重口 | 国产a国产| www国产精品com | 中文字幕资源网 | 欧美亚洲国产精品久久高清浪潮 | 日韩天天操 | 久久国产精品网站 | 天天舔天天射天天操 | 免费观看性生活大片3 | 国产精品女同一区二区三区久久夜 | 一二三四精品 | 国产精品片 | 9999国产| 99视频免费在线观看 | 久精品一区 | 99精品免费在线观看 | 国产一区网 | 精品黄色视 | 超碰人人做 | 视色网站 | 国产精品美女久久久久久久 | 日韩高清二区 | 日本精品视频在线播放 | 久久精品精品电影网 | 日日夜夜狠狠干 | 国产精品去看片 | 精品a视频 | 黄色一级大片免费看 | 久久视频免费观看 | 色网站免费在线看 | 亚洲精品五月天 | 国产精品av在线免费观看 | av在线免费在线观看 | 中文字幕在线观看网站 | 999亚洲国产996395 | 91女神的呻吟细腰翘臀美女 | 操操碰 | 日韩一区在线播放 | 国产视频二区三区 | 丁香五婷 | 亚洲人成在线观看 | 国产视频一区二区在线观看 | 超碰97在线资源 | 黄色毛片网站在线观看 | 国产精品18久久久久久久久久久久 | 综合久久久 | 麻豆91在线观看 | 天天操天天射天天爱 | 久久精品视频在线免费观看 | 国产精品一区二区三区在线免费观看 | 日韩城人在线 | 成人黄色小说在线观看 | 免费日韩av电影 | 中文成人字幕 | 丁香5月婷婷久久 | 少妇av片| 久久久精品午夜 | 国产字幕在线看 | 91热视频在线观看 | 久日视频 | 亚洲国产一区在线观看 | 91精品欧美一区二区三区 | 最新91在线视频 | 欧美激情亚洲综合 | 一区二区三区久久 | 18国产精品白浆在线观看免费 | av色综合网 | 狠狠网站 | 不卡的一区二区三区 | 99热这里精品| 欧美成人影音 | 特黄特黄的视频 | 黄av在线| 国产美女视频网站 | 日本免费久久高清视频 | 人人看看人人 | 欧美激情精品久久久 | 天天看天天干 | 国产美女视频免费观看的网站 | 日韩色中色 | 日韩精品91偷拍在线观看 | 天天艹天天 | 亚洲国产精品视频在线观看 | 国产91精品高清一区二区三区 | 人人艹人人 | 免费视频三区 | 精品国产伦一区二区三区免费 | 欧美一级电影免费观看 | 五月婷在线播放 | 国产精品美乳一区二区免费 | 国产精品a级| 99精品久久久久久久 | 97成人在线观看 | 亚洲高清不卡av | 91九色porny在线 | 久久视频国产精品免费视频在线 | 91精品国产高清 | 97视频一区 | av线上看 | 国产综合久久 | 91完整版在线观看 | 久久99久久99精品免费看小说 | 99热在线精品观看 | 欧美福利视频 | 日韩最新中文字幕 | 精品1区2区 | 国产九九精品视频 | 天天操天天射天天插 | 日韩av综合网站 | 婷婷中文字幕 | 久久久精品国产一区二区三区 | 久久久高清一区二区三区 | 国产不卡精品视频 | 国产在线免费av | 久久综合久久久 | 69av免费视频 | 国产手机视频在线播放 | 国产黄色成人av | 麻豆手机在线 | 99精品欧美一区二区三区 | 韩日av一区二区 | 免费看十八岁美女 | 亚洲欧美视频 | 国产在线欧美日韩 | 91香蕉视频 | 亚洲精品日韩一区二区电影 | 国产亚洲精品美女久久 | av三级在线免费观看 | 久久国产视频网 | 色视频 在线 | 亚洲国产精品女人久久久 | 亚洲乱码在线 | 99热99热| 人九九精品 | 麻豆你懂的 | 色亚洲网 | 激情久久五月天 | 欧美日韩国产精品爽爽 | 国产精品视频免费观看 | 国产高清成人av | 美女视频黄免费的 | 免费高清av在线看 | 久久精品香蕉 | 日本一区二区免费在线观看 | 精品色综合| 欧美成人精品欧美一级乱 | 美女精品网站 | 亚洲一区二区视频在线播放 | 久久精品免视看 | 国产精品美女久久久久久免费 | 日韩精选在线观看 | 91九色国产蝌蚪 | 国产一区在线看 | 色99色| 欧美日韩国产精品一区二区三区 | 日韩最新在线视频 | 四虎成人精品永久免费av九九 | 亚洲人成免费网站 | 国产精品久久久久久久久久白浆 | 中文字幕一区二区三区在线观看 | 国产中文字幕在线免费观看 | 色狠狠综合 | 日韩色在线观看 | 国产 一区二区三区 在线 | 一区二区视频在线观看免费 | 狠狠操狠狠干2017 | 国产精品免费一区二区三区在线观看 | 青青看片 | 亚洲 综合 激情 | 特级黄色一级 | 国产麻豆精品传媒av国产下载 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 激情欧美一区二区三区 | 日韩激情小视频 | 午夜电影久久久 | 免费热情视频 | 国产日韩精品在线观看 | 麻豆免费视频网站 | 亚洲免费色| 亚洲精选视频免费看 | 国产精品久久久久毛片大屁完整版 | 国产专区视频在线观看 | 99精品免费观看 | 欧美日韩在线免费观看视频 | 在线观看mv的中文字幕网站 | 欧美另类z0zx | www.亚洲黄 | 国产一级片免费播放 | 亚洲,播放 | 精品国产乱子伦一区二区 | 99精品久久只有精品 | 99精品观看 | www.夜夜干.com | 日韩电影在线观看一区 | 久久久国产毛片 | 97精品超碰一区二区三区 | 四虎永久精品在线 | 日韩一级黄色大片 | 久久99久久99精品免费看小说 | 亚洲天堂精品视频 | 精品欧美一区二区三区久久久 | 色婷婷综合视频在线观看 | 在线观看黄网站 | 国产精品久久久久永久免费看 | 国产麻豆精品传媒av国产下载 | 久久久黄色免费网站 | 国产成人精品综合久久久 | 久久免视频 | 色综合久久88色综合天天免费 | 伊人天天| 久久久久亚洲a | 深夜国产在线 | 国产精品视频你懂的 | 韩国视频一区二区三区 | 精品一区电影国产 | 成人免费在线观看入口 | 日本最大色倩网站www | 伊人色播 | 亚洲最新av在线 | 九九九在线 | 美州a亚洲一视本频v色道 | 97超碰免费在线观看 | av一级久久 | 国产 一区二区三区 在线 | 国产精品高清在线 | 特级aaa毛片 | 国产又粗又硬又爽视频 | 久久久精品网 | 黄色大片av | 在线亚洲播放 | 欧美在线视频精品 | 午夜私人影院久久久久 | 日韩高清久久 | 亚洲欧美视频在线 | 亚洲天天在线 | 亚洲在线视频网站 | 深爱激情五月综合 | 久久久久激情视频 | 超级碰碰视频 | 18性欧美xxxⅹ性满足 | 日日操夜夜操狠狠操 | 99热这里精品 | 高清中文字幕av | 亚洲成色777777在线观看影院 | 欧美在线日韩在线 | 国产区第一页 | 欧美婷婷色 | 国产电影一区二区三区四区 | 黄色成年片 | 久久视频99 | 在线观看免费 | 国产精品久久电影网 | 亚洲一区二区天堂 | 成人9ⅰ免费影视网站 | 91av色| 国产伦精品一区二区三区四区视频 | 国产精品大片在线观看 | 四川bbb搡bbb爽爽视频 | 久久精品8| 亚洲视频在线免费观看 | 精品1区2区 | 最近中文字幕国语免费av | 黄色精品在线看 | 成人黄色大片在线免费观看 | 在线看v片成人 | 久久久香蕉视频 | 狠狠色狠狠色 | 精品亚洲免费 | 亚洲闷骚少妇在线观看网站 | 日韩美av在线 | 中文字幕久久精品一区 | 日韩欧美电影在线观看 | av网站有哪些 | 成人精品国产 | 五月天婷婷免费视频 | 四虎免费在线观看视频 | 色综合久久久久久久 | 欧美性色黄大片在线观看 | 中文av在线免费观看 | 亚洲婷婷在线 | 四虎免费av| 免费国产在线视频 | 中文字幕欧美日韩va免费视频 | 中文字幕888| 日韩专区视频 | 在线观看av国产 | 久久国产精品电影 | www中文在线 | 欧美专区亚洲专区 | 97人人模人人爽人人喊网 | 国产精品一区二区精品视频免费看 | 超碰人人超 | 98超碰人人 | 免费观看www7722午夜电影 | 激情综合电影网 | 国产成年免费视频 | 亚洲另类视频在线观看 | 免费av一级电影 | www日| 久操视频在线观看 | 免费观看日韩av | 91精品电影| 日韩电影在线观看一区二区 | 成人丁香花 | 伊人婷婷久久 | 九九热免费在线观看 | 久久免费99精品久久久久久 | 成人h在线观看 | 欧美资源在线观看 | 久久99久久99精品免观看粉嫩 | 99激情网| 国产一区二区精品久久 | 国产亚洲精品久久久久久电影 | 国产视频一区二区三区在线 | 久久9999久久免费精品国产 | 天堂在线免费视频 | 操操操综合 | 国产护士hd高朝护士1 | 极品中文字幕 | 久久久久久国产一区二区三区 | 蜜臀av性久久久久av蜜臀妖精 | 成人免费一区二区三区在线观看 | 午夜精品久久久久久中宇69 | 国产在线观看污片 | 好看av在线| 一区二区不卡高清 | 日韩在线视频看看 | 特级大胆西西4444www | 婷婷av网| 天天色天天射天天干 | 日韩va欧美va亚洲va久久 | 日韩av一区二区三区四区 | 四虎在线免费观看 | 国产不卡av在线 | 色香蕉视频 | 国产精品福利午夜在线观看 | 婷婷网五月天 | 最近中文字幕大全中文字幕免费 | 国产精品免费看 | 99精品国产亚洲 | 久久久久久久久久久福利 | 久久九九影院 | 丝袜美女视频网站 | 天天插天天爱 | av免费高清观看 | 在线免费观看视频一区二区三区 | 天天色天天干天天 | 国产午夜小视频 | 婷婷六月在线 | 国产精品短视频 | 深爱激情久久 | 激情视频免费在线 | 成人国产精品一区 | 日韩精品久久久久久久电影99爱 | 亚洲高清视频在线 | 91高清视频免费 | 成片视频在线观看 | 啪啪肉肉污av国网站 | 国产日韩精品久久 | 天天干天天综合 | 天天干天天干天天 | 久久国产精品网站 | 91网页版在线观看 | 婷婷久久一区 | 免费色视频网站 | 奇米7777狠狠狠琪琪视频 | 国产视频 亚洲视频 | 91久久电影 | 中文字幕国产精品一区二区 | 92精品国产成人观看免费 | www.超碰97.com| 伊人av综合 | 在线观看中文字幕av | 欧美 高跟鞋交 xxxxhd | 久草在线免费新视频 | 国产精品黄 | 日韩在线观看视频网站 | av一级在线观看 | 日韩系列在线观看 | 亚洲激情一区二区三区 | 日日夜夜精品视频天天综合网 | 国产精品久久久久影视 | 99热9| 国产一卡二卡在线 | 成 人 黄 色 视频 免费观看 | 天天干天天操天天做 | 日韩精品久久久久久久电影99爱 | 成人黄色国产 | 国内精品视频在线 | 五月婷婷丁香激情 | 青青看片 | 免费视频色 | 日韩av片无码一区二区不卡电影 | 日韩av手机在线看 | 欧美日韩视频在线播放 | 丁香花中文在线免费观看 | 欧美疯狂性受xxxxx另类 | 五月天久久激情 | 久久这里只精品 | 日韩在线视| 欧美日韩一二三四区 | 国产精品日韩高清 | 中文字幕在线观看网址 | 国产成人a亚洲精品 | 欧美少妇bbwhd | www一起操 | 久久久久久欧美二区电影网 | 91成人精品观看 | 久久99免费视频 | 久久免费黄色 | 久久影院精品 | 狠狠狠的干 | 免费观看国产视频 | 日韩视频在线不卡 | 69国产盗摄一区二区三区五区 | 中文字幕免费久久 | 亚洲无吗视频在线 | 在线看片一区 | 色五月成人 | 在线国产中文 | 日韩精品欧美精品 | 综合色爱| 久久精品2| 国产精品美女免费 | 国语麻豆| 98久久 | 有码一区二区三区 | 国产精品九九久久99视频 | 国产 一区二区三区 在线 | 美女网站在线观看 | 天天做日日爱夜夜爽 | 中文字幕在线播放av | 色香蕉视频 | 黄色一级免费电影 | 91视频这里只有精品 | 国产亚洲精品久久久久久大师 | 国产亚洲成av片在线观看 | 国内成人精品视频 | 韩日色视频 | 精品免费一区二区三区 | 国产精品永久免费观看 | 手机看片中文字幕 | 天天操天天干天天玩 | 国产精品99久久久久久久久 | 三级视频日韩 | 日韩大陆欧美高清视频区 | 中文字幕 婷婷 | 91精品视频播放 | 香蕉视频4aa| 97超碰在线久草超碰在线观看 | 成人在线你懂得 | 成人综合婷婷国产精品久久免费 | 日本视频久久久 | 99久久精品电影 | 最新极品jizzhd欧美 | 日韩av电影中文字幕在线观看 | 欧美激情精品久久久久久 | 五月天激情综合 | 国产精品久久网 | 日韩精品欧美一区 | 国产精品成人一区二区三区吃奶 | 国产一区二区不卡视频 | 91人人爽久久涩噜噜噜 | 99国产精品视频免费观看一公开 | 成人免费看片98欧美 | 91一区二区三区在线观看 | 国产精品系列在线观看 | 五月婷婷综合激情网 | 99产精品成人啪免费网站 | 久草视频在线看 | 亚洲黄色小说网 | 免费在线观看av网址 | 一区二区精品国产 | 在线观看午夜av | 激情五月看片 | 婷婷午夜| 最近高清中文字幕在线国语5 | 久久精品网站免费观看 | 最新国产在线视频 | 久久99爱视频 | 国产小视频在线免费观看 | 亚洲精品在线免费 | 一区二区在线影院 | 成人观看 | 成人国产网站 | 97超在线视频 | av资源免费观看 | 婷婷综合网 | 黄色99视频 | 中文在线8新资源库 | 97伊人网| 国产一区麻豆 | www.综合网.com | 99热这里是精品 | 久久综合毛片 | 亚洲国产成人在线播放 | 黄色av免费看| 精品久久久久久综合日本 | 91天天操 | 91精选在线观看 | 中文字幕中文字幕在线一区 | 免费观看黄| 四虎国产精品成人免费影视 | 日韩av电影免费在线观看 | 韩国三级av在线 | 成年人免费av| 亚洲伊人婷婷 | 欧美精品久久久久久久免费 | 五月激情视频 | 视频二区在线 | 手机成人在线电影 | 97福利视频 | 欧美激情va永久在线播放 | 91黄色小视频 | 国产色婷婷精品综合在线手机播放 | 日韩激情片在线观看 | 欧美激情综合色 | 色狠狠综合 | 五月天婷婷在线观看视频 | 日韩色一区二区三区 | 日批视频在线播放 | 黄色成人在线 | 在线免费观看视频一区二区三区 | 精品国产成人 | 在线观看国产日韩欧美 | 天天综合网国产 | 美女禁18| 丁香高清视频在线看看 | 精品亚洲视频在线观看 | 亚洲乱码一区 | 最近高清中文在线字幕在线观看 | 91亚洲欧美激情 | 久久精品99国产精品日本 | 国产日韩欧美中文 | 国产麻豆电影在线观看 | 欧美激情精品久久久久久变态 | 国产在线中文字幕 |