【Android开发进阶系列】Android性能分析专题

好文推荐——系列文:

1.  背景:Android App优化, 要怎么做?

2.  Android App优化之性能分析工具

3.  Android App优化之提升你的App启动速度之理论基础

4.  Android App优化之提升你的App启动速度之实例挑战

5.  Android App优化之Layout怎么摆

6.  Android App优化之ANR详解

7.  Android App优化之消除卡顿

8.  Android App优化之内存优化

9.  Android App优化之持久电量

10.  Android App优化之如何高效网络请求

1 简介

1.1 官方工具

    一般来说, 学习一门新的技术, 最应该做的就是阅读其官方文档, 那是最权威的。Android本身给我们提供了很多App性能测试和分析工具, 而且大部分都集成到Android Studio或DDMS中, 非常方便使用。

1.1.1 StrictMode

· 说明

        顾名思义, "严格模式", 主要用来限制应用做一些不符合性能规范的事情. 一般用来检测主线程中的耗   时操作和阻塞. 开启StrictMode后, 如果线程中做一些诸如读写文件, 网络访问等操作, 将会在Log console输出一些警告, 警告信息包含Stack Trace来显示哪个地方出了问题.

· 文档

    o https://developer.android.com/reference/android/os/StrictMode.html

· 作用

    o 主要用来做主线程优化分析

1.1.2 Systrace

· 说明

      Systrace是一个收集和检测时间信息的工具,它能显示CPU和时间被消耗在哪儿了,每个进程和线程都在其CPU时间片内做了什么事儿.而且会指示哪个地方出了问题,以及给出Fix建议.

      其以trace文件(html)的方式记录.可以直接用Chrome浏览器打开查看.界面如下:

Systrace

· 文档

    o https://developer.android.com/studio/profile/systrace.html

    o https://developer.android.com/studio/profile/systrace-walkthru.html

    o https://developer.android.com/studio/profile/systrace-commandline.html?hl=fy

· 作用

    o 作用很多,个人主要用来分析UI的绘制时间,结合Hierarchy Viewer来提升UI性能.

    o 也可以用来发现耗时操作.

1.1.3 HierarchyViewer

· 说明

      Hierarchy Viewer提供了一个可视化的界面来观测布局的层级,让我们可以优化布局层级,删除多余的不必要的View层级,提升布局速度.

Hierarchy Viewer

        有必要说明下的是:

        上图红框标出的三个点是关键分析数据. 左起依次代表View的Measure, Layout和Draw的性能. 另外颜色表示该View的该项时间指数, 分为:

    * 绿色, 表示该View的此项性能比该View Tree中超过50%的View都要快.

    * 黄色, 表示该View的此项性能比该View Tree中超过50%的View都要慢.

    * 红色, 表示该View的此项性能是View Tree中最慢的.


· 文档

    o https://developer.android.com/studio/profile/hierarchy-viewer.html

    o https://developer.android.com/studio/profile/hierarchy-viewer-walkthru.html

    o https://developer.android.com/studio/profile/hierarchy-viewer-setup.html

    o https://developer.android.com/studio/profile/optimize-ui.html#HierarchyViewer

· 作用

    o 用来做View层级分析,可以分析出View Tree中的性能阻塞点,以便对症下药,提升布局性能.


        Hierarchy Viewer需要Root的机器(产品机没有开启ViewServer)才可以执行。可以使用第三方的开源的ViewServer来协助我们在未Root的机器上使用Hierarchy Viewer分析.


1.1.4 TraceView——方法耗时分析

· 文档

    o https://developer.android.com/studio/profile/traceview.html

    o https://developer.android.com/studio/profile/traceview-walkthru.html

· 作用

    o 分析方法调用栈以及其执行时间,优化方法执行.

1.1.5 MemoryMonitor——内存监控

· 说明

      内存使用检测器,可以实时检测当前Application的内存使用和释放等信息,并以图形化界面展示。

Memory Monitor

· 文档

    o https://developer.android.com/studio/profile/am-memory.html

    o https://developer.android.com/studio/profile/heap-viewer-walkthru.html

    o https://developer.android.com/studio/profile/allocation-tracker-walkthru.html

· 作用

    o 用来做内存分析,内存泄露排查的不二之选.可以结合heap viewer, allocation tracker来分析.

    o 可以导出hprof文件结合第三方的MAT工具分析泄露点.

1.1.6 OtherMonitor

· 说明

      Android Studio的Monitor还提供了其他三个Motinor --- CPU, GPU,Network.

· 文档

    o https://developer.android.com/studio/profile/am-cpu.html

    o https://developer.android.com/studio/profile/am-gpu.html

    o https://developer.android.com/studio/profile/am-network.html

· 作用

    o 分别用来跟踪监测CPU,GPU和Network的使用极其变化,可以作为网络优化,流量优化和渲染优化等的一个指导. (个人并不常用到~)

1.1.7 其他

    Android的开发者模式中也提供了较多的用来监测性能的选项, 可以用下:

Developer options


1.2 第三方工具

     以下工具全部开源

1.2.1 Google的Battery Historian

· 说明

      Google出品, 通过Android系统的bug report文件来做电量使用分析的工具.

·  文档

    o https://github.com/google/battery-historian

· 作用

    o 用来做电量使用分析.

1.2.2 网易的Emmagee

· 说明

        针对Android App的CPU, 内存, 网络, 电量等多项综合的测试分析.

· 文档

    o https://github.com/NetEase/Emmagee

· 作用

    o 比官方工具更适合国人使用来做App的整体性能分析.

1.2.3 Square的leakcanary

· 说明

      Square出品, 必属精品.类似于App探针的内存泄露监测工具.

· 文档

    o https://github.com/square/leakcanary

· 作用

    o 集成到App中,用来做内存问题预防最好不过了.

1.2.4 AndroidDevMetrics

· 说明

        一个library, 用来检测Activity生命周期执行性能, Dagger2注入性能以及帧率性能的工具.

· 文档

    o https://github.com/frogermcs/AndroidDevMetrics

· 作用

    o 如果你的应用使用的Dagger2,这个就比较必要了.

2 MemoryMonitor

(转自)Android Studio Memory Monitor

http://blog.csdn.net/flyworkspace/article/details/54019812


2.1 使用方法

2.1.1 内存镜像

        生成内存镜像,当从图中发现内存使用较高时,点击小卡车图标,可以主动触发GC。当应用内存使用很高,并且存在很严重的内存泄露时,点击该按钮后的效果并不明显,内存并没有降低或者降低的很少。点击"Dump Java Heap"图标稍等片刻,会生成该应用的内存快照。如图

    • Total Count:内存中该类对象个数。

    • Heap Count:堆内存中该类对象个数。

    • Sizeof:每个对象的大小(如果为0,则大小不固定)。

    • Shallow size:对象本身占有内存大小。

    • Retained Size:释放该对象后,节省的内存大小。

        其中,Retained Size之所以比Shallowsize大的原因是,该对象释放后,会引起其他对象的回收。 例如,某个对象被回收后: 该对象引用的其他对象也会被回收, 该对象A被另一对象B强引用后,之前对象B因为强引用该对象A而没有被回收,现在该对象A被回收后,若对象B强引用的其他对象都已被回收,则对象B也会被回收。

    优化内存:

        点击Class Name中的类名,查看其所有实例(Instance),分析实例中参数所占用的内存。根据Retaine Size排序,查找Instance中Depth较小的实例或者参数,在代码中找到相应的位置,查看内存占用是否合理。

    判断内存泄露的步骤:

        从Total Count入手。该类的对象数量不对。

    • 很多对象只可能存在1个,若存在多个,则可能存在泄露。例如MainActivity的数量为2。又或者由于单例的使用不规范而导致创建多个“单例”对象。

    • 某个对象已经不再使用,而其还在内存中显示。例如LoginActivity已经退出了,其数量为1。

        Memory Monitor只提供了内存信息,如需详细信息,可以通过android studio的Captures(View—–Tool window—–Captures)栏,右键点击快照文件,Export to standard .hprof将堆快照(Heap Snapshot)转换成通用的hprof文件,之后可以通过其他内存分析工具打开,例如MAT。

2.1.2 跟踪内存分配(Allocation tracker)

        点击“Start Allocation Tracking”开始监听内存的分配情况,再次点击,监听完成,生成报告。该报告显示这段时间内,内存的分配情况。

2.1.3 小结

        2.1是从内存的静态信息中分析,是某一个点的内存使用情况。2.2是跟踪某一段时间内内存的分配情况,是个过程跟踪。分析内存可以相结合,例如,再进行某个操作前,执行2.1导出静态内存信息,在开启2.2开始跟踪内存的分配。当执行完操作的时候,关闭内存分配的跟踪,再次执行2.1的,导出操作某个流程后的静态信息。将2.1的两个静态表结合2.2的内存分配动态过程一起分析。

2.2 分析样例

2.2.1 占用内存分析样例

        打开某应用后,切换几个页面,内存飞速上涨。这只是一个极端例子,有很多app,随着用的时间越长,内存也是一直在升高。

        根据2.1的方法,生成内存镜像。

        查找可疑的对象,这个过程是逐个分析的过程,例如byte[]其实是其他对象的某个参数,很多本质上都是byte[],例如Bitmap中mBuffer也是byte[],当内存中有很多Bitmap的时候,byte[]也会很高。所以byte[]不是我们重点关注对象,如果真的是因为Bitmap(或者其他类)多而造成的byte[]高,那么下面肯定会有该类也会占用很高内存。

        继续分析下一个HashMap$HashMapEntry。点击它后,在Instance栏中,看到其实例很多,先从占用大的实例入手。

        例如99=这个,点击它后ReferenceTree显示如下:

        得知这个HashMap是ReuseThumbnail…类中ReuseThumbnailManager的REUSE_BITMAPS。在代码中查看其大小是否合理。本例中REUSE_BITMAPS参数是static参数,其类型是HashMap,查看逻辑,看其是否正常。

        再看其他参数,逐个分析其内存占用是否正常。

        分析内存是个逐步的过程,一个问题解决后,再次循环这些步骤。有时候虽然列表中显示很多对象占用内存很高,有可能是同一个参数导致的,所以一个问题解决后,有可能有一系列参数占用高的情况会消失。就比如如果高内存bitmap的优化后,byte[]也很降低很多,其他参数也有可能会降低很多。

2.2.2 跟踪内存分配分析样例

        3.1是从静态内存信息中分析内存的使用,现在按照2.2从动态过程中跟踪内存的分配。

        生成报告如下:

        查看Size最大的一个Thread.

        可以看到调用过程,从NewDisplayRunnale(执行了636次)调用了BitmapDecoder的decode方法(执行了135次),从代码中分析过程是否合理。

2.2.3 内存泄露分析样例

        对于android的内存泄露,一般监测Activity的泄露居多,例如LeakCanary默认也是监测Activity是否泄露。

        现写一个demo,故意造成内存泄露作为分析样例。

public class MyActivity extends Activity{

   @Override

   public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        new TestThread().start();

   }


   class TestThread extends Thread{

        @Override

        public voidrun() {

           super.run();

            try{

                Thread.sleep(10 * 60 * 1000);

            }catch(InterruptedException e) {

                e.printStackTrace();

            }

        }

   }

}

        当进入MyActivity点击回退,重复多次。按照2.1方法获取内存镜像。

        看到MyActivity的实例数量为17个。在右边的Analyzer Tasks栏中:

        一般引起Activity泄露是由于其Context被强引用导致的。

        MyActivity的context参数被TestThread引用了。所以在activity的销毁的时候,由于TestThread还引用着MyActivity,所以阻止了MyActivity被释放,因而导致内存泄露。

2.3 性能数据采集

3 DDMS

Android Studio中怎么使用DDMS工具?

http://www.cnblogs.com/gaobig/p/5029381.html


        Android Studio开发工具中,打开DDMS,具体的方式如图:

    打开之后的窗口如图:

    查看进程中的线程:

    查看内存信息:

3.1  Traceview

Android学习之Android studio TraceView和lint工具的使用详解

http://blog.csdn.net/qq_16131393/article/details/51172488

3.1.1 使用方法

    打开AndroidDevice Monitor,这个大家都知道:

    1. 选择你要调试的进程。

    2. 点击start mothod profiling,待图标变黑。

    3. 选择sample base profiling


    这里需要解释一下:

    Trace base profiling

        整体监听,项目中所有方法都会监听,资源消耗比较大。

    sample base profiling

        抽样监听,以指定的频率进行抽样调查,一般不要超过5s,需要较长时间获取准确的样本数据。

    再次点击start mothod profiling,就会生成检测样本。

    效果如下:

        上部分为时间轴,x轴表示时间,黑色区域可放大,每个区域代表每个方法的执行时间。y轴表示每一个独立线程。

        下部分Name为你所选择的颜色区块所代表的性能分析。不同的颜色,代表不同的方法,颜色长度代表占用时间。

        属性介绍:

    Incl cpu time:某方法占用cpu时间(父+子)

    Excl cpu time:某方法本身占用cpu时间(父)

    Incl Real time:某方法真正执行时间(父+子)

    Excl Real time:某方法自身执行时间(父)

        当然还有相应所占百分比,不过多介绍。

        还有Calls+RecurCall调用次数+递归调用次数

        还有比较重要的:

    cpu time/call:平均每次调用占用cpu时间。

    real time/call:平均每次调用所执行的时间。

    我觉得这个参数很具有参考性。

        打开每个方法,会显示Paents和children(即父方法和子方法),以及分别所占用时间。


· 说明

      一个图形化的工具,用来展示和分析方法的执行时间.

TraceView

3.1.2 数据采集


3.2 Heap Viewer

3.2.1 HeapViewer面板

        按上图的标记顺序按下,我们就能看到内存的具体数据,右边面板中数值会在每次GC时发生改变,包括App自动触发或者你来手动触发。

3.2.2 详情

    下面是每一个对象都有的列名含义:

        当我们点击某一行时,可以看到如下的柱状图:

        横坐标是对象的内存大小,这些值随着不同对象是不同的,纵坐标是在某个内存大小上的对象的数量。

3.2.3 HeapViewer的使用

        我们说HeapViewer适合发现内存泄漏的问题,那你知道何为内存泄漏么?

    内存泄漏

    英文名:Memory Leaks

    标准解释:无用的单纯,但是还是没GC ROOT引用的内存

    通俗解释:该死不死的内存

    检测

        那么如何检测呢?Heap Viewer中的数值会自动在每次发生GC时会自动更新,那么我们是等着他自己GC么?小弟不才,刚开始我就是这么一直等啊等,由于GC的时机是系统把握的,所以很不好把握,既然我们是来看内存泄漏,那么我们在需要检测内存泄漏的用例执行过后,手动GC下,然后观察data object一栏的total size(也可以观察HeapSize/Allocated内存的情况),看看内存是不是会回到一个稳定值,多次操作后,只要内存是稳定在某个值,那么说明没有内存溢出的,如果发现内存在每次GC后,都在增长,不管是慢增长还是快速增长,都说明有内存泄漏的可能性。

    实例

    先来看3个图:

    1.刚打开首页,手动GC一下:

        2.首页到详情页10遍,最后回到首页,手动GC一下,直到数值不再变化:

        3.首页到详情页10遍,最后回到首页,手动GC一下:

        从data object一栏看到该类型的数值会在不断增长,可能发生了内存泄漏,而我们也可以从上面三个图的标红部分来看,Allocated分别增加了2.418M和1.084M,而且你继续这么操作下去,内存依然是增长的趋势。

3.2.4 数据采集

4 Emmagee

4.1 使用说明

    1、Emmagee是网易杭州研究院QA团队开发的一个简单易上手的Android性能监测小工具,主要用于监控单个App的CPU,内存,流量,启动耗时,电量,电流等性能状态的变化,且用户可自定义配置监控的频率以及性能的实时显示,并最终生成一份性能统计文件。

    2、操作完成后,从系统任务列表中选择Emmagee,并停止测试,在”storage\sdcard0”下找到命名类似”Emmagee_TestResult_20140403210532.csv”的文件,打卡即为监控的得到的数据。

    3、将csv数据拷贝到excel中生成图表,即可清晰看到整个操作过程中cpu、内存等关键数据的变化。

Android性能测试小工具Emmagee - 敌测漏师

http://blog.csdn.net/anlegor/article/details/22895993

APP性能测试工具Emmagee的使用总结

http://blog.csdn.net/chenrushui/article/details/51589995

Android性能测试工具Emmagee

http://www.cnblogs.com/jytian/p/6516170.html


4.2 性能数据采集


5 leakcanary

· 说明

      Square出品, 必属精品,类似于App探针的内存泄露监测工具.

· 文档

    o https://github.com/square/leakcanary

· 作用

    o 集成到App中,用来做内存问题预防最好不过了.

5.1 使用说明

LeakCanary使用指南(1)

http://blog.csdn.net/hmh0512/article/details/57053265?utm_source=tuicool&utm_medium=referral


    快速集成

        第一步:在build.gradle中添加如下依赖:

dependencies {

    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'

    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

}


        第二步:在自己的Application(假设名为ExampleApplication)中添加如下代码:

public class  ExampleApplication extends Application {

    @Override 

    public void onCreate()  {

        super.onCreate();

        if  (LeakCanary.isInAnalyzerProcess(this)) {

            // This process is dedicated to LeakCanary for heap analysis.

            // You should not init your app in this process.

            return;

        }

        LeakCanary.install(this);

        // Normal app init code...

    }

}

        到这里其实可以检测到Activity的内存泄露了,原理后面再说。以Debug模式运行你的App,你可以看到,你App的图标后面跟着一个Leaks图标,如下图;而如果你以Release模式运行,则没有这个图标。

    测试使用

        假装你是测试人员,你开始各种点击App,进行测试。然后你有幸看到这样一个弹框,如下图。

        你很好奇,然后点击了弹框中间那个图标,于是手机屏幕的左上角出现了你App的图标,再下拉点击那个图标,或者从桌面上LeakCanary图标(跟在你App的图标屁股后面那个)点进去,你看到下图。点击+号可以展开,点击-号收起。

        内存泄露往往发生在,生命周期较长的对象,直接或间接持有了生命周期较短的对象的强引用,导致了生命周期较短的对象不能及时释放。

        上图已经够傻瓜式了,第一行表示生命周期较长的那个对象,图中是AliPayModel这个类;第二行表示生命周期长的那个持有了一个什么样的引用,图中是mActivity;第三行表示生命周期较短的那个对象,图中是SelectPayTypeActivity。

        回去查看源码,发现AliPayModel是个单例,在SelectPayTypeActivity中以AliPayModel.getInstance(this).XXX()的方式调用单例中的XXX()方法。于是AliPayModel通过mActivity持有了SelectPayTypeActivity.this的引用。SelectPayTypeActivity本来应该在用户退出这个页面和进入其他Activity(尤其是其他Activity层级较深时)时释放掉,但是单例的生命周期贯穿整个App,AliPayModel一直引用着SelectPayTypeActivity,导致SelectPayTypeActivity不能及时释放,引发内存泄露。

public class  AliPayModel extends BasePayModel {

    private Activity  mActivity;

    private AliPayModel() {}

    private static  AliPayModel instance = new AliPayModel();

    public static AliPayModel  getInstance(Activity tag) {

        instance.mActivity = tag;

        return instance;

    }

}

        找到了原因,解决方法也呼之欲出。要么AliPayModel这个业务类不要定义成单例,要么mActivity由强引用改成软引用或者弱引用。Java的强、软、弱、虚四种引用的区别不在本文的讨论范围。

    发现开源组件中的内存泄露

        用上述方法,可以检测出各种各样的内存泄露,包括:WebView导致的内存泄露、资源未关闭导致的内存泄露、非静态匿名内部类导致的内存泄露、Handler导致的内存泄露等等。

        请看下图,每次选择图片、上传头像时都会引发0.96M的内存泄露!

        再按图索骥,发现罪魁祸首是将一个Activity定义为static。表示不是很能理解这种神代码。最让人心中万马奔腾的是,它竟然有2600多个star!在这个项目的Issues中很多人反映内存占用大、容易OOM、卡顿等,但是没有人从技术层面去查找和分析原因,更遑论去阅读源码,都是直接拿来就用!


6 参考链接

Android App优化之性能分析工具

http://www.jianshu.com/p/da2a4bfcba68

Android开发调试必备-使用DDMS

http://blog.csdn.net/stzy00/article/details/46554529

Android内存分析工具(二):DDMS

http://blog.csdn.net/berber78/article/details/47784007

Android性能专项测试之Heap Viewer工具

http://blog.csdn.net/itfootball/article/details/48734553


正确使用Android性能分析工具——TraceView

http://android.jobbole.com/78995/

Android性能专项测试之MemoryMonitor工具

http://blog.csdn.net/itfootball/article/details/48712595

Android性能优化第(二)篇---Memory Monitor检测内存泄露

http://www.cnblogs.com/ldq2016/p/6628311.html


Android内存与性能

http://blog.kamidox.com/android-memory-guide.html


Android系统性能调优工具介绍

http://blog.csdn.net/innost/article/details/9008691/


Android开发——Android多进程以及使用场景介绍

http://blog.csdn.net/seu_calvin/article/details/53932171


理解Android进程创建流程

http://gityuan.com/2016/03/26/app-process-create/


LeakCanary使用指南(1)

http://blog.csdn.net/hmh0512/article/details/57053265?utm_source=tuicool&utm_medium=referral


利用LeakCanary来检查Android内存泄漏

http://www.jianshu.com/p/0049e9b344b0


LeakCanary中文使用说明

https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容