Android性能優(yōu)化典范-底層工作原理

60fps的由來:

作為程序員,我們經(jīng)常會聽到60fps和16ms這兩個重要值,同時我們會將程序是否達(dá)到60fps來作為App性能的衡量標(biāo)準(zhǔn),這是因?yàn)槿搜叟c大腦之間的感知對60fps的畫面更新是最為流暢順滑的。

單純的列出數(shù)據(jù),可能無法幫助大家進(jìn)行理解,這里我們舉幾個實(shí)際生活中常用的例子:

a. ? 12fps類似于手動快速翻書的頻率

b. ? 24fps則可以滿足人眼感知的連續(xù)線性的運(yùn)動,這歸功于運(yùn)用模糊的效果,電影膠圈就是以這個幀率作為破防幀率的。

c.? 但是30fps但是低于30fps是無法順暢表現(xiàn)絢麗的畫面內(nèi)容的,此時就需要用到60fps來達(dá)到想要的效果。我們在開發(fā)app的時候需要保持的性能目標(biāo)就為60fps,換算為時間值也就是:1000ms/60大概為16ms,也就意味著我們需要在16ms內(nèi)處理完所有的任務(wù)。

卡頓的由來:

大多數(shù)用戶感知到的卡頓等性能問題的最主要根源都是因?yàn)殇秩拘阅?。從設(shè)計師的角度,他們希望App能夠有更多的動畫,圖片等時尚元素來實(shí)現(xiàn)流暢的用戶體驗(yàn)。但是Android系統(tǒng)很有可能無法及時在16ms內(nèi)完成那些復(fù)雜的界面渲染操作。舉個例子來說,理想情況下,Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進(jìn)行渲染,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫面所需要的60fps:

理想情況下的渲染

而現(xiàn)實(shí)情況下,總會因?yàn)楦鞣N原因造成16ms內(nèi)無法運(yùn)算出繪制時所需要的運(yùn)算結(jié)果,這樣就會造成以下的情況:

假如一個操作花費(fèi)時間為24ms,則此時在16ms(即系統(tǒng)在接收到VSYNC信號指令)時,無法進(jìn)行渲染,則這種情況下,只能在接收到下一個VSYNC信號時才進(jìn)行渲染。這樣,在中間的16ms區(qū)間內(nèi)只能顯示之前的一幀,這樣就意味著在32ms內(nèi)看到的是同一個畫面。這種現(xiàn)象就是我們熟知的卡頓。

卡頓情況下的渲染

VSYNC的由來:

在上面的知識基礎(chǔ)上,我們知道了卡頓的產(chǎn)生原因,針對上面提到的渲染以及VSYNC信號,我們來詳細(xì)說明一下其中的運(yùn)作原理:

在講解渲染的原理之前,我們需要先了解兩個基本概念:

1.? Refresh Rate:

? ? ?概念: ? ? ? 代表了屏幕在一秒內(nèi)刷新屏幕的次數(shù)

? ? ?決定因素:這取決于硬件的固定參數(shù),

? ? ?舉例: ? ? ? 60Hz

2. ? Frame Rate:

? ? ? 概念: ? ? ? 代表了GPU在一秒內(nèi)繪制操作的幀數(shù)

? ? ? 決定因素:GPU

? ? ? 舉例: ? ? ? 60fps

GPU負(fù)責(zé)計算和獲取數(shù)據(jù)進(jìn)行渲染,硬件方面負(fù)責(zé)將畫面展示在頻幕上面,兩者完美的配合就可以達(dá)到最佳的情況,然而不幸的事,兩者在實(shí)際情況下并不能總是保持步調(diào)一致,如果發(fā)生的幀率(fps)與刷新率(Hz)不一致的情況下。就會出現(xiàn)Tearing(圖片撕裂)活著卡頓的情況。

A. ? 當(dāng)GPU計算速度(fps)快于硬件刷新率(Hz)時:

Tearing(圖片撕裂)的由來:

首先我們先介紹一下上面提到的Tearing,我們都知道,在繪制每一幀的時候,顯示屏(硬件)會從圖形芯片(內(nèi)存)中取出GPU計算好的數(shù)據(jù),并繪制該幀上的像素點(diǎn),繪制方式為從上到下,一行一行的繪制。

理想情況下:期望顯示屏在繪制完一幀之后,圖形芯片整好能提供新幀的數(shù)據(jù)。

特殊情況下:當(dāng)fps高于hz時,也就是GPU計算速度快于顯示屏的繪制速度,這樣就會導(dǎo)致在突破沒有繪制完成時,就載入了新一幀的數(shù)據(jù),由于存儲幀數(shù)據(jù)的內(nèi)存空間為同一塊內(nèi)存,新的數(shù)據(jù)就會將老的數(shù)據(jù)進(jìn)行覆蓋,這就導(dǎo)致最終繪制出來的幀是半個幀的新數(shù)據(jù)和半個幀的老數(shù)據(jù)。

發(fā)生Tearing現(xiàn)象的圖片

為了避免這種情況的發(fā)生,Android中引入了雙緩沖機(jī)制和VSYNC(垂直同步)機(jī)制進(jìn)行優(yōu)化:

1.? VSYNC(垂直同步)

? ? ? 垂直同步是為了保證當(dāng)前幀可以完整顯示而提出的機(jī)制,它會告知GPU在載入新幀之前,要等待屏幕繪制完成前一幀。這樣就可以避免數(shù)據(jù)的覆蓋,但是從機(jī)制中可以發(fā)現(xiàn)明顯的漏洞,在等待硬件繪制完成的時間內(nèi),GPU是并不進(jìn)行數(shù)據(jù)計算的,作為在資本主義社會中誕生的Android系統(tǒng),這種現(xiàn)象是無法容忍的,為了保證“榨取每一分勞動力”的無上原則,引入了雙緩沖機(jī)制。

? ? ? 在加入了垂直同步機(jī)制以后,系統(tǒng)由“固定16ms進(jìn)行繪制”變成了“根據(jù)VSYNC信號”來進(jìn)行繪制,也就是說,我們的優(yōu)化目標(biāo)變成了:保證VSYNC信號在16ms內(nèi)發(fā)出。為了保證這個目標(biāo),Android系統(tǒng)默認(rèn)的將幀率設(shè)定在60fps,也就是說Android系統(tǒng)每隔16ms就會發(fā)出VSYNC信號,來通知硬件進(jìn)行刷新。

2. ? 雙緩沖機(jī)制

? ? ? 正如其字面的理解,雙緩沖就是采用兩塊內(nèi)存區(qū)域作為存儲。Android中所采用的雙緩沖機(jī)制,意味著它可以在顯示一幀的同時進(jìn)行另一幀的處理。當(dāng)顯示緩沖A時,系統(tǒng)在緩沖B中構(gòu)建新的幀。完成后,則交換緩沖。顯示緩沖B,而A則被清空,繼續(xù)下一幀的繪制(這里很像我們通常寫代碼時候用到的懶加載機(jī)制),保證了GPU的勞動力充分榨取。

? ? ? 但是這種雙緩沖機(jī)制同樣會有問題,當(dāng)遇到上面我們提到的卡頓現(xiàn)象時,也就是某幀的繪制時間超過16毫秒時,雙緩沖的問題就暴露出來了。如圖所示,當(dāng)緩沖B超時后,一個卡頓發(fā)生了??D總是不好的,但更嚴(yán)重的問題在于圖中代表CPU和GPU的圖片之間的空白,這是對時間的浪費(fèi)。在第一幀中,緩沖B超時,則在它被顯示之前,都處于使用中的狀態(tài),而緩沖A因?yàn)闉榱颂钛a(bǔ)空白,仍在顯示,則也處于使用中的狀態(tài)。我們知道,僅在收到垂直同步脈沖時,才會進(jìn)行幀(緩沖)切換。則CPU和GPU受限于可以使用的緩沖數(shù),不得不消極怠工(好像我們可以說:不是我不干活哦,是程序在編譯哦)。

? ? ? 當(dāng)然,針對這種現(xiàn)象,前輩們也提出了相應(yīng)的解決方案,例如:三倍緩沖(也就是在A,B兩塊緩沖區(qū)域都處于使用狀態(tài)時,創(chuàng)建C快緩沖區(qū),這樣就可以保證GPU在持續(xù)工作過程中)。

B.? 當(dāng)GPU計算速度(fps)慢于硬件刷新率(Hz)時:

? ? ? 通常來說,幀率超過刷新頻率只是一種理想的狀況,在超過60fps的情況下,GPU所產(chǎn)生的幀數(shù)據(jù)會因?yàn)榈却齎SYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實(shí)際的新的數(shù)據(jù)可以顯示。但是我們遇到更多的情況是幀率小于刷新頻率。

fps小于Hz的情況

? ? ? 在這種情況下,某些幀顯示的畫面內(nèi)容就會與上一幀的畫面相同,也就是我們常說的“丟幀”。又或者另外一種情況,用戶在使用過程中幀率從超過60fps突然掉到60fps以下,這樣就會發(fā)生LAG,JANK,HITCHING等卡頓掉幀的不順滑的情況。這也是用戶感受不好的原因所在。

導(dǎo)致卡頓的原因:

在了解繪制以及卡頓的原因以后,我們就可以結(jié)合一些性能優(yōu)化工具來對我們的APP進(jìn)行優(yōu)化,從而使我們的APP具備潤滑流暢的快感:

A. ? 性能監(jiān)測工具

在優(yōu)化性能之前,首要的是需要在?App中找到需要優(yōu)化的點(diǎn),進(jìn)行逐一優(yōu)化,各個擊破,這時就需要用到一些的性能監(jiān)測工具,從哪里找這些工具呢??谷歌為我們這群懶人們提供了一個高效且?易用的可視化工具:Profile GPU Render。

位置:

開發(fā)者選項(xiàng)--->?Profile GPU Render--->On Screen as Bars

?界面介紹:

這樣就可以在屏幕下面看到如心電圖一樣的動態(tài)性能監(jiān)視圖,當(dāng)在使用APP的過程中,下部的監(jiān)視圖會從左至右進(jìn)行繪制。整個屏幕可以分為三個部分,頭頂部的心電圖代表的是,通知欄的繪制性能情況(一般不咋用),下部是我們著重需要重點(diǎn)關(guān)注的,也就是當(dāng)前激活的Application(我們需要優(yōu)化的app)的性能心電圖。

性能監(jiān)視圖

在下部的監(jiān)視圖中,除了那根明顯的綠色線條用于標(biāo)注16ms以外(每個程序員在優(yōu)化的過程中需要保證的就是將在整個APP運(yùn)行過程中,整個心電圖中的每一根線都在這條綠線以下),其下部的心電圖根據(jù)顏色分為三個部分:

A.1 ? 藍(lán)色代表隊(duì):在該幀的計算過程中CPU進(jìn)行轉(zhuǎn)換及緩存(創(chuàng)建和展示displaylist中的內(nèi)容)所消耗掉時間。而繪制時間則是由轉(zhuǎn)換成GPU可以識別的格式所消耗掉時間(由drawable圖片通過命令或者通過canvas繪制兩種方式)以及將格式緩沖進(jìn)Displaylist所消耗掉時間所構(gòu)成。

如果這個部分的消耗很大的話,說明當(dāng)前幀中可能有很多的用到圖片資源的控件需要繪制,活著說當(dāng)前頁面中有一個自定義控件非常復(fù)雜,需要消耗很多的邏輯計算時間。?

格式轉(zhuǎn)換及緩存兩部分

A.2 ? 紅色代表隊(duì):在該幀的計算過程中將結(jié)果存儲進(jìn)DisplayList的集合中,然后利用該集合匯總的信息,轉(zhuǎn)換成圖像,也就是繪制圖像執(zhí)行所消耗掉時間。通常Android中的會通過Open GL ES API來將集合中的信息傳入到GPU當(dāng)中,隨后通過GPU將信息轉(zhuǎn)化成像素點(diǎn),繪制在屏幕上。

也就是說,如果我們需要繪制的信息越多的情況下,比如說華麗復(fù)雜的自定義時,就會消耗很多時間,同時還有重要的一點(diǎn)就是:如果圖片中出現(xiàn)重復(fù)繪制的情況,也會相應(yīng)增加GPU的無謂消耗時間。這部分的內(nèi)容也會增加紅色代表隊(duì)的時間消耗。

A.3 ? 橙色代表隊(duì):在該幀的計算過程中所CPU在完成邏輯計算后,但需要等待GPU繪制完成,才能進(jìn)行下一步工作,所消耗掉的時間。

這里如果出現(xiàn)增長的話,就是意味著你的GPU上承載著高負(fù)荷的工作,也就是說你當(dāng)前有非常多的Open GL ES API命令需要執(zhí)行。

補(bǔ)充:

在上面提到的諸如Open GL ES API,CPU進(jìn)行轉(zhuǎn)換及緩存等一些名詞是不是很懵逼?(別裝,肯定的),在這里我繼續(xù)補(bǔ)充一下從谷歌提供的視頻中針對這些步驟的詳細(xì)解釋:

1. ? XML布局中的文弱少女是如何轉(zhuǎn)換成的頻幕上的妖艷賤貨的:

首先需要通過resterization(柵格化)將圖片,圖形或者文字一類轉(zhuǎn)化成頻幕可以展示的像素的過程。 當(dāng)然,這個過程絕逼是非常消耗時間的, 這里的GPU就是將這個漫長的過程變得飛快。

由于GPU對柵格化的基本格式有特定的要求,主要格式為:polygons,textures和images。CPU在這里負(fù)責(zé)將圖片,文字首先轉(zhuǎn)化成成 polygons,textures和images格式,然后傳遞給GPU進(jìn)行柵格化處理。

例如一個button需要先在CPU中進(jìn)行格式轉(zhuǎn)換(polygons等),然后傳遞給GPU進(jìn)行柵格化

2. ? 看似平整的地面,總有暗坑等你踩

但是也許你可以發(fā)現(xiàn)這里有一個坑比的地方,button轉(zhuǎn)換成特定的polygons是一個時間消耗過程,再由polygons傳遞給GPU又是一個時間消耗過程,而由CPU傳遞給GPU同樣是一個非常耗時的過程。這樣就意味著,GPU中進(jìn)行柵格化所節(jié)省下來的時間,可能在這里被消耗大半。

幸運(yùn)的是Open GL考慮到了這一點(diǎn),它提供了一個類似緩存到機(jī)制:CPU上傳到GPU中的資源,可以作為緩沖保存在GPU當(dāng)中,在下次再次利用的過程中,就省去了CPU的格式轉(zhuǎn)換和CPU上傳到GPU的過程消耗。Android系統(tǒng)就靈活利用到了這一點(diǎn),它在系統(tǒng)啟動過程中,就將主題中的系統(tǒng)資源以一個單一polygons的形式上傳至GPU,以后在調(diào)用系統(tǒng)資源時,就可以直接在GPU中取到相應(yīng)資源,而不需要轉(zhuǎn)換和傳遞。這就是加載Android系統(tǒng)圖片為啥這么快的原因。

然而,有了這個機(jī)制就可以萬事大吉了?并不,隨著UI畫的圖越來越詭異,產(chǎn)品設(shè)計的動畫越來越彪悍,GPU的緩沖機(jī)制變得幾乎形同虛設(shè),因?yàn)槊恳粋€圖片都是不同的,都無法服用,因此GPU中的緩存資源只能通過不斷被覆蓋來達(dá)到相應(yīng)效果,謝天謝地,Android系統(tǒng)提供了差異化繪制機(jī)制,簡單來說就是緩存的舊資源與即將寫入的新資源進(jìn)行對比,只對發(fā)生了改變的部分進(jìn)行重新處理。以此緩解GPU的壓力。

3. ? 媽媽說,破掉的東東要扔掉

在上面,我們有提到,有DisplayList對CPU處理好的格式資源以及需要進(jìn)行的相應(yīng)的繪制指令,進(jìn)行接收。這里的DispalyList在特殊情況下,可以對其接收的信息進(jìn)行復(fù)用,舉例來說:

如果一個button改變了其位置:GPU可以將DisplayList中的信息可以進(jìn)行復(fù)用。

如果一個button改變了其大小或者其形狀,表面顏色發(fā)生改變(視覺上的形體,色彩改變 ):GPU就無法使用之前CPU傳遞來的DisplayList,需要通過CPU進(jìn)行重新格式轉(zhuǎn)換,然后將命令和轉(zhuǎn)換好的資源存入一個新的DisplayList當(dāng)中。

4. ? Android系統(tǒng)都是強(qiáng)迫癥!

我們需要注意的是,我們的每一個小小的改變,都會引起整個view的大量工作,舉例來說:

4.a? 改變界面中一個view或viewgroup中的子view的尺寸大小,原先的measure流程測過的所有尺寸就無效了,系統(tǒng)會遍歷整個view hierarchy,對每個view進(jìn)行重新measure。

4.b? 改變界面中一個view或者一個viewgroup中的子view的位置,原先界面中的所有view位置信息就全部無效了,系統(tǒng)會通過requestLayout來重新確定每個view的位置。

這些個步驟都是非常耗時的,當(dāng)界面中有龐大且復(fù)雜的view出現(xiàn)時,就會嚴(yán)重影響程序的性能。這里我們可以通過將整個view hierarchy扁平化處理,這樣可以有效的降低改動部分view,所造成的整個view hierarchy重新測量或重新定位所消耗掉時間。

Hierarchy Viewer視圖

B.? Overdraw

在上面的介紹中,我們得知,在CPU通過Open GL將轉(zhuǎn)換后的資源交給GPU進(jìn)行繪制時,如果出現(xiàn)大量重復(fù)繪制的情況,會加重GPU的負(fù)擔(dān),那么降低重復(fù)繪制的內(nèi)容,就是我們需要關(guān)注的其中一個重點(diǎn)。那么,問題來了,重復(fù)繪制查找哪家強(qiáng)?Android系統(tǒng)中給我們同樣提供了一個用于監(jiān)測重復(fù)繪制的工具---Overdraw。

Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次。在多層次的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制的操作,這就會導(dǎo)致某些像素區(qū)域被繪制了多次。這就浪費(fèi)大量的CPU以及GPU資源。

當(dāng)設(shè)計上追求更華麗的視覺效果的時候,我們就容易陷入采用越來越多的層疊組件來實(shí)現(xiàn)這種視覺效果的怪圈。這很容易導(dǎo)致大量的性能問題,為了獲得最佳的性能,我們必須盡量減少Overdraw的情況發(fā)生。

幸運(yùn)的是,谷歌為我們考慮到了這一點(diǎn),在系統(tǒng)中的手機(jī)設(shè)置里面的開發(fā)者選項(xiàng),步驟為:"系統(tǒng)設(shè)置"-->"開發(fā)者選項(xiàng)"-->"調(diào)試GPU過度繪制"打開Show GPU Overdraw的選項(xiàng),可以觀察UI上的Overdraw情況。在勾選該選項(xiàng)后,頻幕中會出現(xiàn)四種色調(diào):

Overdraw四種繪制情況

其中,四種顏色:藍(lán)色,綠色,粉色,紅色分別代表:1次繪制,2次繪制,3次繪制,四次繪制。還有一種是原色,代表的是無重復(fù)繪制。

多次繪制的原因有很多,其中有可能是因?yàn)槟鉛I布局存在大量重疊的部分,還有的時候是因?yàn)榉潜仨毜闹丿B背景活著繪制了不可見的UI元素。這里需要提醒的是,通常情況下,我們創(chuàng)建的Activity在默認(rèn)情況下,theme會給window設(shè)置一個純色的背景. 因?yàn)槲覀冞@里不想使用這個默認(rèn)的背景,故而給layout加了一層背景, 導(dǎo)致了多重繪制背景.可以通過:

? ? ? getWindow().setBackgroundDrawable(null);

將這一層默認(rèn)添加的背景消除。

到此為止,是不是發(fā)現(xiàn),隨著科技和優(yōu)化機(jī)制的革新,更漂亮,更華麗的效果也得以實(shí)現(xiàn),兩者相互推進(jìn),使得我們的應(yīng)用越來越完美,這就是我們?yōu)楹涡枰獌?yōu)化我們APP的根本原因。本篇文章只是向程序優(yōu)化的第一步,隨后,我會繼續(xù)學(xué)習(xí)視頻和相關(guān)文檔,推出新的文章,與大家一起分享和討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,604評論 2 380

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