Stetho,一個功能強(qiáng)大的 Android 應(yīng)用調(diào)試橋

What is Stetho ?

Stetho 是一個功能強(qiáng)大的 Android 應(yīng)用調(diào)試橋,起到橋梁的作用,連接 Android 應(yīng)用和 Chrome尸折,通過 Chrome 開發(fā)者工具調(diào)試 Android 應(yīng)用,提供視圖元素檢查,網(wǎng)絡(luò)監(jiān)控旷太,數(shù)據(jù)庫動態(tài)交互,Dumpapp(可擴(kuò)展的命令行交互接口)销睁,JavaScript Console 等功能供璧。

當(dāng)啟用后,開發(fā)者可以通過 Chrome 桌面瀏覽器中的開發(fā)者工具訪問本地應(yīng)用冻记。開發(fā)者也可以選擇啟用可選的 dumpapp 工具提供一個強(qiáng)大的應(yīng)用內(nèi)部命令行接口睡毒。

官網(wǎng):http://facebook.github.io/stetho/

項(xiàng)目地址:https://github.com/facebook/stetho

一旦你完成了下面的設(shè)置說明,只要啟動你電腦上的 Chrome 瀏覽器并輸入chrome://inspect點(diǎn)擊 "Inspect" 按鈕即可開始調(diào)試冗栗。

注意:如果你點(diǎn)擊 "Inspect" 出現(xiàn)的是一個空白頁面演顾,請嘗試下面的解決方案:

可能是 Chrome 版本過低,嘗試升級 Chrome隅居。

先翻個墻再打開調(diào)試界面(第一次需要這樣钠至,后面不需要)。

配置說明

添加 stetho 主依賴

在 Gradle 中包含 stetho

// Gradle dependency on Stetho

dependencies {

compile 'com.facebook.stetho:stetho:1.4.1'

}

在 Maven 中包含 stetho

com.facebook.stetho

stetho

1.4.1

只有 stetho 主依賴是必須的胎源,但你可能還希望有一個網(wǎng)絡(luò)助手

dependencies {

compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'

}

或者:

dependencies {

compile 'com.facebook.stetho:stetho-okhttp:1.4.1'

}

或者:

dependencies {

compile 'com.facebook.stetho:stetho-urlconnection:1.4.1'

}

功能說明

Chrome DevTools

image

Stetho 為你的應(yīng)用提供了 C/S 協(xié)議實(shí)現(xiàn)棉钧,所以你可以通過 Chrome 集成的前端開發(fā)工具訪問你的應(yīng)用。只要你的應(yīng)用集成了 Stetho涕蚤,只需導(dǎo)航到chrome://inspect并點(diǎn)擊 "Inspect" 即可開始使用宪卿。

Network Inspection

image

使用 Chrome 開發(fā)者工具各種功能實(shí)現(xiàn)網(wǎng)絡(luò)監(jiān)控的诵,包括圖片預(yù)覽,JSON 響應(yīng)輔助工具愧捕,甚至把跟蹤信息導(dǎo)出為 HAR 格式文件奢驯。

Database Inspection

image

SQLite 數(shù)據(jù)庫可視化與交互,具備完全讀寫功能次绘,

Web SQL 下的是應(yīng)用的數(shù)據(jù)庫瘪阁,點(diǎn)擊數(shù)據(jù)庫可以輸入 SQL 語句對其進(jìn)行操作

Local Storage 就是對應(yīng) Android 下的 SharedPreferences,可修改 SharedPreferences 中的值

View Hierarchy

image

View Hierarchy 支持 API 15 或更高版本邮偎。使用 View Hierarchy 可以很方便的檢查運(yùn)行時界面的層次結(jié)構(gòu)與數(shù)據(jù)管跺,比如:

View Hierarchy 中包含界面所有元素的層次結(jié)構(gòu)和屬性

鼠標(biāo)移動到 View Hierarchy 中某個 View,app 中對應(yīng)的 View 會高亮顯示

點(diǎn)擊 View Hierarchy 左上角的搜索按鈕禾进,再點(diǎn)擊 app 當(dāng)前界面的控件豁跑,View Hierarchy 會顯示該控件在層次中的位置

dumpapp

image

Dumpapp 為應(yīng)用提供了一個可擴(kuò)展的命令行交互接口,提供了一組默認(rèn)的插件泻云,但是 dumpapp 的真正強(qiáng)大之處在于能夠輕松創(chuàng)建自己的插件艇拍!

dumpapp 就在工程的 scripts/dumpapp 下,遺憾的是目前在 Windows 下還用不了宠纯,因?yàn)樗惶峁┝?Linux/Mac 下的執(zhí)行腳本卸夕。

常用命令(插件):

列出所有 Plugin :./scripts/dumpapp -p com.facebook.stetho.sample -l

打印 SharedPreferences :./scripts/dumpapp prefs print

寫 SharedPreferences :./scripts/dumpapp prefs write

dumpapp 默認(rèn)提供的插件就在com.facebook.stetho.dumpapp.plugins.*,具體使用方法可以參考源碼中的說明婆瓜。

JavaScript Console

image

JavaScript Console 允許執(zhí)行那些可以與應(yīng)用或 Android SDK 交互的 JavaScript 代碼快集。

Stetho 使用Rhino實(shí)現(xiàn)使用腳本方式調(diào)用 Java。

Rhino 是一個完全使用Java語言編寫的開源JavaScript實(shí)現(xiàn)廉白。Rhino通常用于在Java程序中个初,為最終用戶提供腳本化能力。它被作為J2SE 6上的默認(rèn)Java腳本化引擎猴蹂。

Github:https://github.com/mozilla/rhino

官網(wǎng):https://www.mozilla.org/rhino/

集成說明

1. 初始化

在你的 Application 初始化時候調(diào)用 Stetho 的初始化方法:

public class MyApplication extends Application {

public void onCreate() {

super.onCreate();

Stetho.initializeWithDefaults(this);

}

}

這將啟用大多數(shù)默認(rèn)配置院溺,但不啟用一些額外的鉤子(啟用網(wǎng)絡(luò)監(jiān)控需要注意)。 有關(guān)各個子系統(tǒng)的具體細(xì)節(jié)磅轻,請參見下文珍逸。

2. 啟用網(wǎng)絡(luò)監(jiān)控

如果你使用的是 2.2.x+ 或 3.x 版本的 OkHttp 庫,可以使用攔截器系統(tǒng)自動掛接到現(xiàn)有堆棧瓢省。 這是目前啟用網(wǎng)絡(luò)監(jiān)控的最簡單和最直接的方法弄息。

For OkHttp 2.x

OkHttpClient client = new OkHttpClient();

client.networkInterceptors().add(new StethoInterceptor());

For OkHttp 3.x

new OkHttpClient.Builder()

.addNetworkInterceptor(new StethoInterceptor())

.build();

由于攔截器可以修改請求和響應(yīng),應(yīng)該在其他攔截器之后添加 Stetho 攔截器以獲取準(zhǔn)確的網(wǎng)絡(luò)交互視圖勤婚。

如果你使用HttpURLConnection摹量,可以使用StethoURLConnectionManager來幫助集成,但該方法有一些注意事項(xiàng),比如你必須明確地添加Accept-Encoding:gzip到請求頭缨称,并手動處理壓縮的響應(yīng)凝果,以便 Stetho 報告壓縮的有效負(fù)載大小。具體可以參考stetho-sample中的Networker的實(shí)現(xiàn)睦尽。

Stetho 目前沒有提供 HttpClient 網(wǎng)絡(luò)監(jiān)控支持器净,具體原因可以查看issues 116(HttpClient 在 Android5.0 已經(jīng)被廢棄,不建議再使用)当凡。

OkHttp + Retrofit

一般開發(fā)中我們都會使用OkHttp + Retrofit山害,OkHttp用于 HTTP 網(wǎng)絡(luò)交互,Retrofit用于將 HTTP API 轉(zhuǎn)換為 Java 接口沿量。

默認(rèn)情況下浪慌,Retrofit會自己創(chuàng)建一個OkHttpClient,我們也可以在創(chuàng)建Retrofit的時候通過client(OkHttpClient client)方法提供一個OkHttpClient朴则。

sRetrofit = new Retrofit.Builder()

.baseUrl(baseUrl)

.client(sClient)

.build();

通過client(OkHttpClient client)方法設(shè)置共用一個OkHttpClient权纤,監(jiān)控Retrofit中的網(wǎng)絡(luò)交互。

更多細(xì)節(jié)見stetho-sample項(xiàng)目乌妒。

3. 自定義 dumpapp 插件

自定義插件主要是實(shí)現(xiàn) DumperPlugin 接口中的String getName()和void dump(DumperContext dumpContext)方法汹想。getName()方法返回插件的名稱,dump(DumperContext dumpContext)是命令行中調(diào)用該插件時的回調(diào)方法撤蚊。其中古掏,dumpContext.getStdout()獲取命令行輸出,dumpContext.getArgsAsList()獲取命令行調(diào)用的參數(shù)列表拴魄。

public class MyDumperPlugin implements DumperPlugin {

private static final String XML_SUFFIX = ".xml";

private static final String NAME = "prefs";

private final Context mAppContext;

public MyDumperPlugin(Context context) {

mAppContext = context.getApplicationContext();

}

@Override

public String getName() {

return NAME;

}

@Override

public void dump(DumperContext dumpContext) throws DumpUsageException {

PrintStream writer = dumpContext.getStdout();

List args = dumpContext.getArgsAsList();

String commandName = args.isEmpty() ? "" : args.remove(0);

if (commandName.equals("print")) {

doPrint(writer, args);

} else if (commandName.equals("write")) {

doWrite(args);

} else {

doUsage(writer);

}

}

// 省略部分代碼

}

然后把初始化調(diào)用替換如下:

Stetho.initialize(Stetho.newInitializerBuilder(context)

.enableDumpapp(new DumperPluginsProvider() {

@Override

public Iterable get() {

return new Stetho.DefaultDumperPluginsBuilder(context)

.provide(new MyDumperPlugin())

.finish();

}

})

.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))

.build());

更多細(xì)節(jié)見stetho-sample項(xiàng)目冗茸。

4. 啟用JavaScript Console

啟用 JavaScript Console 只需在 build.gradle 中添加如下依賴即可:

compile "com.facebook.stetho:stetho-js-rhino:1.4.1"

啟動 app席镀,在 Chrome 開發(fā)者工具的 Console 輸入下面代碼使 app 打印一個Toast:

importPackage(android.widget);

importPackage(android.os);

var handler = new Handler(Looper.getMainLooper());

handler.post(function() { Toast.makeText(context, "hello", Toast.LENGTH_LONG).show() });

Paste_Image.png

importPackage(android.widget)等于 java 中import android.widget.*;匹中,JavaScript 中使用 var 定義變量,這段代碼就是創(chuàng)建了一個 handler 并調(diào)用 post 方法在 ui 線程彈一個 Toast豪诲。

關(guān)于 Rhino 相關(guān)語法可以參考下面的文檔

Scripting Java

Performance Hints

在Toast.makeText中的 context 是從哪里來的呢?

context 是在com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder的initJsScope方法中被綁定到JSContext的顶捷,下面是 initJsScope 方法的源碼:

private @NonNull ScriptableObject initJsScope(@NonNull Context jsContext) {

// Set the main Rhino goodies

ImporterTopLevel importerTopLevel = new ImporterTopLevel(jsContext);

ScriptableObject scope = jsContext.initStandardObjects(importerTopLevel, false);

ScriptableObject.putProperty(scope, "context", Context.javaToJS(mContext, scope));

try {

importClasses(jsContext, scope);

importPackages(jsContext, scope);

importConsole(scope);

importVariables(scope);

importFunctions(scope);

} catch (StethoJsException e) {

String message = String.format("%s\n%s", e.getMessage(), Log.getStackTraceString(e));

LogUtil.e(e, message);

CLog.writeToConsole(Console.MessageLevel.ERROR, Console.MessageSource.JAVASCRIPT, message);

}

return scope;

}

JsRuntimeReplFactoryBuilder提供了一些方法可以傳遞自己的變量,類屎篱,包和函數(shù)到 JavaScript 環(huán)境服赎。

添加變量,類交播,包和函數(shù)到 JavaScript 運(yùn)行時

修改初始化代碼如下:

Stetho.initialize(Stetho.newInitializerBuilder(context)

.enableWebKitInspector(new ExtInspectorModulesProvider(context))

.build());

private static class ExtInspectorModulesProvider implements InspectorModulesProvider {

private Context mContext;

private final Handler handler = new Handler(Looper.getMainLooper());

ExtInspectorModulesProvider(Context context) {

mContext = context;

}

@Override

public Iterable get() {

return new Stetho.DefaultInspectorModulesBuilder(mContext)

.runtimeRepl(new JsRuntimeReplFactoryBuilder(mContext)

// 添加變量

.addVariable("test", new AtomicBoolean(true))

// 添加類

.importClass(R.class)

// 添加包

.importPackage(MyApplication.class.getPackage().getName())

// 添加方法到 javascript: void toast(String)

.addFunction("toast", new BaseFunction() {

@Override

public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {

// javascript 傳遞的參數(shù)在 varags

final String message = args[0].toString();

handler.post(new Runnable() {

@Override

public void run() {

Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();

}

});

// 在 javascript 返回 undefined

return org.mozilla.javascript.Context.getUndefinedValue();

}

})

.build())

.finish();

}

}

說明:Java原語類型將被自動裝箱重虑,只有對象可以傳遞到 JavaScript 運(yùn)行時。

綁定完成后就可以在 JavaScript Console 中使用自己的變量秦士,類缺厉,包和函數(shù)了。

Paste_Image.png

注意:Rhino 對包名的檢查是嚴(yán)格的,必須是com.**提针,org.**命爬,net.**之類比較正規(guī)的格式。假如:包名使用linchaolong.stetho.demo辐脖,在importClasses(linchaolong.stetho.demo.R)時饲宛,ScriptRuntime會報EcmaError

Failed to import class: linchaolong.stetho.demo.R

com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder$StethoJsException: Failed to import class: linchaolong.stetho.demo.R

at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.importClasses(JsRuntimeReplFactoryBuilder.java:195)

at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.initJsScope(JsRuntimeReplFactoryBuilder.java:173)

at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.initJsScope(JsRuntimeReplFactoryBuilder.java:158)

at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.access$000(JsRuntimeReplFactoryBuilder.java:45)

at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder$1.newInstance(JsRuntimeReplFactoryBuilder.java:146)

at com.facebook.stetho.inspector.protocol.module.Runtime$Session.getRepl(Runtime.java:271)

at com.facebook.stetho.inspector.protocol.module.Runtime$Session.evaluate(Runtime.java:260)

at com.facebook.stetho.inspector.protocol.module.Runtime.evaluate(Runtime.java:158)

at java.lang.reflect.Method.invoke(Native Method)

at java.lang.reflect.Method.invoke(Method.java:372)

at com.facebook.stetho.inspector.MethodDispatcher$MethodDispatchHelper.invoke(MethodDispatcher.java:96)

at com.facebook.stetho.inspector.MethodDispatcher.dispatch(MethodDispatcher.java:67)

at com.facebook.stetho.inspector.ChromeDevtoolsServer.handleRemoteRequest(ChromeDevtoolsServer.java:129)

at com.facebook.stetho.inspector.ChromeDevtoolsServer.handleRemoteMessage(ChromeDevtoolsServer.java:111)

at com.facebook.stetho.inspector.ChromeDevtoolsServer.onMessage(ChromeDevtoolsServer.java:87)

at com.facebook.stetho.websocket.WebSocketSession$1.handleTextFrame(WebSocketSession.java:176)

at com.facebook.stetho.websocket.WebSocketSession$1.onCompleteFrame(WebSocketSession.java:136)

at com.facebook.stetho.websocket.ReadHandler.readLoop(ReadHandler.java:44)

at com.facebook.stetho.websocket.WebSocketSession.handle(WebSocketSession.java:45)

at com.facebook.stetho.websocket.WebSocketHandler.doUpgrade(WebSocketHandler.java:117)

at com.facebook.stetho.websocket.WebSocketHandler.handleRequest(WebSocketHandler.java:83)

at com.facebook.stetho.server.http.LightHttpServer.dispatchToHandler(LightHttpServer.java:84)

at com.facebook.stetho.server.http.LightHttpServer.serve(LightHttpServer.java:61)

at com.facebook.stetho.inspector.DevtoolsSocketHandler.onAccepted(DevtoolsSocketHandler.java:52)

at com.facebook.stetho.server.ProtocolDetectingSocketHandler.onSecured(ProtocolDetectingSocketHandler.java:63)

at com.facebook.stetho.server.SecureSocketHandler.onAccepted(SecureSocketHandler.java:33)

at com.facebook.stetho.server.LazySocketHandler.onAccepted(LazySocketHandler.java:36)

at com.facebook.stetho.server.LocalSocketServer$WorkerThread.run(LocalSocketServer.java:167)

Caused by: org.mozilla.javascript.EcmaError: ReferenceError: "linchaolong" is not defined. (chrome#1)

at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3949)

at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3927)

at org.mozilla.javascript.ScriptRuntime.notFoundError(ScriptRuntime.java:4012)

at org.mozilla.javascript.ScriptRuntime.name(ScriptRuntime.java:1849)

at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:1558)

at org.mozilla.javascript.Interpreter.interpret(Interpreter.java:815)

at org.mozilla.javascript.InterpretedFunction.call(InterpretedFunction.java:109)

at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:393)

at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3280)

at org.mozilla.javascript.InterpretedFunction.exec(InterpretedFunction.java:120)

at org.mozilla.javascript.Context.evaluateString(Context.java:1191)

at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.importClasses(JsRuntimeReplFactoryBuilder.java:193)

... 27 more

更多細(xì)節(jié)請參考stetho-js-rhino項(xiàng)目中的README.md

只在debug模式下使用 Stetho

修改 dependencies 配置,只在 debug 模式下編譯stetho和stetho-js-rhino

dependencies {

// Debug

debugCompile "com.facebook.stetho:stetho:${stetho}"

compile "com.facebook.stetho:stetho-okhttp3:${stetho}"

debugCompile "com.facebook.stetho:stetho-js-rhino:${stetho}"

}

說明:${stetho}是 stetho 的版本號嗜价。

在src/debug/java目錄下新建一個DebugApplication繼承自MyApplication艇抠,并把初始化 Stetho 的代碼移到DebugApplication

public class DebugApplication extends MyApplication{

@Override public void onCreate() {

super.onCreate();

Stetho.initializeWithDefaults(this);

}

在src/debug目錄下創(chuàng)建一個AndroidManifest.xml,并添加 debug 模式下需要的權(quán)限和修改 application 節(jié)點(diǎn)android:name值為 DebugApplication(使用tools:replace覆蓋android:name字段)


xmlns:tools="http://schemas.android.com/tools"

package="com.facebook.stetho.sample">

tools:replace="android:name"

android:name=".DebugApplication" />

Paste_Image.png

完成上面處理后久锥,Stetho 只在 debug 版本下起作用练链,不影響 release 版本。

作者:linchaolong

鏈接:http://www.reibang.com/p/38d8324b126a

來源:簡書

著作權(quán)歸作者所有奴拦。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)媒鼓,非商業(yè)轉(zhuǎn)載請注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末错妖,一起剝皮案震驚了整個濱河市绿鸣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暂氯,老刑警劉巖潮模,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異痴施,居然都是意外死亡擎厢,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門辣吃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來动遭,“玉大人,你說我怎么就攤上這事神得±宓耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵哩簿,是天一觀的道長宵蕉。 經(jīng)常有香客問我,道長节榜,這世上最難降的妖魔是什么羡玛? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮宗苍,結(jié)果婚禮上稼稿,老公的妹妹穿的比我還像新娘亿遂。我一直安慰自己,他們只是感情好渺杉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布蛇数。 她就那樣靜靜地躺著,像睡著了一般是越。 火紅的嫁衣襯著肌膚如雪耳舅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天倚评,我揣著相機(jī)與錄音浦徊,去河邊找鬼。 笑死天梧,一個胖子當(dāng)著我的面吹牛盔性,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呢岗,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼冕香,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了后豫?” 一聲冷哼從身側(cè)響起悉尾,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挫酿,沒想到半個月后构眯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡早龟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年惫霸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葱弟。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡壹店,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出翘悉,到底是詐尸還是另有隱情茫打,我是刑警寧澤居触,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布妖混,位于F島的核電站,受9級特大地震影響轮洋,放射性物質(zhì)發(fā)生泄漏制市。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一弊予、第九天 我趴在偏房一處隱蔽的房頂上張望祥楣。 院中可真熱鬧,春花似錦、人聲如沸误褪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兽间。三九已至历葛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘀略,已是汗流浹背恤溶。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帜羊,地道東北人咒程。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像讼育,于是被迫代替她去往敵國和親帐姻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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