如何像 Chrome 調(diào)試網(wǎng)頁那樣,調(diào)試你的安卓 APP

開發(fā)者在開發(fā)中想查看安卓APP運行時的網(wǎng)絡(luò)訪問和數(shù)據(jù)存儲情況扬虚,調(diào)試太麻煩努隙,日志也挺煩,有沒有更好的辦法呢辜昵?Facebook給廣大開發(fā)者傳了福音荸镊,帶了福利,放在下午茶的小桌子上堪置,美食干貨不敢獨吞躬存,所以拿來分享給大家

從事移動端安卓APP的開發(fā),除了代碼邏輯之外就是在和數(shù)據(jù)打交道舀锨。數(shù)據(jù)的輸入輸出岭洲,往返于網(wǎng)絡(luò)接口之間,流竄于內(nèi)存之中存儲之內(nèi)坎匿,不能像編寫的代碼那樣直接在代碼編輯器中看到其具體的內(nèi)容盾剩。所以如果想窺探數(shù)據(jù)的真?zhèn)螌﹀e雷激,目前來說,不外三法告私。本文開始侥锦,告訴你第四條路。

現(xiàn)狀德挣,以及各自的問題

前面說傳統(tǒng)上有兩條路可以幫助開發(fā)者查看APP運行過程中處理的數(shù)據(jù)恭垦,這里簡單描述下處理方式以及每種方式的優(yōu)缺點。

  • 斷點調(diào)試運行中的APP格嗅。你可以用調(diào)試器直接調(diào)試一個APP番挺,但如果這個APP過于龐大,初始化加載時間很久屯掖,那么玄柏,最好的調(diào)試辦法是先將APP在設(shè)備(手機或者模擬器)上運行起來,然后用attach to process的方式在被調(diào)試的APP進程上加載調(diào)試器贴铜,這樣會比一上來直接調(diào)試APP更快一些粪摘。想看數(shù)據(jù)的話,直接在相應(yīng)代碼行加上斷點绍坝,附著在APP進程上的調(diào)試器會自動斷下程序徘意,然后查看當前上下文中各種變量的值以及內(nèi)存的數(shù)據(jù),也可以修改這些數(shù)據(jù)轩褐。但是椎咧,如果你想看某個數(shù)據(jù)是不是真的寫到存儲的文件里,估計需要添加額外的讀取代碼來查看把介,而且勤讽,每次給APP掛調(diào)試器查看數(shù)據(jù),感覺還是有些不方便拗踢。如果你想診斷和分析網(wǎng)絡(luò)的訪問速度和數(shù)據(jù)的流量脚牍,數(shù)據(jù)存儲的空間和總體數(shù)據(jù)量,單靠調(diào)試這種手段顯得力不從心了巢墅。而且如果斷點的地方在UI的某處代碼诸狭,長時間處于斷點狀態(tài)查看數(shù)據(jù),會導(dǎo)致APP發(fā)生ANR的異常砂缩。
  • 加打印日志作谚。類似產(chǎn)品運營的埋點和服務(wù)端訪問/操作日志,我們也可以在客戶端APP相應(yīng)的位置大書類似到此一游此地無淫三百靚的句式庵芭,讓APP進程通過一個叫控制臺的老東西(console是計算機世界的老司機了妹懒,啥大風大浪沒見過的)告訴我們發(fā)生了啥,如何發(fā)生的双吆,以及發(fā)生的結(jié)果如何眨唬。斷點不好做到的網(wǎng)絡(luò)訪問速度和數(shù)據(jù)流量等東西也可以通過日志叫喚了会前。這么看起來,貌似加日志已經(jīng)是一種很完美的辦法了匾竿。但是瓦宜,你有沒有感覺到這樣超級麻煩?首先是你的代碼量突然變大了岭妖,代碼結(jié)構(gòu)變丑了临庇,代碼環(huán)境衛(wèi)生變差了,翠花上的酸菜我不敢吃了昵慌。相信我假夺,日志海(骷髏海的代碼態(tài))一定會讓你疲憊的雙眼猶如狂風暴雨里的一葉孤舟,說翻就翻斋攀,眼都不帶眨一下的已卷。說人話,日志是一種侵入式的調(diào)試手段淳蔼,啥叫侵入式侧蘸?就是它必須由您老人家親自動手埋藏在代碼的心房里,直到天荒地老,APP下架鹉梨,它也不會化作半點春泥更護花的讳癌。而對于調(diào)試來說,看日志的情調(diào)less than lower俯画,千篇飛過如同嚼蠟析桥。看過安卓日志的童鞋都知道艰垂,前塵往事并木有渺云煙,那些個天天在微信群里大呼小叫的群主來看看到底啥叫刷屏埋虹〔略鳎可憐的安卓開發(fā)們,天天被日志刷屏搔课。
  • 借助第三方工具胰柑。對于網(wǎng)絡(luò)來說,基本就是設(shè)置代理爬泥,最常用的不外乎Charles (收費,基于Java開發(fā)柬讨,跨平臺);Fiddler(免費&收費,基于.Net開發(fā)袍啡,目前支持通過mono的方式運行在Mac和Linux上)踩官;Mitmproxy(免費&開源,基于Python開發(fā)境输,跨平臺)蔗牡;還有比較麻煩的辦法颖系,比如Http/Https代理+Wireshark/tcpdump這種。這些工具只能滿足網(wǎng)絡(luò)監(jiān)控辩越,對于非網(wǎng)絡(luò)數(shù)據(jù)就無能為力了嘁扼。對于存儲在手機上的數(shù)據(jù),可以通過adb登陸到手機黔攒,獲得root權(quán)限后查看APP內(nèi)部數(shù)據(jù)趁啸,也可以采用一些安裝在手機端的帶圖形界面的APP來查看和修改數(shù)據(jù),比如SQLEditor之類的督惰,這類APP同樣需要獲取root權(quán)限莲绰。

那么,后來姑丑,Facebook給我們這些可憐的娃帶來了福音和福利蛤签,試試看咯

聽診器來了

Stetho英譯為“聽診”,是Facebook研發(fā)的安卓APP網(wǎng)絡(luò)診斷和數(shù)據(jù)監(jiān)控的框架栅哀,目前已經(jīng)開放源代碼震肮,開發(fā)者接入Stetho框架提供的SDK到APP中,這樣就可以通過安裝在開發(fā)機(PC/MAC留拾,Windows/OS X/Linux)上安裝的谷歌的Chrome開發(fā)者工具(通過Chrome瀏覽器使用)來查看戳晌,診斷和分析APP中發(fā)生的網(wǎng)絡(luò)請求和響應(yīng)以及數(shù)據(jù)內(nèi)容,就像用Chrome調(diào)試網(wǎng)站一樣調(diào)試APP程序痴柔。當然沦偎,幾乎任何工具都自帶老司機console,Stetho也不例外咳蔚,它提供了一個叫做dumpapp的工具豪嚎,可以向你傾述更多的APP內(nèi)心世界。

接入其實很簡單

再簡單的接入也總有1234步谈火,這里簡單叨逼叨逼幾句

gradle配置

這里不說mvn和low逼的下載&拷貝庫的方式了(拷貝源代碼的方式集成就更不能忍了)侈询,直接上gradle配置

// Gradle dependency on Stetho 
  dependencies { 
    compile 'com.facebook.stetho:stetho:1.3.1' 
  } 

如果你使用了Okhttp 3.x的網(wǎng)絡(luò)棧,請集成如下網(wǎng)絡(luò)工具庫

dependencies { 
    compile 'com.facebook.stetho:stetho-okhttp3:1.3.1' 
  } 

Okhttp 2.2.x+

dependencies { 
    compile 'com.facebook.stetho:stetho-okhttp:1.3.1' 
  } 

如果使用的是HttpURLConnection

dependencies { 
    compile 'com.facebook.stetho:stetho-urlconnection:1.3.1' 
  } 

小白兔和大灰狼請注意:

  • 如果你使用的是Apache HttpClient糯耍,對不起扔字,你out了,請自行升級網(wǎng)絡(luò)棧温技,當然你也可以在了解了Stetho的玩法之后自己寫一套網(wǎng)絡(luò)監(jiān)控來適配Apache HttpClient革为。
  • 如果你使用的網(wǎng)絡(luò)棧不在上面列舉的里面,或者你用c/c++寫的網(wǎng)絡(luò)操作舵鳞,又或者你采用的協(xié)議不是http/https的震檩,那么,網(wǎng)絡(luò)這部分的診斷和監(jiān)控方法系任,估計是很難用了恳蹲。還想用虐块,自己寫咯。

初始化

需要寫的代碼
其實很少嘉蕾,而且?guī)缀跛械膽?yīng)用都是一樣的代碼贺奠,首先是在Application類中初始化

public class MyApplication extends Application {
  public void onCreate() {
    super.onCreate();
    Stetho.initializeWithDefaults(this);
  }
}

這個初始化會開啟大部分的聽診模塊,但是網(wǎng)絡(luò)監(jiān)控等一些附加的鉤子模塊除外

網(wǎng)絡(luò)診斷

如果你使用的網(wǎng)絡(luò)棧是OkHttp错忱,而且版本區(qū)間在2.2.x+到3.x儡率,那么想要打開網(wǎng)絡(luò)診斷模塊,只需要在程序合適的位置調(diào)用如下代碼即可

OkHttp 2.2.x+

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

OkHttp 3.x

new OkHttpClient.Builder()
    .addNetworkInterceptor(new StethoInterceptor())
    .build();

HttpURLConnection

如果你使用的HttpURLConnection,稍微有些麻煩的說以清,你可以使用Stetho框架SDK提供的類StethoURLConnectionManager來完成客戶端網(wǎng)絡(luò)診斷的開啟儿普,但是有一些坑是要注意的。
比如為了讓Stetho向開發(fā)機上的Chrome匯報正確的經(jīng)過壓縮的有效載荷的大小掷倔,你需要親自在http/https的請求頭加上"Accept-Encoding: gzip"眉孩,并且自己處理壓縮過的響應(yīng)數(shù)據(jù)。如果采用Okhttp勒葱,這些都不必勞煩您老人家操心了浪汪,框架默認幫你考慮了。
參考代碼如下:

private final StethoURLConnectionManager stethoManager;

private static final int READ_TIMEOUT_MS = 10000;
private static final int CONNECT_TIMEOUT_MS = 15000;

private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
private static final String GZIP_ENCODING = "gzip";

private String url = "http://www.figotan.org";
stethoManager = new StethoURLConnectionManager(url);

URL url = new URL(url);

// Note that this does not actually create a new connection so it is appropriate to
// defer preConnect until after the HttpURLConnection instance is configured.  Do not
// invoke connect, conn.getInputStream, conn.getOutputStream, etc before calling
// preConnect!
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
try {
    conn.setReadTimeout(READ_TIMEOUT_MS);
    conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
    conn.setRequestMethod(request.method.toString());

    // Adding this disables transparent gzip compression so that we can intercept
    // the raw stream and display the correct response body size.
    conn.setRequestProperty(HEADER_ACCEPT_ENCODING, GZIP_ENCODING);

    SimpleRequestEntity requestEntity = null;
    if (request.body != null) {
        requestEntity = new ByteArrayRequestEntity(request.body);
    }

    stethoManager.preConnect(conn, requestEntity);
    try {
          if (request.method == HttpMethod.POST) {
            if (requestEntity == null) {
              throw new IllegalStateException("POST requires an entity");
            }
            conn.setDoOutput(true);
            requestEntity.writeTo(conn.getOutputStream());
          }

          // Ensure that we are connected after this point.  Note that getOutputStream above will
          // also connect and exchange HTTP messages.
          conn.connect();

          stethoManager.postConnect();

    } catch (IOException inner) {
          // This must only be called after preConnect.  Failures before that cannot be
          // represented since the request has not yet begun according to Stetho.
          stethoManager.httpExchangeFailed(inner);
          throw inner;
    }
} catch (IOException outer) {
        conn.disconnect();
        throw outer;
}
      
try {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    InputStream rawStream = conn.getInputStream();
    try {
        // Let Stetho see the raw, possibly compressed stream.
        rawStream = stethoManager.interpretResponseStream(rawStream);
        if (rawStream != null && GZIP_ENCODING.equals(conn.getContentEncoding())) {
            decompressedStream  = new GZIPInputStream(in);
        } else {
            decompressedStream = rawStream;
        } 
    
        if (decompressedStream != null) {
            int n;
            byte[] buf = new byte[1024];
            while ((n = decompressedStream.read(buf)) != -1) {
                out.write(buf, 0, n);
            }
        }
    } finally {
        if (rawStream != null) {
            rawStream.close();
          }
    }
} finally {
    conn.disconnect();
}

通過如上步驟凛虽,已經(jīng)可以讓你的APP支持網(wǎng)絡(luò)監(jiān)控死遭,數(shù)據(jù)庫監(jiān)控和SharedPreferences文件內(nèi)容監(jiān)控了。如果想玩更高級的凯旋,請看后面的自定義dumpapp插件和Rhino呀潭,如果想直接玩起來,請繼續(xù)往下看至非。

使用起來也不麻煩

使用步驟如下:

  1. 首先在手機上運行APP

  2. 確保手機USB連接開發(fā)機钠署,在開發(fā)機上打開Chrome瀏覽器

  3. 在Chrome瀏覽器地址欄中輸入chrome://inspect,會看到如下這張圖,如果圖里面沒有你的APP睡蟋,請返回到上面檢查代碼接入是否正確

  4. 點擊APP旁邊的inspect連接踏幻,這個時候會彈出一個窗口,如果你用過Chrome的開發(fā)者工具戳杀,是不是會覺得這個界面很熟悉?對了夭苗,這個窗口就是Chrome內(nèi)置的開發(fā)者工具信卡,只不過里面監(jiān)控的內(nèi)容從網(wǎng)頁變成了APP

  5. 首先看功能導(dǎo)航條的第一個tab,叫做"Elements",工作區(qū)中的內(nèi)容是不是很熟悉题造,Hierarchy Viewer傍菇,很像吧,點擊具體的xml節(jié)點界赔,可以看到連接的手機上對應(yīng)的UI控件高亮顯示了丢习,這個可以像Hierarchy Viewer那樣分析APP頁面的嵌套層級


  6. 第二個tab叫做"Network",是用來做網(wǎng)絡(luò)監(jiān)控的牵触,基本上覆蓋了Chrome開發(fā)者工具中"Network inspection"的所有功能點,包括下載圖片的預(yù)覽咐低,JSON數(shù)據(jù)查看揽思,網(wǎng)絡(luò)請求內(nèi)容和返回內(nèi)容

  7. 第三個tab是"Sources",用來查看網(wǎng)頁的詳細內(nèi)容

  8. 直接跳過"Timeline", "Profiles"看第六個tab见擦,"Resources"钉汗,顧名思義,這里應(yīng)該就是查看APP內(nèi)部產(chǎn)生數(shù)據(jù)的地方啦鲤屡,目前支持的數(shù)據(jù)有兩種损痰,一種是數(shù)據(jù)庫(ContentProvider和Sql的方式)的數(shù)據(jù),另一種是SharedPreferences數(shù)據(jù)






  9. "Audits" 跳過酒来,如同"Timeline"和"Profiles"卢未,目前沒怎么支持,有待進一步發(fā)掘的功能堰汉。

  10. "Console"老司機下面講

有坑嗎辽社?

關(guān)于網(wǎng)絡(luò)監(jiān)控的有一些需要注意的字段的含義,詳細的內(nèi)容可以去精讀一遍Chrome開發(fā)者工具官方文檔


這里只詳細解釋下上面圖中個字段的含義衡奥。

  • Name/Path 網(wǎng)絡(luò)資源的名稱和URL路徑爹袁,比如http://www.figotan.org/c/v/logo.jpg這個網(wǎng)絡(luò)資源,Name是logo.jpg Path是www.figotan.org/c/v/
  • Method HTTP協(xié)議規(guī)定的請求方法矮固,比如GET POST
  • Status/Text HTTP協(xié)議規(guī)定的返回碼和這個返回碼對應(yīng)的含義解釋文字 失息,比如200/OK
  • Type 請求資源的MIME類型,比如application/json image/jpeg image/png等等
  • Initiator 發(fā)起請求的對象档址,可以是Parser/Redirect/Script/Other,詳見上面的官方文檔
  • Size/Content Size表示HTTP響應(yīng)的頭和數(shù)據(jù)體的和盹兢,由遠程服務(wù)端返回;Content是返回的資源解碼后的大小
  • Time/Latentcy Time是總的時間間隔守伸,從發(fā)起請求開始到接收到服務(wù)端返回的最后一個字節(jié)為止绎秒;Latency是指的接收到服務(wù)端返回的第一個字節(jié)消耗的時間
  • Timeline 顯示了所有網(wǎng)絡(luò)請求的瀑布流

還可以做什么

除了監(jiān)控網(wǎng)絡(luò),查看/修改數(shù)據(jù)之外尼摹,還可以做很多事情见芹,因為Stetho預(yù)留了兩種接口,為了可持續(xù)的發(fā)展

自定義dumpapp插件

自定義插件是讓老司機dumpapp get新技能的首選方法蠢涝,可以很容易地在配置過程中添加玄呛。只需如下代碼即可添加自定義插件:

Stetho.initialize(Stetho.newInitializerBuilder(context)
        .enableDumpapp(new DumperPluginsProvider() {
          @Override
          public Iterable<DumperPlugin> get() {
            return new Stetho.DefaultDumperPluginsBuilder(context)
                .provide(new HelloWorldDumperPlugin())
                .provide(new APODDumperPlugin(context.getContentResolver()))
                .finish();
          }
        })
        .enableWebKitInspector(new ExtInspectorModulesProvider(context))
        .build());

其中HelloWorldDumperPlugin和APODDumperPlugin是自定義的插件,具體內(nèi)容可以參考Stetho提供的sample程序
執(zhí)行dumpapp命令需要先從git取下最新的代碼和二,然后找到dumpapp腳本徘铝,并執(zhí)行

$ git clone https://github.com/facebook/stetho.git
$ cd stetho
// 列舉出支持的命令(插件)
$ ./scripts/dumpapp -p com.facebook.stetho.sample -l

參照sample代碼編寫dumpapp插件,然后用dumpapp命令驗證插件的效果

Stetho對于JavaScript的支持

目前Stetho對于JavaScript腳本的支持是采用內(nèi)嵌Mozilla研發(fā)的Rhino
第一種采用dumpapp的插件擴展方式雖然功能強大,無所不能惕它,但是完成一件事情需要一定的技術(shù)和時間成本怕午,必須經(jīng)歷一系列的編寫,編譯淹魄,構(gòu)建郁惜,安裝,調(diào)試揭北,修改代碼扳炬,再下一個輪回,迭代幾次后才能形成產(chǎn)出搔体,這其實是類c/c++/java這種非動態(tài)語言的一個缺陷恨樟,軟件的研發(fā)周期太長。那么疚俱,如果有一種寫完即發(fā)布的腳本語言能夠支持起來劝术,其實對于研發(fā)效率來說,是有很大提升的呆奕,比如lua/javascript/perl/python/groovy等等养晋,這樣的語言輕巧,無需編譯梁钾,寫完就可以發(fā)布驗證绳泉,甚至可以邊寫邊調(diào)試邊上線。
Chrome開發(fā)者工具原生支持JavaScript姆泻,所以Stetho也提供了JavaScript的支持零酪。

Rhino是一個可以運行在Java程序內(nèi)部的JavaScript實現(xiàn),由Mozilla開發(fā)并發(fā)布為一個開源的項目

下面說說集成和使用方式

如果要讓APP支持Rhino, 首先是gradle配置

dependencies { 
    compile 'com.facebook.stetho:stetho-js-rhino:1.3.1' 
} 

然后就可以通過開發(fā)機上Chrome瀏覽器提供的開發(fā)者工具的"Console"老司機(任何工具都有老司機console)來發(fā)射JavaScript代碼了拇勃,參考代碼如下:

importPackage(android.widget);
importPackage(android.os);
var handler = new Handler(Looper.getMainLooper());
handler.post(function() { Toast.makeText(context, "Hello from JavaScript", Toast.LENGTH_LONG).show() });

運行效果如下:

如果你想通過APP傳遞一些變量四苇,類,閉包和函數(shù)給JavaScript運行環(huán)境中方咆,那么你可以在Stetho初始化的時候添加如下代碼:

Stetho.initialize(Stetho.newInitializerBuilder(context)
        .enableWebKitInspector(new InspectorModulesProvider() {
          @Override
          public Iterable<ChromeDevtoolsDomain> get() {
            return new DefaultInspectorModulesBuilder(context).runtimeRepl(
                new JsRuntimeReplFactoryBuilder(context)
                    // Pass to JavaScript: var foo = "bar";
                    .addVariable("foo", "bar")
                    .build()
            ).finish();
          }
        })
        .build());

更多玩法請移步Rhino on Stetho

參考資料

Stetho官方文檔
Stetho源代碼
Stetho: A new debugging platform for Android
A First Glance at Stetho tool
Remote Debugging on Android with Chrome
Chrome DevTools Overview

想要知道的更多月腋,那就來我的博客看看吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓣赂,一起剝皮案震驚了整個濱河市榆骚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌煌集,老刑警劉巖寨躁,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異牙勘,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門方面,熙熙樓的掌柜王于貴愁眉苦臉地迎上來放钦,“玉大人,你說我怎么就攤上這事恭金〔儋鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵横腿,是天一觀的道長颓屑。 經(jīng)常有香客問我,道長耿焊,這世上最難降的妖魔是什么揪惦? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮罗侯,結(jié)果婚禮上器腋,老公的妹妹穿的比我還像新娘。我一直安慰自己钩杰,他們只是感情好纫塌,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布婆瓜。 她就那樣靜靜地躺著焊虏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛙酪。 梳的紋絲不亂的頭發(fā)上避除,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天怎披,我揣著相機與錄音,去河邊找鬼驹饺。 笑死钳枕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的赏壹。 我是一名探鬼主播鱼炒,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蝌借!你這毒婦竟也來了昔瞧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤菩佑,失蹤者是張志新(化名)和其女友劉穎自晰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稍坯,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡酬荞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年搓劫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混巧。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡枪向,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咧党,到底是詐尸還是另有隱情秘蛔,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布傍衡,位于F島的核電站深员,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛙埂。R本人自食惡果不足惜倦畅,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箱残。 院中可真熱鬧滔迈,春花似錦、人聲如沸被辑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盼理。三九已至谈山,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宏怔,已是汗流浹背奏路。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留臊诊,地道東北人鸽粉。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像抓艳,于是被迫代替她去往敵國和親触机。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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