Android渲染系列之原理概述篇

屏幕硬件

渲染離不開屏幕礁竞,Android中的屏幕碎片化比較嚴重模捂,尺寸大小不一枫绅,材質(zhì)也是屏幕重要的因素寓搬。 目前智能手機主流的屏幕可分為兩大類即液晶顯示器;

  • LCD (Liquid Crystal Display) 液晶顯示器
  • OLED (Organic Light Emitting Diode)即有機發(fā)光二極管

CPU與GPU

除了屏幕 UI渲染還要依賴另外兩個核心的硬件: 「CPU」「GPU」镣典。

  • CPU (Central Processing Unit兄春,中央處理器)赶舆,是計算機系統(tǒng)的運算和控制核心,是信息處理九串、程序運行的最終執(zhí)行單元
  • GPU (Graphics Processin Unit品山,圖形處理器)肘交,是一種專門用于圖像運算的處理器,在計算機系統(tǒng)中通常被稱為"「顯卡」"的核心部件就是 GPU。

UI 組件在繪制到屏幕之前市栗,都需要經(jīng)過 Rasterization (柵格化)操作填帽,而柵格化又是一個非常耗時的操作。GPU的引入就是為了加快柵格化

GPU簡述

在沒有 GPU 的時代嘹悼,UI 的繪制任務(wù)完全由 CPU 完成,CPU 既要負責 UI 繪制還要負責內(nèi)存管理限匣、邏輯運算等其他任務(wù),這就導(dǎo)致 CPU 的任務(wù)繁多毁菱,因此性能也會有影響. CPU與GPU 在結(jié)構(gòu)設(shè)計上完全不同米死,如下圖:

  • 黃色 Control 為控制器,用于協(xié)調(diào)控制整個 CPU 的運行贮庞,包括指令讀取哲身、控制其他模塊的運行等;
  • 綠色的 ALU (Arithmetic Logic Unit) 是算數(shù)邏輯單元,用于進行數(shù)學(xué)贸伐、邏輯運算;
  • 橙色的 Cache 和 DRAM 分別為高速緩存和 RAM勘天,用于存儲信息

從上圖可以看出脯丝,CPU 的控制器較為復(fù)雜材蹬,而 ALU 數(shù)量較少闸溃,因此 CPU 更擅長各種復(fù)雜的邏輯運算府蛇,但不擅長數(shù)學(xué)尤其是浮點運算 而 GPU 的設(shè)計正是為實現(xiàn)大量數(shù)學(xué)運算。GPU 的控制器比較簡單,但包含大量 ALU酷窥。GPU 中的 ALU 使用了并行設(shè)計糕珊,且具有較多的浮點運算單元坟乾。「可以幫助我們加快柵格化操作」渺绒。

在處理3D場景時殷绍,通常Android使用OpenGL ES,不過在Android 7.0 添加了對 Vulkan 的支持

Vulkan 的設(shè)計目標是取代 OpenGL姿锭,Vulkan 是一個相當?shù)图墑e的 API掌唾,并且提供并行的任務(wù)處理。Vulkan 還能夠渲染 2D 圖形應(yīng)用程序挽拔。除了其較低的 CPU 使用率,Vulkan 還能夠更好地在多個 CPU 內(nèi)核之間分配工作猾编。在功耗、多核優(yōu)化提升繪圖調(diào)用上有著非常明顯的優(yōu)勢。

而且從性能考慮稿存,「GPU是用戶進程唯一可以直接操作的硬件」,是個特例

這與Android 「Client-Sever」 架構(gòu)不同,一般情況下 應(yīng)用與硬件交互都需要經(jīng)過binder委托給系統(tǒng)服務(wù)

軟件繪制與硬件繪制

有了GPU归形,便可以在渲染中加速柵格化的過程哈误,軟件繪制往往是cpu借助2d渲染引起skia的能力,去完成渲染,一般實在低端機器

軟件繪制使用 Skia 庫戈鲁,它是一款能在低端設(shè)備,如手機呈現(xiàn)高質(zhì)量的 2D 跨平臺圖形框架肠鲫,類似 Chrome、Flutter 內(nèi)部使用的都是 Skia 庫

Android圖形系統(tǒng)

有了上面知識之后,我們視角切入到Android系統(tǒng)中來澎埠,先來看下Android的圖像系統(tǒng)組件 無論開發(fā)者使用什么渲染 API,一切內(nèi)容都會渲染到 「Surface」 上始藕。Surface 表示緩沖區(qū)隊列中的生產(chǎn)方蒲稳,而緩沖區(qū)隊列通常會被 SurfaceFlinger 消耗。在 Android 平臺上創(chuàng)建的每個窗口都由 Surface 提供支持伍派。所有被渲染的可見 Surface 都被 SurfaceFlinger 合成到屏幕江耀。

圖形架構(gòu)

下圖顯示了關(guān)鍵組件如何協(xié)同工作

我們可以看到 有關(guān)鍵的兩個角色,

  • IMAGE STREAM PRODUCERS(圖像生產(chǎn)者)诉植、
  • 另外一個叫IMAGE STREAM CONSUMERS (圖像消費者)祥国,

整個圖形渲染系統(tǒng)就是采用了「生產(chǎn)者消費者模式」。屏幕渲染的核心倍踪,是對圖像數(shù)據(jù)的生產(chǎn)和消費系宫。 「生產(chǎn)和消費的對象,是BufferQueue 里的 Buffer」建车。

數(shù)據(jù)流

這是描述Buffer是如何流動起來的扩借,是對上面流程的的數(shù)據(jù)化抽象

左側(cè)的對象是生成圖形緩沖區(qū)的渲染器,如主屏幕缤至、狀態(tài)欄和系統(tǒng)界面潮罪。SurfaceFlinger 是合成器,而硬件混合渲染器是混合渲染器领斥。

「BufferQueues 是 Android 圖形組件之間的粘合劑」嫉到。它們是一對隊列,可以調(diào)解緩沖區(qū)從生產(chǎn)方到消耗方的固定周期月洛。一旦生產(chǎn)方移交其緩沖區(qū)何恶,SurfaceFlinger 便會負責將所有內(nèi)容合成到顯示部分。 抽象成生成消費者模型的 BufferQueue 通信過程

BufferQueue 包含將圖像流生產(chǎn)方與圖像流消耗方結(jié)合在一起的邏輯嚼黔。

  • 圖像生產(chǎn)方的一些示例包括由相機 HAL 或 OpenGL ES 游戲生成的相機預(yù)覽细层。
  • 圖像消耗方的一些示例包括 SurfaceFlinger 或顯示 OpenGL ES 流的另一個應(yīng)用,如顯示相機取景器的相機應(yīng)用唬涧。

BufferQueue 是將緩沖區(qū)池與隊列相結(jié)合的數(shù)據(jù)結(jié)構(gòu)疫赎,它使用 Binder IPC 在進程之間傳遞緩沖區(qū)。

Vsync 有兩種碎节,Vsync-app 和 Vsync-sf捧搞,前者用于告訴 Choreographer,是時候協(xié)調(diào) app 生產(chǎn)buffer了狮荔;后者用于告訴 SurfaceFlinger胎撇,是時候來消費buffer合成并顯示到屏幕了

如果把應(yīng)用程序圖形渲染過程當做一次繪畫過程,那么繪畫過程中 Android 的各個圖形組件的作用是:

  • 畫筆: Skia 或者 OpenGL轴合。我們可以用 Skia 畫筆繪制 2D 圖形创坞,也可以用 OpenGL 來繪制2D/3D 圖形。正如前面所說受葛,前者使用 CPU 繪制题涨,后者使用 GPU 繪制。
  • 畫紙: Surface总滩。所有的元素都在 Surface 這張畫紙上進行繪制和渲染纲堵。在 Android 中Window 是 View 的容器,「每個窗口都會關(guān)聯(lián)一個 Surface」闰渔。而 「WindowManager 則負責管理這些窗口席函,并且把它們的數(shù)據(jù)傳遞給 SurfaceFlinger」
  • 畫板: Graphic Buffer冈涧。Graphic Buffer 緩沖用于應(yīng)用于應(yīng)用程序圖形的繪制茂附,「在 Android4.1 之前使用的是雙緩沖機制;在 Android 4.1之后正蛙,使用的是三緩沖機制」
  • 顯示: SurfaceFlinger营曼。它將 WindowManger 提供的所有 Surface乒验,通過硬件合成器Hardware Composer 合成layer并輸出到顯示屏。

使用畫筆 Skia /OpenGL 將內(nèi)容繪制到 Surface 上蒂阱,

「繪制的過程中如果使用 Open GL 渲染锻全,那便是硬件加速」,否則純靠 CPU 繪制染柵格化的過程就叫軟件繪制录煤。對于硬件繪制鳄厌,我們通過調(diào)用 OpenGL ES 接口利用 GPU 完成繪制。

對 BufferQueue 理解了的話妈踊,那么可以延伸思考一下:

  • 生產(chǎn)者何時 dequeueBuffer了嚎,消費者何時 require Buffer?兩者是怎么協(xié)調(diào)工作的廊营?
  • BufferQueue 里可能會有多少個 Buffer新思?里面含有不同數(shù)目的 Buffer,對 App 的顯示工作(具體來說赘风,就是幀率)會有怎樣的影響夹囚?

要了解這兩個問題,就要了解:

  • Choreographer 和 Vsync
  • Double Buffer 和 Triple Buffer

更多精彩內(nèi)容關(guān)注公眾號 「Android茶話會」

這在后面會有介紹

渲染系統(tǒng)的持續(xù)演進

為了使AndroidUI更加的絲滑邀窃,渲染系統(tǒng)也是下足了功夫荸哟,在持續(xù)演進,

Android 4.0 默認開啟硬件加速

  • Android 3.0瞬捕,API 11開始引入硬件加速

Android 2D 渲染管道支持硬件加速鞍历,也就是說,在 View 的畫布上執(zhí)行的所有繪制操作都會使用 GPU肪虎。啟用硬件加速需要更多資源劣砍,因此應(yīng)用會占用更多內(nèi)存

  • Android4.0,API 14默認開啟硬件加速

由于現(xiàn)在Android4.4以下的手機基本已被淘汰扇救,所以以后的版本默認都是開啟了硬件加速刑枝。

軟件渲染

再沒有硬件加速之前主要是通過skia這種軟件方式來渲染UI,如下圖所示

整個流程如上圖所示:

  • Surface:每個 View 都由某一個窗口管理迅腔,而每一個窗口都會關(guān)聯(lián)有一個 Surface装畅。
  • Canvas:通過 Surface 的 Lock 函數(shù)獲得一個 Canvas,「Canvas 可以簡單理解為 Skia 底層接口的封裝」沧烈。
  • Graphic Buffer:SurfaceFlinger 會幫助我們托管一個 BufferQueue掠兄,我們從 BufferQueue中拿到 Graphic Buffer,然后「通過 Canvas 以及 Skia 將繪制內(nèi)容柵格化到上面」
  • SurfaceFlinger:通過 Swap Buffer 把 Front Graphic Buffer 的內(nèi)容交給 SurfaceFlinger,最后硬件合成器 Hardware Composer 合成并輸出到顯示屏蚂夕。

整個渲染流程看上去比較簡單顶霞,但是正如前面所說姐直,CPU 對于圖形處理并不是那么高效偶宫,這個過程完全沒有利用 GPU 的高性能琳要。

硬件加速

Android3.0,支持硬件加速牍汹,需要手動打開,Android4.0就默認開啟硬件加速了柬泽,開啟硬件加速流程如下

硬件加速繪制最核心就是通過 GPU 完成 Graphic Buffer 的內(nèi)容繪制慎菲。 在Android 5.0之前,Android應(yīng)用程序的Main Thread不僅負責用戶輸入锨并,同時也是一個OpenGL線程露该,也負責渲染UI。通過引進Render Thread第煮,我們就可以將UI渲染工作從Main Thread釋放出來解幼,交由Render Thread來處理,從而也使得Main Thread可以更專注高效地處理用戶輸入包警,這樣使得在提高UI繪制效率的同時撵摆,也使得UI具有更高的響應(yīng)

此外硬件繪制還引入了一個 DisplayList 的概念,每個 View 內(nèi)部都有一個DisplayList害晦,當某個 View 需要重繪時特铝,將它標記為 Dirty。當需要重繪時壹瘟,僅僅只需要重繪一個 View 的 DisplayList鲫剿,而不是像軟件繪制那樣需要向上遞歸。這樣可以大大減少繪圖的操作數(shù)量稻轨,因而提高了渲染效率

Android 4.1黃油計劃(Project Butter)

Google 在 2012 年的 I0 大會上宣布了 Project Butter 黃油計劃灵莲,并且在Android 4.1 中正式開啟了這個機制。 Project Butter 主要包含三個組成部分殴俱,

  • VSYNC
  • Triple Buffer
  • Choreographer政冻。

其中 VSYNC (Vertical Synchronization) 是理解 Project Butter 的核心

Vsync

對于 Android 4.0 CPU 可能會因為在忙其他的事情,導(dǎo)致沒來得及處理 UI 繪制线欲,導(dǎo)致卡頓赠幕。 為了解決這個問題,Project Butter 引入了 VSYNC询筏,「它類似于時鐘中斷榕堰,每收到 VSYNC 中斷,CPU 會立即準備 Buffer 數(shù)據(jù)」

由于大部分顯示設(shè)備刷新頻率都是 60 Hz (一秒刷新 60次) 逆屡,也就是說一數(shù)據(jù)的準備都要在 16ms 內(nèi)完成

這樣應(yīng)用總是在 VSYNC 邊界上開始繪制圾旨,而 SurfaceFlinger 總是在 VSYNC 邊界上進行合成。這樣可以消除卡頓魏蔗,并提升圖形的視覺表現(xiàn)砍的。

三緩沖機制(Triple Buffering)

在Android 4.1 之前,Android 使用雙緩沖機制莺治。

通常不同的 View 或者Activity 都會共用同一個 Window廓鞠,也就是共用同一個 Surface。而每個 Surface 都會有一個 BufferQueue 緩存隊列谣旁,但是這個隊列會由 SurfaceFlinger 管理通過匿名共享內(nèi)存機制與 App 應(yīng)用層交互

從上圖可以看出

  • 每個 Surface 對應(yīng)的 BufferQueue 內(nèi)部都有兩個 Graphic Buffer床佳,一個用于繪制一個用于系那是。應(yīng)用會把內(nèi)容先繪制到離屏緩沖區(qū) (OffScreen Buffer) 榄审,在需要顯示時砌们,才把離屏緩沖區(qū)的內(nèi)容通過 Swap Buffer 復(fù)制到 Front Graphic Buffer 中。
  • 這樣 SurfaceFlinge 就拿到了某個 Surface 最終要顯示的內(nèi)容搁进,

但是同一時間我們可能會有多個 Surface浪感。這里面可能是不同應(yīng)用的 Surface,也可能是同一個應(yīng)用里面類似SurfaceView 和 TextureView饼问,它們都會有自己單獨的 Surface影兽。

  • 這個時候 SurfaceFlinger 把所有 Surface 要顯示的內(nèi)容統(tǒng)一交給 Hardware Composer,它會根據(jù)位置莱革、Z- Order 順序等信息合成為最終屏募需要顯示的內(nèi)容赢笨,而這個內(nèi)容交給系統(tǒng)的頓緩沖區(qū) Frame Buffer 來顯示 (Frame Buffer 是非常底層的,可以理解為屏幕顯示的抽象)

如果只有兩個Graphic Buffer 緩存區(qū)A和 B驮吱,如果 CPU/GPU 繪制過程較長茧妒,超過了一個 VSYNC 信號周期,因為緩沖區(qū) B 中的數(shù)據(jù)還沒有準備完成左冬,所以只能繼續(xù)展示A 緩沖區(qū)的內(nèi)容桐筏,這樣緩沖區(qū)A和 B 都分別被顯示設(shè)備和 GPU 占用,CPU 無法準備下一的數(shù)據(jù)拇砰,如下所示

如果再提供一個緩沖區(qū)梅忌,CPU、GPU 和顯示設(shè)備都能使用各自的緩沖區(qū)工作除破,互不影響牧氮。 簡單來說,三緩沖機制就是在雙緩沖機制的基礎(chǔ)上增加了一個 Graphic Buffer 緩沖區(qū)瑰枫,這樣可以最大限度的利用空閑時間踱葛,

帶來的壞處是多使用了一個 Graphic Buffer 所占用的內(nèi)存丹莲。

我們看到,在第一次Jank內(nèi)尸诽,CPU使用了第三塊緩沖區(qū)甥材,自己計算了C幀的數(shù)據(jù),

假如此時沒有三緩沖性含,那么cpu就只能再繼續(xù)等下一個vsync信號洲赵,也就是在圖中藍色A塊的地方,才能開始計算C幀數(shù)據(jù)商蕴,就又引發(fā)下一次卡頓叠萍。

我們看到,「通過引入三緩沖绪商,雖然不能避免卡頓問題苛谷,但是卻可以大幅優(yōu)化卡頓問題」,尤其是「避免連續(xù)卡頓」部宿,但是,三緩沖也有缺點瓢湃,就是「內(nèi)存消耗多」理张,所以系統(tǒng)并非一直開啟三緩沖,要想真正解決問題绵患,還需要在cpu層對數(shù)據(jù)盡量優(yōu)化雾叭,從而減小cpu和gpu的計算量,

編舞者Choreographer

這個名字起的非常貼合且很文藝落蝙,舞蹈是有節(jié)奏的织狐,節(jié)奏使舞蹈的每個動作更加協(xié)調(diào)和連貫; 視圖刷新也是如此,Choreographer 可以接收系統(tǒng)的 VSYNC 信號

Choreographer 和 Vsync 共同解決生產(chǎn)者何時生產(chǎn)筏勒,消費者何時消費的問題

因此需要一個「協(xié)調(diào)者」來協(xié)調(diào)這個工作移迫。 Android 這里的協(xié)調(diào)者就是 Choreographer——a person who composes the sequence of steps and moves for a performance of dance. Choreographer 協(xié)調(diào)生產(chǎn)者什么時候去生產(chǎn)——也就是什么時候去繪制一幀。既然要協(xié)調(diào)管行,那么肯定是需要有一個協(xié)調(diào)的依據(jù)厨埋,這個依據(jù)就是 Vsync 信號——也就是垂直同步信號

具體來說是 Vsync_app

Android 4.2 (Jelly Bean)

增加檢測過度繪制工具,開發(fā)者模式打開檢查開關(guān)之后捐顷,可以通過不同顏色反應(yīng)過度繪制的程度

Android 5.0 (Lollipop)

RenderThread

最初的 Android 版本里面是沒有渲染線程的荡陷,渲染工作都是在主線程完成,使用的也都是 CPU 迅涮,調(diào)用的是 libSkia 這個庫废赞, Android5.0之后,進一步提升渲染性能叮姑。所有的GL 命令執(zhí)行都放到單獨線程RenderThread(渲染線程)「中唉地,僅與GPU對話,渲染線程在 RenderNode 中存有染頓的所有信息,可以做些屬性動畫渣蜗,這樣即便主線程有耗時操作的時候也可以保證動畫流程屠尊。 是當UI開啟硬件加速后,用于分擔主線程繪制任務(wù)的渲染線程耕拷,主線程的 draw 函數(shù)并沒有真正的執(zhí)行 drawCall 讼昆,而是把要 draw 的內(nèi)容記錄到」** DIsplayList 里面,同步到 RenderThread **中骚烧,一旦同步完成浸赫,主線程就可以被釋放出來做其他的事情,RenderThread 則繼續(xù)進行渲染工作

Android 7.0 (Nougat)

支持Vulkan

GL的替代品

Android 8.0 (Oreo)

Bitmap內(nèi)存轉(zhuǎn)移至native內(nèi)存

  • 2.3之前的像素存儲需要的內(nèi)存是在native上分配的赃绊,并且生命周期不太可控既峡,可能需要用戶自己回收。
  • 2.3-7.1之間碧查,Bitmap的像素存儲在Dalvik的Java堆上运敢,
  • 4.4之前的甚至能在匿名共享內(nèi)存上分配(Fresco采用),
  • 8.0之后的像素內(nèi)存又重新回到native上去分配忠售,「不需要用戶主動回收」传惠,8.0之后圖像資源的管理更加優(yōu)秀,極大降低了OOM

NativeAllocationRegistry是Android 8.0引入的一種輔助自動回收native內(nèi)存的一種機制稻扬,當Java對象因為GC被回收后卦方,NativeAllocationRegistry可以輔助回收Java對象所申請的native內(nèi)存

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泰佳,隨后出現(xiàn)的幾起案子盼砍,更是在濱河造成了極大的恐慌,老刑警劉巖逝她,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浇坐,死亡現(xiàn)場離奇詭異,居然都是意外死亡黔宛,警方通過查閱死者的電腦和手機吗跋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宁昭,“玉大人跌宛,你說我怎么就攤上這事』蹋” “怎么了疆拘?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寂曹。 經(jīng)常有香客問我哎迄,道長回右,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任漱挚,我火速辦了婚禮翔烁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旨涝。我一直安慰自己蹬屹,他們只是感情好,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布白华。 她就那樣靜靜地躺著慨默,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弧腥。 梳的紋絲不亂的頭發(fā)上厦取,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天,我揣著相機與錄音管搪,去河邊找鬼虾攻。 笑死,一個胖子當著我的面吹牛更鲁,可吹牛的內(nèi)容都是我干的霎箍。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼岁经,長吁一口氣:“原來是場噩夢啊……” “哼朋沮!你這毒婦竟也來了蛇券?” 一聲冷哼從身側(cè)響起缀壤,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纠亚,沒想到半個月后塘慕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡蒂胞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年图呢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骗随。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛤织,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸿染,到底是詐尸還是另有隱情指蚜,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布涨椒,位于F島的核電站摊鸡,受9級特大地震影響绽媒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜免猾,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一是辕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猎提,春花似錦获三、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚓炬,卻和暖如春松逊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肯夏。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工经宏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驯击。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓烁兰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親徊都。 傳聞我的和親對象是個殘疾皇子沪斟,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

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