Android網(wǎng)絡(luò)框架httplite使用指南

前言

Http請(qǐng)求是做Android應(yīng)用開發(fā)工作幾乎必須要用到的東西扛门。做Android開發(fā)這幾年帆离,從最開始仿照網(wǎng)上代碼自己使用apache的DefaultHttpClient封裝網(wǎng)絡(luò)請(qǐng)求工具類私沮,到后面開始使用GitHub上面的一些http框架缝左,Afinal,xUtils到Volley,AsyncHttpClient等碎税,網(wǎng)上這些http框架大多都還比較易用歪泳,但是做實(shí)際業(yè)務(wù)中還是感覺到業(yè)務(wù)和界面代碼與Http請(qǐng)求的代碼還是耦合性過高,特別是在服務(wù)器接口比較多的時(shí)候。所以自己在以前的項(xiàng)目中也一直在嘗試做一些封裝解耦沸呐,但是一直感覺達(dá)不到自己想要的效果,直到看到Retrofit這個(gè)類庫(kù)呢燥。

在斷斷續(xù)續(xù)看了幾個(gè)月的OkHttpClient和Retrofit源碼崭添,我終于決定嘗試著封裝一個(gè)自己的框架:httplite

Github:https://github.com/alexclin0188/httplite

類庫(kù)主要特性介紹

httplite類庫(kù)主要實(shí)現(xiàn)了以下特性

  • 1.隔離了底層http實(shí)現(xiàn),http實(shí)現(xiàn)可替換
    雖然okhttpclient的實(shí)現(xiàn)很好叛氨,但是有時(shí)候也會(huì)因?yàn)轫?xiàng)目包大小等原因需要使用系統(tǒng)UrlConection來實(shí)現(xiàn)
  • 2.建造者模式的流式調(diào)用和結(jié)果解析的解耦
    使用Request.url().param().header().***.async(Callback<T>)方式調(diào)用呼渣,可自定義多重ResponseParser來實(shí)現(xiàn)不同的http返回結(jié)果(json,protocolbuf等)
  • 3.支持使用類似Retrofit的方式棘伴,使用java接口類來定義后后臺(tái)API接口,并且支持RxJava屁置,支持自定義注解

目前類庫(kù)底層的http實(shí)現(xiàn)提供了okhttp2.x/okhttp3.x/URLConnection三種可選.

類庫(kù)使用指南

一焊夸、添加依賴

  • Gradle

使用okhttp 2.7.5作為http實(shí)現(xiàn)

    compile 'alexclin.httplite:httplite-okhttp2:1.1.1'
    compile 'com.squareup.okhttp:okhttp:2.7.5'

使用okhttp 3.2.0作為http實(shí)現(xiàn)

    compile 'alexclin.httplite:httplite-okhttp3:1.1.1'
    compile 'com.squareup.okhttp3:okhttp:3.2.0'

使用系統(tǒng)URLConnection作為http實(shí)現(xiàn)

    compile 'alexclin.httplite:httplite-url:1.1.1'

如需Rx擴(kuò)展則還需要

    compile 'alexclin.httplite:retrofit-rx:1.1.1'
    compile 'io.reactivex:rxjava:1.1.1'

或者直接使用releaselib中的jar包

  • 1 okhttp2: httplite1.1.1.jar+httplite-ok2lite1.1.1.jar+okhttp 2.x.x版本jar包

  • 2 okhttp3: httplite1.1.1.jar+httplite-ok3lite1.1.1.jar+okhttp 3.x.x版本jar包

  • 3 url: httplite1.1.1.jar+httplite-urlite1.1.1.jar

  • 4 使用rx擴(kuò)展:httplite-retrofit-rx1.1.1.jar+rx1.x.x版本jar包

二、類庫(kù)初始化

或者也可以直接使用jar包
首先創(chuàng)建HttpLiteBuilder進(jìn)行配置蓝角,目前有三種HTTP實(shí)現(xiàn)可選

//使用OkHttp2.x作為Http實(shí)現(xiàn)
HttpLiteBuilder builder = Ok2Lite.create();
//使用OkHttp3.x作為Http實(shí)現(xiàn)
HttpLiteBuilder builder = Ok3Lite.create();
//使用系統(tǒng)URLConnection作為http實(shí)現(xiàn)
HttpLiteBuilder builder = URLite.create();

對(duì)Builder進(jìn)行配置

builder = builder.setConnectTimeout(10, TimeUnit.SECONDS)  //設(shè)置連接超時(shí)
              .setWriteTimeout(10, TimeUnit.SECONDS)  //設(shè)置寫超時(shí)
              .setReadTimeout(10, TimeUnit.SECONDS)  //設(shè)置讀超時(shí)
              .setMaxRetryCount(2)  //設(shè)置失敗重試次數(shù)
              .setFollowRedirects(true)  //設(shè)置是否sFollowRedirects,默認(rèn)false
              .setFollowSslRedirects(true) //設(shè)置是否setFollowSslRedirects
              .addResponseParser(new GsonParser())
              .baseUrl("http://192.168.99.238:10080/")//BaseUrl
              .setProxy(...)//
              .setProxySelector(...)//
              .setSocketFactory(...)//
              .setSslSocketFactory(...)//
              .setHostnameVerifier(..)//
              .useCookie(...)  //設(shè)置CookieStore,設(shè)置則啟用Cookie,不設(shè)置則不啟用
              .addCallAdapter(new RxCallAdapter());//添加Rx支持

創(chuàng)建HttpLite實(shí)例

HttpLite httpLite = builder.build();

另外提供mock支持阱穗,需傳入MockHandler

httpLite = builder.mock(new MockHandler() {
                      @Override
                      public <T> void mock(Request request, Mock<T> mock) throws Exception {
                          //模擬完整的http返回結(jié)果輸入流
                          mock.mock(int code,String msg,Map<String, List<String>> headers, final InputStream stream,MediaType mediaType);
                          //直接模擬結(jié)果
                          mock(T result, Map<String, List<String>> headers);
                          //模擬Json格式的結(jié)果
                          mock.mockJson(....);
                          //以文件內(nèi)容作為Http返回結(jié)果輸入流
                          mock.mock(new File("...."))使鹅;
                      }
                      @Override
                      public boolean needMock(Request request) {
                          //TODO 判斷該請(qǐng)求是否需要Mock
                          return true;
                      }
            });

二揪阶、普通方式發(fā)起http請(qǐng)求

發(fā)起普通GET請(qǐng)求

    mHttpLite.url(url).header("header","not chinese").header("test_header","2016-01-06")
                .header("double_header","header1").addHeader("double_header","head2")
                .param("type","json").param("param2","You dog").param("param3", "中文")
                .get().async(new Callback<Result<List<FileInfo>>>() {
            @Override
            public void onSuccess(Request req, Map<String, List<String>> headers,Result<List<FileInfo>> result) {
                //TODO
            }

            @Override
            public void onFailed(Request req, Exception e) {
                //TODO
            }
        });

發(fā)起post請(qǐng)求,監(jiān)聽進(jìn)度

    //multipart上傳文件
    MediaType type = mHttpLite.parse(MediaType.MULTIPART_FORM+";charset=utf-8");
    RequestBody body = mHttpLite.createRequestBody(mHttpLite.parse(MediaType.APPLICATION_STREAM),file);
    mHttpLite.url("/").multipartType(type).multipart("早起早睡","身體好").multipart(info.fileName,info.hash).multipart(info.fileName,info.filePath,body)
       .onProgress(new ProgressListener() {
            @Override
            public void onProgressUpdate(boolean out, long current, long total) {
                LogUtil.e("是否上傳:"+out+",cur:"+current+",total:"+total);
            }
        })
        .post().async(new Callback<Result<String>>() {
            @Override
            public void onSuccess(Request req,Map<String, List<String>> headers,Result<String> result) {
                LogUtil.e("Result:"+result);
            }
            @Override
            public void onFailed(Request req, Exception e) {
                LogUtil.e("onFailed:"+e);
                e.printStackTrace();
           }
    });
    //post json
    mHttpLite.url("/").post(MediaType.APPLICATION_JSON, JSON.toJSONString(info)).async(new Callback<String>() {
        @Override
        public void onSuccess(Request req,Map<String, List<String>> headers,String result) {
            LogUtil.e("Result:" + result);
        }
        @Override
        public void onFailed(Request req, Exception e) {
            LogUtil.e("E:" + e.getLocalizedMessage());
            e.printStackTrace();
        }
    });
    //post form表單
    mHttpLite.url("/").form("&test1","name&1").form("干撒呢","whatfuck").formEncoded(Uri.encode("test&2"),Uri.encode("name&2")).post().async(new Callback<String>() {
        @Override
       public void onSuccess(Request req,Map<String, List<String>> headers,String result) {
            LogUtil.e("Result:" + result);
        }
        @Override
        public void onFailed(Request req, Exception e) {
            LogUtil.e("E:" + e.getLocalizedMessage());
            e.printStackTrace();
        }
    });

下載文件

mHttpLite.url(url).intoFile(dir,name,true,true)
        .onProgress(new ProgressListener() {
            @Override
            public void onProgressUpdate(boolean out, long current, long total) {
                        //TODO
                    }
                })
                .download(new Callback<File>() {
                    @Override
                    public void onSuccess(Request req, Map<String, List<String>> headers, File result) {
                        //TODO
                    }

                    @Override
                    public void onFailed(Request req, Exception e) {
                        //TODO
                    }
                });

三并徘、使用java接口定義API接口(類似Retrofit的功能)

1.基礎(chǔ)使用

        //生成API接口實(shí)例
        final SampleApi api = mHttplite.retrofit(SampleApi.class);
        //調(diào)用異步方法
        api.login("user", "pass", "token", new Callback<Result<UserInfo>>() {
            @Override
            public void onSuccess(Request req, Map<String, List<String>> headers, Result<UserInfo> result) {
                //TODO
            }

            @Override
            public void onFailed(Request req, Exception e) {
                //TODO
            }
        });
        //調(diào)用異步方法
        new Thread(){
            @Override
            public void run() {
                //獲取知乎主頁數(shù)據(jù)
                try {
                    ZhihuData data = api.syncZhihu();
                    //TODO
                } catch (Exception e) {
                    //TODO
                }
            }
        }.start();
        //生成Call
        final Call call = api.zhihuCall();
        //異步調(diào)用Call
        call.async(new Callback<ZhihuData>() {
            @Override
            public void onSuccess(Request req, Map<String, List<String>> headers, ZhihuData result) {
                //TODO
            }

            @Override
            public void onFailed(Request req, Exception e) {
                //TODO
            }
        });
        //或者同步調(diào)用Call
        new Thread(){
            @Override
            public void run() {
                //獲取知乎主頁數(shù)據(jù)
                try {
                    ZhihuData data = call.sync(new Clazz<ZhihuData>(){});
                    //TODO
                } catch (Exception e) {
                    //TODO
                }
            }
        }.start();

2.RxJava的支持

支持RxJava需要在配置HttpLiteBuilder時(shí)添加RxCallAdapter

HttpLiteBuilder builder = ....
.....
builder.addCallAdapter(new RxCallAdapter());
.....

定義返回Obserable的API函數(shù)

@GET("http://news-at.zhihu.com/api/4/news/latest")
Observable<ZhihuData> testZhihu();

使用返回的Obserable

Observable<ZhihuData> observable = apiService.testZhihu();
observable.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Subscriber<ZhihuData>() {
       @Override
       public void onCompleted() {
           LogUtil.e("onCompleted");
       }
       @Override
       public void onError(Throwable e) {
           LogUtil.e("Onfailed", e);
       }
       @Override
       public void onNext(ZhihuData zhihuData) {
           LogUtil.e("Result:" + zhihuData);
           LogUtil.e("Result:" + (Thread.currentThread()== Looper.getMainLooper().getThread()));
       }
    });

3.自定義注解的使用

自定義注解支持方法注解參數(shù)注解

只需定義自己的注解遣钳,在HttpLite的Retrofit實(shí)例中添加對(duì)應(yīng)注解的處理器即可

定義注解和注解處理器,此處只列出GsonFieldProcesscor代碼,詳細(xì)參考Demo

public class GsonFieldProcesscor implements ParamMiscProcessor {
    public static final String BODY_TYPE = "gson_json_body";

    @Override
    public void process(Request request, Annotation[][] annotations, List<Pair<Integer, Integer>> list, Object... args) {
        //處理所有帶有Gson注解的參數(shù)麦乞,list中存儲(chǔ)的是所有Gson注解的位置
        JsonObject object = new JsonObject();
        for(Pair<Integer,Integer> pair:list){
            int argPos = pair.first;
            int annotationPos = pair.second;
            if(args[argPos]==null) continue;
            GsonField annotation = (GsonField) annotations[argPos][annotationPos];
            String key = annotation.value();
            if(args[argPos] instanceof String){
                object.addProperty(key,(String)args[argPos]);
            }else if(args[argPos] instanceof JsonElement){
                object.add(key,(JsonElement)args[argPos]);
            }else if(args[argPos] instanceof Boolean){
                object.addProperty(key,(Boolean)args[argPos]);
            }else if(args[argPos] instanceof Number){
                object.addProperty(key,(Number)args[argPos]);
            }
        }
        request.body(MediaType.APPLICATION_JSON,object.toString());
    }

    @Override
    public boolean support(Annotation annotation) {
        return annotation instanceof GsonField;
    }

    @Override
    public void checkParameters(Method method, Annotation annotation, Type parameterType) throws RuntimeException {
        //在此函數(shù)中檢查參數(shù)類型是否定義正確
        if(!gsonSupportType(parameterType)){
            throw Util.methodError(method,"Annotation @GsonField only support parameter type String/JsonElement/Boolean/Number/int/long/double/short");
        }if(TextUtils.isEmpty(((GsonField)annotation).value())){
            throw Util.methodError(method,"The annotation {@GsonField(value) value} must not be null");
        }
    }

    private boolean gsonSupportType(Type type){
        return type==String.class || Util.isSubType(type,JsonElement.class) || type == int.class || type == long.class || type == double.class
                || type == short.class || Util.isSubType(type,Number.class) || type == boolean.class || type == Boolean.class;
    }
}
@BaseURL("http://192.168.99.238:10080/")
public interface CustomApi {
    @GET("/login")
    void login(
            @Query("username") String userName,
            @Query("password") String password,
            @Query("token") String token,
            @Tag Object tag,
            Callback<Result<UserInfo>> callback
    );

    @POST("/test")
    void testPost(@GsonField("param1") String param1,
                  @GsonField("param1")String param2,
                  Callback<Result<RequestInfo>> callback);
}
//添加自定義注解處理器
//普通的參數(shù)注解處理ParamterProcessor
Retrofit.registerParamterProcessor(new QueryProcessor());
//對(duì)個(gè)參數(shù)組合到一起的參數(shù)注解處理ParamMiscProcessor蕴茴,如將多個(gè)參數(shù)組合成一個(gè)json字符串作為請(qǐng)求的BODY
Retrofit.registerParamMiscProcessor(new GsonFieldProcesscor());
//當(dāng)注解處理的參數(shù)是用作Body時(shí),還需要注冊(cè)Body類型
Retrofit.basicAnnotationRule().registerBodyAnnotation(GsonField.class,
     GsonFieldProcesscor.BODY_TYPE,true);
        //創(chuàng)建實(shí)例
        CustomApi api = mHttplite.retrofit(CustomApi.class);
        //發(fā)起請(qǐng)求
        Object tag = new Object();
        api.login("user", "pass", "token", tag, new Callback<Result<UserInfo>>() {
            @Override
            public void onSuccess(Request req, Map<String, List<String>> headers, Result<UserInfo> result) {
                //TODO
            }

            @Override
            public void onFailed(Request req, Exception e) {
                //TODO
            }
        });
        api.testPost("test1", "test2", new Callback<Result<RequestInfo>>() {
                    @Override
                    public void onSuccess(Request req, Map<String, List<String>> headers, Result<RequestInfo> result) {
                        //TODO
                        LogUtil.e("Result:"+result);
                    }

                    @Override
                    public void onFailed(Request req, Exception e) {
                        //TODO
                        LogUtil.e("onFailed",e);
                    }
                });
    }

4.RequestListener和MethodFilter的使用

HttpLite支持在創(chuàng)建API接口實(shí)例時(shí)傳入RequestListener和MethodFilter

SampleApi api = mHttplite.retrofit(SampleApi.class,listener,filter);
  • RequestListener主要用于監(jiān)聽接口中的請(qǐng)求姐直,或者為請(qǐng)求添加一些通用參數(shù)
    RequestListener listener = new RequestListener() {
            @Override
            public void onRequest(HttpLite lite, Request request, Type resultType) {
                LogUtil.e("RequestUrl:"+request.rawUrl());
                //添加通用參數(shù)
                request.param("commonParam","1234");
            }
        };
  • MethodFilter主要用于給某些請(qǐng)求加一些前置操作
    MethodFilter filter = new MethodFilter() {
            @Override
            public Object onMethod(HttpLite lite, final MethodInvoker invoker, final Object[] args) throws Throwable {
                //此處僅以同步請(qǐng)求方法為例
                String publicKey = ......
                if(TextUtils.isEmpty(publicKey)){
                    LogUtil.e("methodFilter:"+invoker);
                    String publicKey = ......
                    if(TextUtils.isEmpty(publicKey)){
                        //獲取key
                        ......
                        return invoker.invoke(args);
                    }else{
                        return invoker.invoke(args);
                    }
                }else{
                    return invoker.invoke(args);
                }            
            }
        };

四倦淀、配置ResponseParser

默認(rèn)支持String的解析,但是類對(duì)象結(jié)果的解析需要使用httpLite.addResponseParser()添加支持該類型的解析器ResponseParser声畏,可添加多個(gè)以便支持多種不同的結(jié)果解析

ResponseParser接口定義如下:

public interface ResponseParser {
    boolean isSupported(Type type);
    <T> T praseResponse(Response response, Type type) throws Exception;
}

可以通過實(shí)現(xiàn)此接口解析Json,XML或者二進(jìn)制流為對(duì)象的功能

demo模塊app中分別有使用Jackson,FastJson,Gson實(shí)現(xiàn)Json解析撞叽,通過繼承StringParser實(shí)現(xiàn)。

public abstract class StringParser implements ResponseParser{

    @Override
    public final <T> T praseResponse(Response response, Type type) throws Exception{
        return praseResponse(HttpCallback.decodeResponseToString(response),type);
    }

    public abstract <T> T praseResponse(String content, Type type) throws Exception;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末插龄,一起剝皮案震驚了整個(gè)濱河市愿棋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌均牢,老刑警劉巖糠雨,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異徘跪,居然都是意外死亡甘邀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門垮庐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來松邪,“玉大人,你說我怎么就攤上這事哨查《阂郑” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)锋八。 經(jīng)常有香客問我浙于,道長(zhǎng),這世上最難降的妖魔是什么挟纱? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任羞酗,我火速辦了婚禮,結(jié)果婚禮上紊服,老公的妹妹穿的比我還像新娘檀轨。我一直安慰自己,他們只是感情好欺嗤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布参萄。 她就那樣靜靜地躺著,像睡著了一般煎饼。 火紅的嫁衣襯著肌膚如雪讹挎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天吆玖,我揣著相機(jī)與錄音筒溃,去河邊找鬼。 笑死沾乘,一個(gè)胖子當(dāng)著我的面吹牛怜奖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翅阵,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼歪玲,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了掷匠?” 一聲冷哼從身側(cè)響起滥崩,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讹语,沒想到半個(gè)月后钙皮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡募强,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年株灸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了崇摄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擎值。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逐抑,靈堂內(nèi)的尸體忽然破棺而出鸠儿,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布进每,位于F島的核電站汹粤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏田晚。R本人自食惡果不足惜嘱兼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贤徒。 院中可真熱鬧芹壕,春花似錦、人聲如沸接奈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽序宦。三九已至睁壁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間互捌,已是汗流浹背潘明。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疫剃,地道東北人钉疫。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像巢价,于是被迫代替她去往敵國(guó)和親牲阁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,161評(píng)論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,822評(píng)論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理壤躲,服務(wù)發(fā)現(xiàn)城菊,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 自信點(diǎn),別漏麦,害客税,羞 (1)想想你想改變什么,為什么要改變 不是每個(gè)人都能圓滑地應(yīng)對(duì)各種社交場(chǎng)合撕贞,所以更耻,我們不要浪費(fèi)...
    海蓉sarah閱讀 549評(píng)論 0 1
  • 【第一章】突如其來的大火 【第二十章】愛恨一夜之間1 且說,方子逸本想了了與白玉雪之間的恩恩怨怨捏膨,卻因宋鵬飛的撞見...
    黃飛蝗閱讀 459評(píng)論 0 2