Android内存优化——内存泄露检测分析方法

上一篇文章总结了一些常见的内存泄露场景及优化方案,这篇文章继续总结内存泄露的一些常用的检测和分析方法。

Lint代码检查

AndroidStudio自动Lint代码检查工具,一些常见的代码警告Lint工具都会给我们提示。使用也比较简单:

Analyze —> Inspect Code 然后选择检查范围:

Lint使用-1
Lint使用-2
Lint使用-3

比如如果存在非静态内部类的Handler,可能会导致内存泄露,检查结果就会显示在AndroidStudio的控制台。

其实,只要我们在设置里面勾选了Lint代码检查(AnroidStudio默认是勾选了的),在写代码的时候就会自动提示可能发生内存泄露。

Lint检查代码内存泄露
Lint提示内存泄露

通常在写Handler、静态字段、标记对象等可能存在的内存泄露时,Lint检查工具都会有一个警告提示信息,我们可以根据Lint检查的提示信息来避免这些有可能发生的内存泄露。

Android Monitor

在AndroidStudio中,可以通过Monitors来监控Memory、CPU、Network、GPU等。在Monitors监控中,我们可以获取内存的各种信息来分析内存泄露。

首先运行工程后,打开控制台的Android Monitor:

Android Monitor

在运行设备中使用app(各个页面的跳转,使用相应的各种功能),就可以看到内存使用的不断变化:

内存使用变化

淡蓝色和浅灰色区域就是内存分配的变化过程,浅灰色表示空闲内存,淡蓝色表示使用内存。

通常,我们在打开一个新的页面后,使用的内存就会增加,相应的,关闭一个页面后,系统执行了GC,使用的内存应该下降。如果我们在退出界面并执行GC后,内存使用并未下降明显,或者使用内存没有下降初始的使用大小,那么有可能就发生了内存泄露。

运行工程,在设备上操作app,观察Monitor中内存的变化,点击 initiate GC 触发GC,然后点击Dump Java Heap转出堆信息,稍等片刻,生成hprof文件,生成后会在Studio中自动打开。

hprof文件

点击右侧的Analyzer Tasks,再点击Perform Analyzer,展开下面分析结果
中的 Leaked Activities 就可以看到发生内存泄露的Activity了。


Monitor内存泄露分析

可以根据左侧的引用树,来查找持有Activity引用的位置,从而判断出哪个地方导致了内存泄露。

Mat

使用第三方的Mat工具来分析内存泄露,需要在官网下载独立版的Mat。

将Android Monitor生成的hprof文件导出为标准的hprof文件(必须这样导出,直接copy出来会报错的):

导出hprof文件

使用Mat打开导出的hprof文件:

Mat打开hprof文件

点击Histogram(直方图),可以看到类对应的实例数量的统计。

直方图

在Class Name下面输入需要匹配的类名,根据类来查看它的实例的引用,进而分析是否存在内存泄露。

类的实例

可以看到TestActivity和MyHandler都只有一个实例被引用。

TestActivity点击右键—>Merge Shortest Paths to GC Roots —>exclude all phantom/weak/soft etc.references。

Merge Shortest Paths to GC Roots 可以查看一个对象到RC Roots是否存在引用链相连接, 在JAVA中是通过可达性(Reachability Analysis)来判断对象是否存活,这个算法的基本思想是通过一系列的称谓"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走得路径称为引用链,当一个对象到GC Roots没有任何引用链相连则该对象被判定为可以被回收的对象,反之不能被回收,我们可以选择 exclude all phantom/weak/soft etc.references(排查虚引用/弱引用/软引用等)因为被虚引用/弱引用/软引用的对象可以直接被GC给回收。

参考自Android 性能优化之使用MAT分析内存泄露问题

TestActivity实例 RC Roots引用链

可以看到TestActivity实例存在GC Roots链,TextActivity实例被mMessageQueue.mMessae.target.this$0持有,那么发生了内存泄露,我们可以根据引用链来在代码中找到内存泄露的位置。

LeakCanary

LeakCanary是square开源的检测内存泄露的第三方库。它最大的有点就是开发者只需要添加简单代码,app在运行时如果发生了内存泄露,就会很直观的将内存泄露的详细信息展示在通知栏上,这样避免了Android Monitor或者Mat等工具的繁琐的分析过程。

项目地址:https://github.com/square/leakcanary

在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中进行初始化:

public class App extends Application {

    // 模拟内存泄露场景
    public static ArrayList<Activity> sActivities = new ArrayList<>();

    @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...
    }
}

在Application中新建一个静态List,里面存储Activity,来模拟内存泄露案例:

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        App.sActivities.add(this);
    }

    public void back(View view) {
        finish();
    }
}

运行app后,在控制台可以看到LeakCanary的日志:

04-25 10:20:46.793 D/LeakCanary: In com.xiao.memoryleakexample:1.0:1.
04-25 10:20:46.793 D/LeakCanary: * com.xiao.memoryleakexample.TestActivity has leaked:
04-25 10:20:46.793 D/LeakCanary: * GC ROOT static com.xiao.memoryleakexample.app.App.sActivities
04-25 10:20:46.793 D/LeakCanary: * references java.util.ArrayList.array
04-25 10:20:46.793 D/LeakCanary: * references array java.lang.Object[].[0]
04-25 10:20:46.793 D/LeakCanary: * leaks com.xiao.memoryleakexample.TestActivity instance
04-25 10:20:46.793 D/LeakCanary: * Retaining: 88 KB.
04-25 10:20:46.793 D/LeakCanary: * Reference Key: 915bf11a-db9f-468e-8064-d6fb103710e9
04-25 10:20:46.793 D/LeakCanary: * Device: OPPO OPPO OPPO R9 Plusm A R9PlusmA
04-25 10:20:46.793 D/LeakCanary: * Android Version: 5.1.1 API: 22 LeakCanary: 1.5 00f37f5
04-25 10:20:46.793 D/LeakCanary: * Durations: watch=5049ms, gc=207ms, heap dump=979ms, analysis=122889ms
04-25 10:20:46.793 D/LeakCanary: * Details:
04-25 10:20:46.793 D/LeakCanary: * Class com.xiao.memoryleakexample.app.App
04-25 10:20:46.793 D/LeakCanary: |   static $staticOverhead = byte[24]@314667009 (0x12c17001)
04-25 10:20:46.793 D/LeakCanary: |   static sActivities = java.util.ArrayList@315492800 (0x12ce09c0)
04-25 10:20:46.793 D/LeakCanary: |   static serialVersionUID = -920324649544707127
04-25 10:20:46.793 D/LeakCanary: |   static $change = null
04-25 10:20:46.793 D/LeakCanary: * Instance of java.util.ArrayList
04-25 10:20:46.793 D/LeakCanary: |   static $staticOverhead = byte[16]@1893824473 (0x70e177d9)
04-25 10:20:46.793 D/LeakCanary: |   static MIN_CAPACITY_INCREMENT = 12
04-25 10:20:46.793 D/LeakCanary: |   static serialVersionUID = 8683452581122892189
04-25 10:20:46.793 D/LeakCanary: |   array = java.lang.Object[12]@318048768 (0x12f50a00)
04-25 10:20:46.793 D/LeakCanary: |   size = 1
04-25 10:20:46.793 D/LeakCanary: |   modCount = 1
04-25 10:20:46.793 D/LeakCanary: * Array of java.lang.Object[]
04-25 10:20:46.793 D/LeakCanary: |   [0] = com.xiao.memoryleakexample.TestActivity@316091520 (0x12d72c80)
04-25 10:20:46.793 D/LeakCanary: |   [1] = null
04-25 10:20:46.793 D/LeakCanary: |   [2] = null
04-25 10:20:46.793 D/LeakCanary: |   [3] = null
04-25 10:20:46.793 D/LeakCanary: |   [4] = null
04-25 10:20:46.793 D/LeakCanary: |   [5] = null
04-25 10:20:46.793 D/LeakCanary: |   [6] = null
04-25 10:20:46.793 D/LeakCanary: |   [7] = null
04-25 10:20:46.793 D/LeakCanary: |   [8] = null
04-25 10:20:46.793 D/LeakCanary: |   [9] = null
04-25 10:20:46.793 D/LeakCanary: |   [10] = null
04-25 10:20:46.793 D/LeakCanary: |   [11] = null
04-25 10:20:46.793 D/LeakCanary: * Instance of com.xiao.memoryleakexample.TestActivity
04-25 10:20:46.793 D/LeakCanary: |   static $staticOverhead = byte[16]@316125185 (0x12d7b001)
04-25 10:20:46.793 D/LeakCanary: |   static serialVersionUID = 836998863274086997
04-25 10:20:46.793 D/LeakCanary: |   static $change = null
04-25 10:20:46.793 D/LeakCanary: |   mHandler = com.xiao.memoryleakexample.TestActivity$MyHandler@318005952 (0x12f462c0)
04-25 10:20:46.793 D/LeakCanary: |   mDelegate = android.support.v7.app.AppCompatDelegateImplV14@314816320 (0x12c3b740)
04-25 10:20:46.793 D/LeakCanary: |   mEatKeyUpEvent = false
04-25 10:20:46.793 D/LeakCanary: |   mResources = null
04-25 10:20:46.793 D/LeakCanary: |   mThemeId = 2131230884
04-25 10:20:46.793 D/LeakCanary: |   mCreated = true
04-25 10:20:46.793 D/LeakCanary: |   mFragments = android.support.v4.app.FragmentController@317876896 (0x12f26aa0)
04-25 10:20:46.793 D/LeakCanary: |   mHandler = android.support.v4.app.FragmentActivity$1@318005920 (0x12f462a0)
04-25 10:20:46.793 D/LeakCanary: |   mNextCandidateRequestIndex = 0
04-25 10:20:46.793 D/LeakCanary: |   mOptionsMenuInvalidated = false
04-25 10:20:46.793 D/LeakCanary: |   mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@318008352 (0x12f46c20)
04-25 10:20:46.793 D/LeakCanary: |   mReallyStopped = true
04-25 10:20:46.793 D/LeakCanary: |   mRequestedPermissionsFromFragment = false
04-25 10:20:46.793 D/LeakCanary: |   mResumed = false
04-25 10:20:46.793 D/LeakCanary: |   mRetaining = false
04-25 10:20:46.793 D/LeakCanary: |   mStopped = true
04-25 10:20:46.793 D/LeakCanary: |   mStartedActivityFromFragment = false
04-25 10:20:46.793 D/LeakCanary: |   mStartedIntentSenderFromFragment = false
04-25 10:20:46.793 D/LeakCanary: |   mExtraDataMap = android.support.v4.util.SimpleArrayMap@318005888 (0x12f46280)
04-25 10:20:46.793 D/LeakCanary: |   mActionBar = null
04-25 10:20:46.793 D/LeakCanary: |   mActivityInfo = android.content.pm.ActivityInfo@318009472 (0x12f47080)
04-25 10:20:46.793 D/LeakCanary: |   mActivityTransitionState = android.app.ActivityTransitionState@317937344 (0x12f356c0)
04-25 10:20:46.793 D/LeakCanary: |   mAllLoaderManagers = android.util.ArrayMap@318081312 (0x12f58920)
04-25 10:20:46.793 D/LeakCanary: |   mApplication = com.xiao.memoryleakexample.app.App@315492832 (0x12ce09e0)
04-25 10:20:46.793 D/LeakCanary: |   mCalled = true
04-25 10:20:46.793 D/LeakCanary: |   mChangeCanvasToTranslucent = false
04-25 10:20:46.793 D/LeakCanary: |   mChangingConfigurations = false
04-25 10:20:46.793 D/LeakCanary: |   mCheckedForLoaderManager = true
04-25 10:20:46.793 D/LeakCanary: |   mComponent = android.content.ComponentName@314990768 (0x12c660b0)
04-25 10:20:46.793 D/LeakCanary: |   mConfigChangeFlags = 0
04-25 10:20:46.793 D/LeakCanary: |   mContainer = android.app.Activity$1@317876848 (0x12f26a70)
04-25 10:20:46.793 D/LeakCanary: |   mCurrentConfig = android.content.res.Configuration@317856672 (0x12f21ba0)
04-25 10:20:46.793 D/LeakCanary: |   mDecor = null
04-25 10:20:46.793 D/LeakCanary: |   mDefaultKeyMode = 0
04-25 10:20:46.793 D/LeakCanary: |   mDefaultKeySsb = null
04-25 10:20:46.793 D/LeakCanary: |   mDestroyed = true
04-25 10:20:46.793 D/LeakCanary: |   mDoReportFullyDrawn = false
04-25 10:20:46.793 D/LeakCanary: |   mEmbeddedID = null
04-25 10:20:46.793 D/LeakCanary: |   mEnableDefaultActionBarUp = false
04-25 10:20:46.793 D/LeakCanary: |   mEnterTransitionListener = android.app.SharedElementCallback$1@1893595344 (0x70ddf8d0)
04-25 10:20:46.793 D/LeakCanary: |   mExitTransitionListener = android.app.SharedElementCallback$1@1893595344 (0x70ddf8d0)
04-25 10:20:46.793 D/LeakCanary: |   mFinished = true
04-25 10:20:46.793 D/LeakCanary: |   mFragments = android.app.FragmentManagerImpl@317856448 (0x12f21ac0)
04-25 10:20:46.793 D/LeakCanary: |   mHandler = android.os.Handler@318005856 (0x12f46260)
04-25 10:20:46.793 D/LeakCanary: |   mIdent = 578025123
04-25 10:20:46.793 D/LeakCanary: |   mInstanceTracker = android.os.StrictMode$InstanceTracker@317876864 (0x12f26a80)
04-25 10:20:46.793 D/LeakCanary: |   mInstrumentation = android.app.Instrumentation@315352176 (0x12cbe470)
04-25 10:20:46.793 D/LeakCanary: |   mIntent = android.content.Intent@317362304 (0x12ea9080)
04-25 10:20:46.793 D/LeakCanary: |   mLastNonConfigurationInstances = null
04-25 10:20:46.793 D/LeakCanary: |   mLoaderManager = null
04-25 10:20:46.793 D/LeakCanary: |   mLoadersStarted = false
04-25 10:20:46.793 D/LeakCanary: |   mMainThread = android.app.ActivityThread@314856000 (0x12c45240)
04-25 10:20:46.803 D/LeakCanary: |   mManagedCursors = java.util.ArrayList@318005792 (0x12f46220)
04-25 10:20:46.803 D/LeakCanary: |   mManagedDialogs = null
04-25 10:20:46.803 D/LeakCanary: |   mMenuInflater = null
04-25 10:20:46.803 D/LeakCanary: |   mParent = null
04-25 10:20:46.803 D/LeakCanary: |   mReferrer = java.lang.String@314984512 (0x12c64840)
04-25 10:20:46.803 D/LeakCanary: |   mResultCode = 0
04-25 10:20:46.803 D/LeakCanary: |   mResultData = null
04-25 10:20:46.803 D/LeakCanary: |   mResumed = false
04-25 10:20:46.803 D/LeakCanary: |   mSearchManager = null
04-25 10:20:46.803 D/LeakCanary: |   mStartedActivity = false
04-25 10:20:46.803 D/LeakCanary: |   mStopped = true
04-25 10:20:46.803 D/LeakCanary: |   mTemporaryPause = false
04-25 10:20:46.803 D/LeakCanary: |   mTitle = java.lang.String@316164352 (0x12d84900)
04-25 10:20:46.803 D/LeakCanary: |   mTitleColor = 0
04-25 10:20:46.803 D/LeakCanary: |   mTitleReady = true
04-25 10:20:46.803 D/LeakCanary: |   mToken = android.os.BinderProxy@314983040 (0x12c64280)
04-25 10:20:46.803 D/LeakCanary: |   mTranslucentCallback = null
04-25 10:20:46.803 D/LeakCanary: |   mUiThread = java.lang.Thread@1967775656 (0x7549dfa8)
04-25 10:20:46.803 D/LeakCanary: |   mVisibleBehind = false
04-25 10:20:46.803 D/LeakCanary: |   mVisibleFromClient = true
04-25 10:20:46.803 D/LeakCanary: |   mVisibleFromServer = true
04-25 10:20:46.803 D/LeakCanary: |   mVoiceInteractor = null
04-25 10:20:46.803 D/LeakCanary: |   mWindow = com.android.internal.policy.impl.PhoneWindow@315631936 (0x12d02940)
04-25 10:20:46.803 D/LeakCanary: |   mWindowAdded = true
04-25 10:20:46.803 D/LeakCanary: |   mWindowManager = android.view.WindowManagerImpl@318006848 (0x12f46640)
04-25 10:20:46.803 D/LeakCanary: |   mInflater = com.android.internal.policy.impl.PhoneLayoutInflater@317929312 (0x12f33760)
04-25 10:20:46.803 D/LeakCanary: |   mOverrideConfiguration = null
04-25 10:20:46.803 D/LeakCanary: |   mResources = android.content.res.Resources@314591360 (0x12c04880)
04-25 10:20:46.803 D/LeakCanary: |   mTheme = android.content.res.Resources$Theme@318006400 (0x12f46480)
04-25 10:20:46.803 D/LeakCanary: |   mThemeResource = 2131230884
04-25 10:20:46.803 D/LeakCanary: |   mBase = android.app.ContextImpl@317145792 (0x12e742c0)
04-25 10:20:46.803 D/LeakCanary: * Excluded Refs:
04-25 10:20:46.803 D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mNextServedView
04-25 10:20:46.803 D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedView
04-25 10:20:46.803 D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedInputConnection
04-25 10:20:46.803 D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mCurRootView
04-25 10:20:46.803 D/LeakCanary: | Field: android.animation.LayoutTransition$1.val$parent
04-25 10:20:46.803 D/LeakCanary: | Field: android.view.textservice.SpellCheckerSession$1.this$0
04-25 10:20:46.803 D/LeakCanary: | Field: android.support.v7.internal.widget.ActivityChooserModel.mActivityChoserModelPolicy
04-25 10:20:46.803 D/LeakCanary: | Field: android.widget.ActivityChooserModel.mActivityChoserModelPolicy
04-25 10:20:46.803 D/LeakCanary: | Field: android.accounts.AccountManager$AmsTask$Response.this$1
04-25 10:20:46.803 D/LeakCanary: | Field: android.media.MediaScannerConnection.mContext
04-25 10:20:46.803 D/LeakCanary: | Field: android.os.UserManager.mContext
04-25 10:20:46.803 D/LeakCanary: | Field: android.media.AudioManager$1.this$0
04-25 10:20:46.803 D/LeakCanary: | Field: android.widget.Editor$Blink.this$0
04-25 10:20:46.803 D/LeakCanary: | Field: android.net.ConnectivityManager.sInstance
04-25 10:20:46.803 D/LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
04-25 10:20:46.803 D/LeakCanary: | Static field: android.text.TextLine.sCached
04-25 10:20:46.803 D/LeakCanary: | Thread:FinalizerWatchdogDaemon (always)
04-25 10:20:46.803 D/LeakCanary: | Thread:main (always)
04-25 10:20:46.803 D/LeakCanary: | Thread:LeakCanary-Heap-Dump (always)
04-25 10:20:46.803 D/LeakCanary: | Class:java.lang.ref.WeakReference (always)
04-25 10:20:46.803 D/LeakCanary: | Class:java.lang.ref.SoftReference (always)
04-25 10:20:46.803 D/LeakCanary: | Class:java.lang.ref.PhantomReference (always)
04-25 10:20:46.803 D/LeakCanary: | Class:java.lang.ref.Finalizer (always)
04-25 10:20:46.803 D/LeakCanary: | Class:java.lang.ref.FinalizerReference (always)

日志当中展示了详细的内存泄露信息。同时,在运行设备上,会以通知的形式展示内存泄露:

LeakCanary内存泄露通知

点击通知栏后会展示消息的内存泄露信息,包括泄露的具体实例、以及发生在哪个类中的具体引用位置:

LeakCanary内存泄露详细信息-1
LeakCanary内存泄露详细信息-2
最后

Lint、Android Monitor、Mat,以及LeakCanary都能让我们在平常的开发过程中非常有效的避免内存泄露,至于选择哪个工具,那种方式,就看自己平常的习惯了。个人还是更喜欢使用LeakCanary,只需要简单的集成,就可以更加快速,直观展示内存泄露的信息。

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

推荐阅读更多精彩内容