Android - 性能優(yōu)化

前言

性能優(yōu)化的過(guò)程分兩部分:

  • 發(fā)現(xiàn)性能瓶頸
  • 制定方案,解決性能問(wèn)題

解決性能問(wèn)題的方案需要具體情況具體分析甲棍,并沒(méi)有完全固定的路子简识,更多的是靠經(jīng)驗(yàn)的積累,本文不做涉及。但是發(fā)現(xiàn)性能瓶頸確實(shí)有著固定的方法七扰。本文主要介紹 如何找到性能瓶頸 奢赂。

如何找到性能瓶頸

常用的性能檢測(cè)工具是traceview,集成于 Android Device Monitor 中戳寸。從Android Studio3.0開(kāi)始呈驶, Android Device Monitor 被廢棄,取而代之的是 Android Profiler 疫鹊,其中提供了 Memory Prodiler 袖瞻、CPU ProfilerNetwork Prodiler三大功能拆吆。

內(nèi)存優(yōu)化(包括內(nèi)存泄漏)常用的是 MAT 或者 LeakCanary 聋迎,而 Memory Profiler 相當(dāng)于將 MAT 的簡(jiǎn)化版功能集成到 AS 中。相對(duì)的在性能優(yōu)化方面枣耀,CPU Profiler 相當(dāng)于將 traceview 的功能集成到了 AS 中霉晕。

所以,使用AS3.0之前版本的捞奕,可以使用traceview牺堰,而使用AS3.0以后版本的,除了traceview颅围,還可以選擇CPU Profiler伟葫。

如果想追蹤系統(tǒng)進(jìn)程的詳細(xì)數(shù)據(jù),以解決幀引起的界面卡頓等問(wèn)題院促,可以使用 systrace 筏养,本文不做涉及〕M兀可參考:
Perform on-device system tracing
systrace

traceview 使用方法

使用 traceview 需要首先使用 Debug 類進(jìn)行 插樁 渐溶,當(dāng)應(yīng)用執(zhí)行到被插樁的代碼時(shí)就會(huì)在手機(jī)sdcard中自動(dòng)生成 .trace 文件,之后使用 traceview 或者 AS(3.0以上版本)打開(kāi)文件即可弄抬。

一茎辐、插樁

插樁需要使用到 Debug 類,并且會(huì)在 sdcard 中生成 .trace 文件掂恕,所以你必須首先保證你的應(yīng)用具有寫外部存儲(chǔ)( WRITE_EXTERNAL_STORAGE )的權(quán)限拖陆。

在想要跟蹤的代碼邏輯開(kāi)頭和結(jié)尾處分別插樁:

// Starts recording a trace log with the name you provide. For example, the
// following code tells the system to start recording a .trace file to the
// device with the name "sample.trace".
Debug.startMethodTracing("sample");
...
// The system begins buffering the generated trace data, until your
// application calls stopMethodTracing(), at which time it writes
// the buffered data to the output file.
Debug.stopMethodTracing();

生成的 .trace 文件會(huì)被保存在固定目錄下,與 getExternalFilesDir() 返回的目錄相同竹海,即 /sdcard/Android/data/[YOUR_PACKAGE_NAME]/files 下慕蔚。

請(qǐng)注意,如果您的應(yīng)用在未更改跟蹤日志名稱的情況下再次調(diào)用 startMethodTracing()斋配,則會(huì)覆蓋已保存至設(shè)備的現(xiàn)有日志孔飒。如果希望每次運(yùn)行都保存至不同的日志文件灌闺,可以使用如下代碼:

// Uses the SimpleDateFormat class to create a String with
// the current date and time.
SimpleDateFormat date =
        new SimpleDateFormat("dd_MM_yyyy_hh_mm_ss");
String logDate = date.format(new Date());
// Applies the date and time to the name of the trace log.
Debug.startMethodTracing(
        "sample-" + logDate);

如果系統(tǒng)在您調(diào)用 stopMethodTracing() 之前達(dá)到最大緩沖值,則會(huì)停止跟蹤并向管理中心發(fā)送通知坏瞄。 開(kāi)始和停止跟蹤的函數(shù)在您的整個(gè)應(yīng)用流程內(nèi)均有效桂对。 也就是說(shuō),您可以在 Activity 的 onCreate(Bundle) 函數(shù)中調(diào)用 startMethodTracing()鸠匀,在 Activity 的 onDestroy() 函數(shù)中調(diào)用 stopMethodTracing()蕉斜。

二、查看 .trace 文件

插好樁后缀棍,安裝應(yīng)用并運(yùn)行被檢測(cè)部分的功能宅此,然后就可以通過(guò) AS 或者 traceview 查看文件了。

使用 AS 查看

在AS中點(diǎn)擊 View - Tool Windows - Android File Explorer 打開(kāi) Android File Explorer :

/sdcard/Android/data/[YOUR_PACKAGE_NAME]/files 下即可找到生成的 .trace 文件爬范,雙擊文件即可打開(kāi)父腕。

將 .trace 文件保存至電腦,直接拖入AS窗口青瀑,也可直接打開(kāi)該視圖璧亮。

在打開(kāi)的視圖中,左上方可以選擇想要查看的線程斥难≈λ唬可以查看監(jiān)控期間指定線程運(yùn)行了多久、執(zhí)行了哪些方法哑诊、每個(gè)方法執(zhí)行了多久等等群扶。

其中有4個(gè)名詞需要解釋一下:

  • Wall Clock Time:壁鐘時(shí)間,表示實(shí)際經(jīng)過(guò)的時(shí)間搭儒,即進(jìn)入某個(gè)方法到退出該方法的時(shí)間穷当,不考慮線程是活動(dòng)還是休眠狀態(tài)提茁。
  • Thread time:線程時(shí)間淹禾,表示實(shí)際經(jīng)過(guò)的時(shí)間減去線程沒(méi)有消耗 CPU 資源(處于休眠)的時(shí)間部分。 對(duì)于任何給定函數(shù)茴扁,其線程時(shí)間始終少于或等于其壁鐘時(shí)間铃岔。 使用線程時(shí)間可以讓您更好地了解線程的實(shí)際 CPU 使用率中有多少是給定函數(shù)消耗的。
  • Inclusive Time:方法執(zhí)行自己代碼的時(shí)間 + 執(zhí)行自己child方法的時(shí)間峭火。
  • Exclusive Time:方法執(zhí)行自己代碼的時(shí)間毁习。
使用 traceview 查看

要使用 traceview 查看,需要首先將 .trace 文件保存到電腦:

adb pull /sdcard/Android/data/[YOUR_PACKAGE_NAME]/files/sample.trace D:\Documents\sample.trace

打開(kāi) Android Device Monitor 卖丸。AS3.0以前的版本纺且,就是LogCat所在的窗口,再切換一下tab頁(yè)即可稍浆。AS3.0以后载碌,進(jìn)入 android-sdk/tools/ 路徑猜嘱,運(yùn)行以下命令:

monitor

雖然 Android Device Monitor 的 DDMS 也有 File Explorer ,但是未 root 的手機(jī)嫁艇,查看不到上述路徑朗伶,因此只能將 .trace 文件保存到電腦查看。

在 Android Device Monitor 中步咪,依次點(diǎn)擊 File - Open File 论皆,選擇 .trace 文件路徑即可打開(kāi):

內(nèi)容與AS打開(kāi)時(shí)類似,相差較大的主要是圖標(biāo)部分猾漫,沒(méi)有AS的 Call Chart 直觀形象点晴。

其中也有4個(gè)概念:

  • Cpu Time:相當(dāng)于AS中的 Thread time。
  • Real Time:相當(dāng)于AS中的Wall Clock Time悯周。
  • Inclusive Time:同AS一樣觉鼻。
  • Exclusive Time:同AS一樣。
使用 AS 查看還是使用 traceview 查看

這個(gè)就見(jiàn)仁見(jiàn)智了队橙,根據(jù)我個(gè)人使用的感覺(jué)來(lái)看坠陈,建議使用AS查看。原因有二:

  • AS更簡(jiǎn)單捐康。不需要單獨(dú)打開(kāi)ADM仇矾,更不需要將 .trace 文件保存到電腦。
  • AS的調(diào)用圖( Call Chart )更加直觀解总,cpu時(shí)間的消耗一目了然贮匕。

Call Chart 的水平軸表示函數(shù)調(diào)用(或調(diào)用方)的時(shí)間段和時(shí)間,并沿垂直軸顯示其被調(diào)用者花枫。 下圖展示了一個(gè)調(diào)用圖表示例刻盐,并描繪了給定函數(shù)的 self time、children time 以及總時(shí)間的概念劳翰。

最后需要注意一點(diǎn)敦锌,跟蹤分析過(guò)程中,應(yīng)用的運(yùn)行速度會(huì)減慢佳簸。所以乙墙,通過(guò) traceview 得到的分析數(shù)據(jù)并不能精確反應(yīng)某個(gè)方法在實(shí)際執(zhí)行時(shí)的絕對(duì)時(shí)間。關(guān)于這一點(diǎn)生均,在最后的注意事項(xiàng)中再做詳細(xì)分析听想。

Google還提供了基于樣本的分析方式,以減少分析對(duì)運(yùn)行時(shí)性能的影響马胧。要啟用樣本分析汉买,需調(diào)用 Debug.startMethodTracingSampling() 方法(而非 Debug.startMethodTracing() 方法)。系統(tǒng)會(huì)定期收集樣本佩脊,直至調(diào)用 stopMethodTracing() 蛙粘。

CPU Profiler 使用方法

使用 CPU Profiler 進(jìn)行函數(shù)跟蹤比 traceview 更簡(jiǎn)單朽色。不需要做任何代碼上的植入,下面做一個(gè)簡(jiǎn)單的介紹:

首先组题,通過(guò) View - Tool Windows - Android Profiler 打開(kāi) Android Profiler 葫男。手機(jī)連接電腦后運(yùn)行應(yīng)用,在 Android Profiler 中會(huì)看到以下視圖:

左上角可以選擇設(shè)備和進(jìn)程崔列,點(diǎn)擊 CPU 區(qū)域梢褐,即可進(jìn)入CPU Profiler視圖:

左上角可以選擇跟蹤模式:

  • Sampled:按默認(rèn)采樣率捕獲應(yīng)用的調(diào)用堆棧。該模式的固有問(wèn)題是赵讯,如果應(yīng)用在一次捕獲后進(jìn)入一個(gè)函數(shù)并在下一次捕獲前退出該函數(shù)盈咳,則分析器不會(huì)記錄該函數(shù)調(diào)用。如果對(duì)此類生命周期很短的跟蹤函數(shù)感興趣边翼,可以使用“Instrumented”跟蹤鱼响。
  • Instrumented:以在每個(gè)函數(shù)調(diào)用的開(kāi)始和結(jié)束時(shí)記錄時(shí)間戳。 分析比較時(shí)間戳组底,以生成函數(shù)跟蹤數(shù)據(jù)丈积。 需要注意的是,設(shè)置與函數(shù)關(guān)聯(lián)的開(kāi)銷會(huì)影響運(yùn)行時(shí)性能债鸡,甚至分析數(shù)據(jù)江滨,對(duì)于生命周期相對(duì)較短的函數(shù),這一點(diǎn)更為明顯厌均。 此外唬滑,如果應(yīng)用短時(shí)間內(nèi)執(zhí)行大量函數(shù),則分析器可能會(huì)迅速超出它的文件大小限制棺弊,且不能再記錄更多跟蹤數(shù)據(jù)晶密。
  • Edit configurations:自定義采樣率。與 traceview 中的 Debug.startMethodTracingSampling() 類似模她。

.trace 文件的大小是有限制的稻艰。對(duì)于給定錄制,當(dāng)分析器到達(dá)該限制時(shí)缝驳,AS 將停止收集新數(shù)據(jù)(不過(guò)连锯,這不會(huì)停止記錄)归苍。 在執(zhí)行“Instrumented”跟蹤時(shí)用狱,這種情況通常會(huì)更快發(fā)生,因?yàn)榕c“Sampled”跟蹤相比拼弃,此類跟蹤在較短時(shí)間里會(huì)收集更多數(shù)據(jù)夏伊。

如果你使用的是Android 8.0(API 26)或更高版本的設(shè)備,則對(duì)于跟蹤數(shù)據(jù)的文件大小沒(méi)有限制吻氧,此值可忽略溺忧。不過(guò)咏连,你仍需留意每次記錄后設(shè)備收集了多少數(shù)據(jù),因?yàn)?AS 可能難以解析大型跟蹤文件鲁森。

點(diǎn)擊上方的“開(kāi)始錄制”按鈕祟滴,然后在應(yīng)用中操作執(zhí)行被追蹤的功能,結(jié)束后再點(diǎn)擊“停止錄制”按鈕歌溉。CPU Profiler 會(huì)自動(dòng)開(kāi)始分析并生成數(shù)據(jù)垄懂。

以上就是 CPU Profilertraceview 的使用方法。至于如何制定優(yōu)化方案痛垛,就不展開(kāi)了草慧,并沒(méi)有完全固定的路子。就我本例的 onRebuild() 方法而言匙头,是針對(duì)耗時(shí)的Contact構(gòu)造過(guò)程做了并行處理漫谷,將上百個(gè)有序的構(gòu)造過(guò)程平分到5個(gè)線程中并發(fā)執(zhí)行,然后再按順序合并數(shù)據(jù)到一個(gè)線程中蹂析。最終 onRebuild() 執(zhí)行速度從15秒提升到了2.5秒舔示,對(duì)我來(lái)說(shuō)已經(jīng)夠用了。

重要注意事項(xiàng)

無(wú)論是使用 traceview 還是 CPU Profiler 進(jìn)行函數(shù)跟蹤电抚,有一點(diǎn)需要注意:跟蹤分析過(guò)程中斩郎,應(yīng)用的運(yùn)行速度會(huì)減慢。所以喻频,分析數(shù)據(jù)并不能精確反應(yīng)某個(gè)方法在實(shí)際執(zhí)行時(shí)的絕對(duì)時(shí)間缩宜。下面是我在優(yōu)化項(xiàng)目中的 onRebuild(boolean) 方法時(shí),記錄的4組數(shù)據(jù)甥温,讓我們來(lái)對(duì)比一下:

  • 實(shí)際執(zhí)行時(shí)間:不啟用分析模式锻煌,正常運(yùn)行狀態(tài)下通過(guò)打印日志得到的實(shí)際執(zhí)行時(shí)間。
  • Profiler統(tǒng)計(jì)時(shí)間:使用 CPU Profiler 分析獲得的執(zhí)行時(shí)間姻蚓。
  • traceview統(tǒng)計(jì)時(shí)間:通過(guò)分析 traceview 產(chǎn)生的 .trace 文件宋梧,從中獲得的執(zhí)行時(shí)間。
  • traceview實(shí)際時(shí)間:使用 traceview 的情況下狰挡,通過(guò)打印日志得到的實(shí)際執(zhí)行時(shí)間捂龄。

為什么針對(duì) traceview 會(huì)例舉兩個(gè)時(shí)間呢?這是因?yàn)闇y(cè)試過(guò)程中發(fā)現(xiàn) traceview 自動(dòng)分析出來(lái)的時(shí)間比 實(shí)際執(zhí)行時(shí)間 不僅沒(méi)有慢加叁,反而快了很多倦沧,疑惑下又在啟用 traceview 的情況下通過(guò)以下代碼測(cè)算了一下實(shí)際的時(shí)間,這個(gè)倒是真的比 實(shí)際執(zhí)行時(shí)間 慢了它匕。

Debug.startMethodTracing("smssdk_onrebuild");
long curr = System.currentTimeMillis();
onRebuild(true);
Log.d(TAG, "onRebuild lasts: " + (System.currentTimeMillis() - curr));
Debug.stopMethodTracing();
實(shí)際執(zhí)行時(shí)間 Profiler統(tǒng)計(jì)時(shí)間 traceview統(tǒng)計(jì)時(shí)間 traceview實(shí)際時(shí)間
15s 35s 3.5s 64s

從上表數(shù)據(jù)可見(jiàn)展融,無(wú)論是 CPU Profiler 還是 traceview ,統(tǒng)計(jì)出來(lái)的時(shí)間都不能準(zhǔn)確代表實(shí)際執(zhí)行時(shí)間豫柬。更甚者告希,traceview自動(dòng)分析出來(lái)的數(shù)據(jù)也與traceview跟蹤模式下實(shí)際的時(shí)間有巨大差別扑浸,關(guān)于這一點(diǎn),我沒(méi)找到詳細(xì)的解釋燕偶,如果有人知道喝噪,還望不吝賜教。

既然跟蹤分析得到的時(shí)間都不能表示實(shí)際的時(shí)間指么,那么這些數(shù)據(jù)是不是沒(méi)用呢仙逻?當(dāng)然不是!它們至少在以下兩個(gè)方面具有價(jià)值:

  • 在一次檢測(cè)得到的數(shù)據(jù)中涧尿,線程內(nèi)各個(gè)方法執(zhí)行所耗時(shí)間在整個(gè)線程執(zhí)行時(shí)間中所占比例具有一定參考價(jià)值系奉。占比高的方法當(dāng)是優(yōu)化的重點(diǎn)目標(biāo)。
  • 優(yōu)化前后兩次檢測(cè)得到的數(shù)據(jù)姑廉,有比較價(jià)值缺亮,以確認(rèn)優(yōu)化方案是否真的生效。

通過(guò)這些工具跟蹤函數(shù)桥言,也只能做一個(gè)相對(duì)的參考萌踱,并不能完全正確的反應(yīng)函數(shù)的執(zhí)行性能。比如我通過(guò) CPU Profiler 獲得的 onRebuild() 方法的分析數(shù)據(jù)顯示号阿,整個(gè)執(zhí)行過(guò)程中 Contact 的構(gòu)造方法占了60%左右并鸵,Contact.toString() 方法占了40%左右,但實(shí)際上在 onRebuild() 方法消耗的15秒中扔涧,Contact.toString() 只消耗了百毫秒級(jí)园担,而九成以上時(shí)間都被其構(gòu)造方法消耗了,說(shuō)明 CPU Profiler 的監(jiān)控過(guò)程對(duì) Contact.toString() 的性能產(chǎn)生了更大的影響枯夜。

而同樣的問(wèn)題卻并沒(méi)有出現(xiàn)在 traceview 的分析結(jié)果中弯汰。

請(qǐng)注意,CPU Profiler 和 traceview 不能同時(shí)使用湖雹,如果代碼中植入了插樁的代碼咏闪,則有可能導(dǎo)致 CPU Profiler 無(wú)法正常開(kāi)始或停止錄制。

traceview 和 CPU Profiler 的對(duì)比

從用法上來(lái)看摔吏,traceview 比 CPU Profiler 稍微復(fù)雜一點(diǎn)鸽嫂。類似于MAT需要首先獲取 .hprof 堆轉(zhuǎn)儲(chǔ)文件,traceview 也要首先獲取 .trace 文件征讲,然后使用traceview分析該文件据某。而 CPU Profiler 則可以直接對(duì)應(yīng)用進(jìn)行分析。

從最終生成的圖表上來(lái)看稳诚,CPU Profiler 生成的圖表有 Call Chart哗脖、Flame Chart ,它們可以非常形象的表示出線程內(nèi)執(zhí)行了哪些函數(shù)扳还,函數(shù)的執(zhí)行時(shí)間才避,調(diào)用棧等等,一目了然氨距,而且在任意函數(shù)上點(diǎn)擊右鍵桑逝,可以直接跳轉(zhuǎn)至對(duì)應(yīng)的代碼,非常方便俏让,在這一點(diǎn)上楞遏,相對(duì)于 traceview 要優(yōu)秀。

參考文獻(xiàn)

Inspect threads and methods traces
Inspect trace logs with Traceview
Generate trace logs by instrumenting your app

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末首昔,一起剝皮案震驚了整個(gè)濱河市寡喝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勒奇,老刑警劉巖预鬓,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赊颠,居然都是意外死亡格二,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門竣蹦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)顶猜,“玉大人,你說(shuō)我怎么就攤上這事痘括〕ふ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵纲菌,是天一觀的道長(zhǎng)抄淑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)驰后,這世上最難降的妖魔是什么肆资? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮灶芝,結(jié)果婚禮上郑原,老公的妹妹穿的比我還像新娘。我一直安慰自己夜涕,他們只是感情好犯犁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著女器,像睡著了一般酸役。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天涣澡,我揣著相機(jī)與錄音贱呐,去河邊找鬼。 笑死入桂,一個(gè)胖子當(dāng)著我的面吹牛奄薇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抗愁,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼馁蒂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蜘腌?” 一聲冷哼從身側(cè)響起沫屡,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撮珠,沒(méi)想到半個(gè)月后沮脖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劫瞳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年倘潜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片志于。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涮因,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伺绽,到底是詐尸還是另有隱情养泡,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布奈应,位于F島的核電站澜掩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杖挣。R本人自食惡果不足惜肩榕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惩妇。 院中可真熱鬧株汉,春花似錦贮尖、人聲如沸橡疼。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氓皱。三九已至路召,卻和暖如春勃刨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背股淡。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工身隐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揣非。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓抡医,卻偏偏與公主長(zhǎng)得像躲因,于是被迫代替她去往敵國(guó)和親早敬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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