優(yōu)雅的構(gòu)建Android項目之RxAndroid+Retrofit網(wǎng)絡(luò)請求

注意

Retrofit 2.0+和Retrofit 2.0之前的版本語法上有差別逞敷,本文基于Retrofit2.1.0

什么是Retrofit?

retrofit是一款針對Android網(wǎng)絡(luò)請求的開源框架蝶涩,它與okhttp一樣出自Square公司琳省。Rotrofit2.0的網(wǎng)絡(luò)框架全部交給了okhttp來實現(xiàn)姐军,Android N之后Apache的httpclient已經(jīng)被Google從SDK中移除亭引,Okhttp則成功上位吧恃。Retrofit的網(wǎng)絡(luò)請求實現(xiàn)風(fēng)格與URLconnection和httpClient有較大的差別嫡良。創(chuàng)建請求的時候需要先創(chuàng)建基于注解的服務(wù)接口(不了解的可以先了解一下注解)袜炕,進(jìn)行網(wǎng)絡(luò)請求的時候再通過retrofit.creat()方法創(chuàng)建請求本谜。

Retrofit中http POST/GET請求

Retrofit中的網(wǎng)絡(luò)請求都是通過注解方式的接口方法來表示的,此處只對常用的post和get請求進(jìn)行說明,Retrofit還提供有put偎窘,delete等請求方式可自己研究文檔使用

post請求

  • Body對象作為post參數(shù)
@POST("user/login")
Call<User> login(@Body LoginInfo loginInfo);
  • Field方式
@FormUrlEncoded
@POST("user/login")
Call<User> login(@Field("username") String username,
                 @Field(password) String password);
  • FieldMap方式
@FormUrlEncoded
@POST("user/login")
Call<User> login(@FieldMap Map<String,String> map);

參數(shù)較多時建議用Body方式和FieldMap方式

get請求

  • 直接請求某一地址獲取列表
//接口我瞎寫的
@GET("news/toplist")
Call<ArrayList<News> news> getNewsList(); 
  • url拼接固定查詢條件
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList(); 
  • url中拼接地址信息
@GEt("news/{city}/newslist")
Call<ArrayList<News> news> getCityNewsList(@Path("city") String city);
  • 通過Query注解添加其他查詢條件
@GET("news/{city}/newslist")
Call<ArrayList<News> news> getCityNewsList(@Path("city") String city
                                           @Query("date") String date
                                           @Query("newsType") String newsType);
  • 查詢條件較多時同樣有QueryMap注解方法供使用
@GET("news/{city}/newslist")
Call<ArrayList<News> news> getCityNewsList(@Path("city") String city
                                           @QueryMap<String, String> options);

通過上面的API方法會發(fā)現(xiàn)都是在進(jìn)行請求條件的配置乌助,假如我要給請求加請求頭怎么辦?放心陌知,retrofit也有相應(yīng)的注解他托。除了注解之外還有一個萬用的處理方法。

Header請求頭設(shè)置

  • 為請求添加固定請求頭
//添加單個固定請求頭
@Header("Cache-Control: max-aget-640000")
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList(); 
//多個請求頭以數(shù)組的形式提交
@Header(
    {"Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
    })
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList(); 
  • 動態(tài)添加請求頭
//添加動態(tài)請求頭仆葡,比如獲取的認(rèn)證信息等
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList(@Header(Authorization) String authorization); 

上面的兩種添加請求頭的方法作用范圍只是添加注解的單個方法赏参,如果想為每個請求都添加請求頭還按這種方式來做的話就很不程序猿了。Retrofit的網(wǎng)絡(luò)請求全部交給okhttp來處理,因此我們可以通過OkHttpClient來做文章把篓,自己重寫okhttp的攔截器在攔截器內(nèi)再進(jìn)行需要的操作

Okhttp interceptor

攔截器顧名思義纫溃,所有通過okhttp進(jìn)行的請求都會過一遍okhttpClient的攔截器,發(fā)出去的請求韧掩,收到的響應(yīng)都會經(jīng)過他紊浩,就像一個雙向的安檢通道。
okhttp攔截器的原理如下:

okhttp攔截器疗锐,圖片來自okhttp官網(wǎng)
okhttp攔截器坊谁,圖片來自okhttp官網(wǎng)

如圖所示攔截器分為Application Interceptors和NetWork Interceptors。Application攔截器工作區(qū)域為應(yīng)用發(fā)出請求到okhttp核心之間窒悔,遠(yuǎn)端響應(yīng)經(jīng)過okhttp核心后到達(dá)應(yīng)用處理之前呜袁。而NetWork攔截器的作用域為okhtt核心到遠(yuǎn)端服務(wù)器之間的部分。明顯區(qū)別就是當(dāng)一次請求中會有一個重定向的時候Application攔截器只會響應(yīng)一次简珠,因為對于應(yīng)用來說就進(jìn)行了一次請求阶界。而NetWork攔截器會在重定向時也響應(yīng)即響應(yīng)兩次,也不難理解聋庵,畢竟重定向也會經(jīng)過一次okhttp核心嘛膘融。

攔截器工作示意簡圖
攔截器工作示意簡圖

上圖是okhttp攔截器工作原理簡圖,重點在右邊部分祭玉。當(dāng)多個攔截器配合使用時氧映,不用擔(dān)心請求攔截和響應(yīng)攔截順序會錯亂,okhttp已經(jīng)給你排好了脱货。


上傳個需要壓縮和編碼的東東的時候岛都,你可以選擇先寫個攔截器請求時壓縮響應(yīng)時解壓,再寫個攔截器請求時編碼響應(yīng)時解碼振峻。加起來就是壓縮->編碼->okhttClient與服務(wù)器的Py交易->解碼->解壓跟棧先進(jìn)后出類似臼疫。

原理扯了一大堆,代碼才是干貨扣孟,看了代碼才知道怎么用烫堤。

//官方的栗子
class LoggingInterceptor implements Interceptor {
        @Override
        public Response intercept(Interceptor.Chain chain) throws IOException {
         //拿到request實例在此對請求做需要的設(shè)置
            Request request = chain.request();
            long t1 = System.nanoTime();
            logger.info(String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));
            //發(fā)送request請求
            Response response = chain.proceed(request);
            //得到請求后的response實例,做相應(yīng)操作
            long t2 = System.nanoTime();
            logger.info(String.format("Received response for %s in %.1fms%n%s",
                    response.request().url(), (t2 - t1) / 1e6d, response.headers()));
            return response;
        }
    }

通過Request request = chain.request();拿到請求實例凤价,想怎么裝扮就怎么裝扮鸽斟,什么加請求頭,設(shè)置編碼格式soeasy利诺。前面說到的為每個請求設(shè)置請求頭就是在這完成設(shè)置工作的富蓄。但是真正要加到請求里跟retrofit的ApiService接口一起用還需要將Okhttp注冊攔截器后與Retrofit綁定才行。

//注冊應(yīng)用攔截器
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();
Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();··
Response response = client.newCall(request).execute();
response.body().close();

//注冊網(wǎng)絡(luò)攔截器
OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();
Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();
Response response = client.newCall(request).execute();
response.body().close();

該如何使用Retrofit?

基本也說得差不多了慢逾,那么怎么使用Retrofit進(jìn)行一次完整的網(wǎng)絡(luò)請求呢
需要注意一下Retrofit的Url拼接規(guī)則

enter description here
enter description here

enter description here
enter description here

enter description here
enter description here

個人建議以第一幅圖的方式立倍,baseUrl總是以/結(jié)尾躏吊,接口rul總是不以/開頭

  • 1、當(dāng)然是引入retrofit的庫啦
//build.gradle的依賴中加入帐萎,其中第二條不一定要使用gson,其他方式在官方的github上也有
// retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
  • 2胜卤、創(chuàng)建一個ServiceApi接口
//方便后面RxAndroid我把RxAndroid方式的接口也貼上來疆导。只有返回類型不同而已
public interface RetrofitService {
    //單純使用retrofit接口定義
    @GET("news/latest")
    Call<ZhiHuDaily> getZhihuDailyRetrofitOnly();

    //使用retrofit+RxAndroid的接口定義
    @GET("news/latest")
    Observable<ZhiHuDaily> getZhihuDaily();
}
  • 3、我建議是維護(hù)一個統(tǒng)一的api管理類葛躏。當(dāng)然你要直接拿接口用也行澈段,但可維護(hù)性會降低很多
public class ApiManager {

    private RetrofitService mDailyApi;
    private static ApiManager sApiManager;
    //獲取ApiManager的單例
    public static ApiManager getInstence() {
        if (sApiManager == null) {
            synchronized (ApiManager.class) {
                if (sApiManager == null) {
                    sApiManager = new ApiManager();
                }
            }
        }
        return sApiManager;
    }
    /**
     * 封裝配置知乎API
     */
    public RetrofitService getDailyService() {
    //不需要使用攔截器就不創(chuàng)建直接從if開始
        OkHttpClient client = new OkHttpClient.Builder()
                //添加應(yīng)用攔截器
                .addInterceptor(new MyOkhttpInterceptor())
                //添加網(wǎng)絡(luò)攔截器
//                .addNetworkInterceptor(new MyOkhttpInterceptor())
                .build();
        if (mDailyApi == null) {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(GlobalConfig.baseUrl)
                    //將client與retrofit關(guān)聯(lián)
                    .client(client)
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            //到這一步創(chuàng)建完成
            mDailyApi = retrofit.create(RetrofitService.class);
        }
        return mDailyApi;
    }
}
  • 4、調(diào)用接口方法進(jìn)行網(wǎng)絡(luò)請求
    public void getStoryDataByRetrofit(final OnEventLister<ArrayList<ZhihuStory>> eventLister) {
        ApiManager apiManager = ApiManager.getInstence();
        Call<ZhiHuDaily> call = apiManager.getDailyService().getZhihuDailyRetrofitOnly();
        //發(fā)送異步請求
        call.enqueue(new Callback<ZhiHuDaily>() {
            @Override
            public void onResponse(Call<ZhiHuDaily> call, Response<ZhiHuDaily> response) {
                eventLister.onSuccess(response.body().getStories());
            }

            @Override
            public void onFailure(Call<ZhiHuDaily> call, Throwable t) {
                eventLister.onFail(t.getMessage(), "");
            }
        });
    }

使用Retrofit的好處

  • 可以少寫不少的代碼
  • 接口方便維護(hù)需要改什么直接到ApiService中進(jìn)行配置即可
  • 異步請求不再需要自己來newThread再handler舰攒,也不需要自己再來寫請求結(jié)果回調(diào)败富。異步請求只需要使用call.enqueue()即可。
  • 支持RxAndroid摩窃,這個我覺得很重要
  • 降低工程的耦合度兽叮,網(wǎng)絡(luò)請求跟邏輯代碼完全剝離開。需要的僅僅是傳遞參數(shù)有的請求甚至參數(shù)都不需要傳遞猾愿。直接在接口中配置就好鹦聪。

Retrofit的好基友——RxAndroid

RxAndroid是RxJava在Android上的變種。那么RxJava到底是什么呢蒂秘?
"a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個在 Java VM 上使用可觀測的序列來組成異步的泽本、基于事件的程序的庫)。這是github項目主頁的自我概括姻僧,我覺得其實就兩個關(guān)鍵詞规丽,異步基于事件撇贺。這里我只說一下RxAndroid怎么跟Retrofit搭配使用赌莺,要進(jìn)一步了解可以異步扔物線大神的文章給 Android 開發(fā)者的 RxJava 詳解。講的肯定比我好显熏,我就是看這個入門的雄嚣。

Retrofit+RxAndroid使用

因為用到Retrofit所以定義接口,創(chuàng)建ApiManager這些跟上面單純用Retrofit是一毛一樣的喘蟆,唯一的不同是接口的返回類型從Retrofit的Call對象變成了Observable對象缓升,即被觀察者對象。然后就是調(diào)用進(jìn)行網(wǎng)絡(luò)請求部分變成如下形式

    //使用rxandroid+retrofit進(jìn)行請求
    public void loadDataByRxandroidRetrofit() {
        mIMainActivity.showProgressBar();
        Subscription subscription = ApiManager.getInstence().getDailyService()
                .getZhihuDaily()
                .map(new Func1<ZhiHuDaily, ArrayList<ZhihuStory>>() {
                    @Override
                    public ArrayList<ZhihuStory> call(ZhiHuDaily zhiHuDaily) {
                        return zhiHuDaily.getStories();
                    }
                })
                //設(shè)置事件觸發(fā)在非主線程
                .subscribeOn(Schedulers.io())
                //設(shè)置事件接受在UI線程以達(dá)到UI顯示的目的
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ArrayList<ZhihuStory>>() {
                    @Override
                    public void onCompleted() {
                        mIMainActivity.hidProgressBar();
                    }

                    @Override
                    public void onError(Throwable e) {
                        mIMainActivity.getDataFail("", e.getMessage());
                    }

                    @Override
                    public void onNext(ArrayList<ZhihuStory> stories) {
                        mIMainActivity.getDataSuccess(stories);
                    }
                });
        //綁定觀察對象蕴轨,注意在界面的ondestory或者onpouse方法中調(diào)用presenter.unsubcription();進(jìn)行解綁避免內(nèi)存泄漏
        addSubscription(subscription);
    }

網(wǎng)絡(luò)請求得到的是一個Obserable對象港谊,該對象再通過subscrible()綁定一個觀察者對象,觀察者對象中有onCompleted(),onError(),onNext()三個回調(diào)方法橙弱。事件過程中出錯onError()觸發(fā)并停止后續(xù)事件歧寺,一個Obserable對象一次發(fā)出多個事件每次都會觸發(fā)onNext()燥狰,當(dāng)不再有事件發(fā)出的時候onCompleted()方法觸發(fā)并結(jié)束。異步在RxAndroid中變得很簡單subscribeOn指定事件發(fā)生線程斜筐,如上面的網(wǎng)絡(luò)請求被指定在io線程中龙致,observeOn指定事件的消費線程,如上面的知乎故事數(shù)據(jù)結(jié)果被交給主線程顯示顷链。

RxAndroid的優(yōu)點

看完上面單獨使用retrofit和使用retrofit+rxandroid兩種方式之后你也許會吐槽目代,尼瑪腦子有坑?明明代碼變多了嗤练。
但是你也會發(fā)現(xiàn)代碼中都是.XX()的形式如果需求變化更多一些會更明顯榛了。比如說就上面的例子我要在每一條信息中修改某一個值。并且又要對結(jié)果進(jìn)行一些篩選煞抬。只用retrofit的話是不是應(yīng)該向這樣:

    call.enqueue(new Callback<ZhiHuDaily>() {
        @Override
        public void onResponse(Call<ZhiHuDaily> call, Response<ZhiHuDaily> response) {
            ArrayList<ZhihuStory> stories = response.body().getStories();
            for(ZhihuStory story : stories){
                //修改每一條story中的某一值,這里用XXX代替
                story.setXXX(XXX);
                //篩選出id<100的
                if(story.getId()>100){
                    stories.remove(story);
                }
            }
            eventLister.onSuccess(response.body().getStories());
        }

        @Override
        public void onFailure(Call<ZhiHuDaily> call, Throwable t) {
            eventLister.onFail(t.getMessage(), "");
        }
    });

如果需要設(shè)置和條件賽選層次越多會發(fā)現(xiàn)for套if霜大,if再if會越嵌套越多。隔一段時間之后就真的成了“當(dāng)初寫下這段代碼的時候只有我跟上帝知道他是干嘛的革答,現(xiàn)在只有上帝知道他是干嘛的”战坤。而使用RxAndroid的話整個變換過程都是線性的哪一步做了什么都會很清楚不會出現(xiàn)各種蜜汁縮進(jìn):

        Subscription subscription = ApiManager.getInstence().getDailyService()
                .getZhihuDaily()
                //從ZhihuDaily中獲取Stories列表
                .map(new Func1<ZhiHuDaily, ArrayList<ZhihuStory>>() {
                    @Override
                    public ArrayList<ZhihuStory> call(ZhiHuDaily zhiHuDaily) {
                        return zhiHuDaily.getStories();
                    }
                })
                //將列表拆開成事件發(fā)送
                .flatMap(new Func1<ArrayList<ZhihuStory>, Observable<ZhihuStory>>() {
                    @Override
                    public Observable<ZhihuStory> call(ArrayList<ZhihuStory> stories) {
                        return Observable.from(stories);
                    }
                })
                //將story中的XXX設(shè)置為xxx
                .map(new Func1<ZhihuStory, ZhihuStory>() {
                    @Override
                    public ZhihuStory call(ZhihuStory zhihuStory) {
                        zhihuStory.setXXX(xxx);
                        return zhihuStory;
                    }
                })
                //過濾掉Id>10的story
                .filter(new Func1<ZhihuStory, Boolean>() {
                    @Override
                    public Boolean call(ZhihuStory zhihuStory) {
                        return zhihuStory.getId()<10;
                    }
                })
                //將結(jié)果重新整理成List
                .toList()
                //設(shè)置事件觸發(fā)在非主線程
                .subscribeOn(Schedulers.io())
                //設(shè)置事件接受在UI線程以達(dá)到UI顯示的目的
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<List<ZhihuStory>>() {
                    @Override
                    public void onCompleted() {
                        mIMainActivity.hidProgressBar();
                    }

                    @Override
                    public void onError(Throwable e) {
                        mIMainActivity.getDataFail("", e.getMessage());
                    }

                    @Override
                    public void onNext(List<ZhihuStory> stories) {
                        mIMainActivity.getDataSuccess((ArrayList<ZhihuStory>) stories);
                    }
                });

越復(fù)雜的邏輯,Rx的優(yōu)勢也就越明顯蝗碎。Rx的操作符各種組合起來幾乎能夠滿足所有的變換需求湖笨。開始寫可能會覺得很不適應(yīng),但熟練使用之后會默念Rx大法好的蹦骑。
對于RxJava操作符鼠標(biāo)懸停都會有文字和示意圖的慈省,另外發(fā)現(xiàn)一個不錯的博客里面也有較詳細(xì)的解析RxJava/RxAndroid操作符

Demo地址

在之前的那個MVPDemo的基礎(chǔ)上寫的,okhttp請求方式眠菇,單純retrofit方式边败,retrofit+rxAndroid方式的請求都有保留,可以對比著感受一下捎废。
demo倉庫地址

覺得本文對你有幫助

關(guān)注簡書PandaQ404笑窜,持續(xù)分享中。登疗。排截。
Github主頁

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辐益,隨后出現(xiàn)的幾起案子断傲,更是在濱河造成了極大的恐慌,老刑警劉巖智政,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件认罩,死亡現(xiàn)場離奇詭異,居然都是意外死亡续捂,警方通過查閱死者的電腦和手機垦垂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門宦搬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劫拗,你說我怎么就攤上這事间校。” “怎么了页慷?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵撇簿,是天一觀的道長。 經(jīng)常有香客問我差购,道長,這世上最難降的妖魔是什么汉嗽? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任欲逃,我火速辦了婚禮,結(jié)果婚禮上饼暑,老公的妹妹穿的比我還像新娘稳析。我一直安慰自己,他們只是感情好弓叛,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布彰居。 她就那樣靜靜地躺著,像睡著了一般撰筷。 火紅的嫁衣襯著肌膚如雪陈惰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天毕籽,我揣著相機與錄音抬闯,去河邊找鬼。 笑死关筒,一個胖子當(dāng)著我的面吹牛溶握,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒸播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼睡榆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了袍榆?” 一聲冷哼從身側(cè)響起胀屿,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜡塌,沒想到半個月后碉纳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡馏艾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年劳曹,在試婚紗的時候發(fā)現(xiàn)自己被綠了奴愉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡铁孵,死狀恐怖锭硼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜕劝,我是刑警寧澤檀头,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站岖沛,受9級特大地震影響暑始,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜婴削,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一廊镜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唉俗,春花似錦嗤朴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衡楞,卻和暖如春吱雏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘾境。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工坎背, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寄雀。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓得滤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盒犹。 傳聞我的和親對象是個殘疾皇子懂更,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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