Android 内存泄露及LeakCanary分析

一.前言

       在日常的Android开发中,不经意间就会造成内存泄露,如果持续泄露的话,那么最后会造成内存溢出,应用也就崩溃了。内存泄露与内存溢出是老生常谈的问题,在这里还是看一下官方对两者的定义:
       内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
       内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,从而出现OOM;
       内存泄漏不一定会引起崩溃,但是内存溢出一定会!
       什么操作会造成动态分配的最内存不会被释放,即不会被回收呢,平时常见的操作如下:
       1.单例造成的内存泄露:单例是长时间存活的,如果持有Activity的引用,那么就会导致Activity在销毁的时候不会被回收;
       2.非静态内部类创建静态实例造成的内存泄露:非静态内部类持有外部类的引用,如果创建静态实例,那么会一直持有外部类的引用,导致外部实例不会被回收;
       3.Handler造成的内存泄露:原因如上,Handler持有外部类的引用,Message持有Handler的引用,MessageQueue持有Message的引用,Looper持有MessageQueue的引用,Looper一直存在,导致引用链都不会被回收;
       4.线程造成的内存泄露:在Activity内部启动匿名的Thread,在Thread内部执行耗时的操作,Thread不退出的话,就会导致Activity在销毁的时候不会被回收;
       5.WebView造成的内存泄露:WebView不好管理,通常将其放在独立的进程中,即使内存溢出也不会导致应用崩溃;

       以上就是常见的导致内存泄露的操作,那么系统在做垃圾回收的时候会根据一定的规则来判断对象是否能被回收,那规则是什么呢?
       垃圾收集器对Java堆里的对象是否进行回收的判断准则:Java对象是存活 or 死亡,判断对象为死亡才会进行回收
       在Java虚拟机中,判断对象是否存活有2种方法:
       引用计数法
       引用链法(可达性分析法)
       当前最常用的是引用链法(可达性分析法),从字面上来看,该方法的判断需要一个链头,然后一步一步向下搜索,如果可达,代表不能回收;如果不可达,代表可以回收;接下来Gc Roots登场了:

二.GC Roots

       通过一系列称为"GC Roots"的对象作为起始点,从这些点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明该对象是不可用的。
       判定图如下:

image.png

      Q1:某对象没有任何引用的时候才进行回收?
         A:不。无法往上追溯到GC Roots引用点的时候就回收[比如:上图中的G、H被F引用,但是F没有被GcRoots引用]
      Q2:某对象被别的对象引用就不能进行回收?
         A:不。软引用,弱引用,虚引用就可以回收

Java虚拟机内存结构

       那么什么对象可以作为GC Roots呢?先回忆一下Java虚拟机内存结构:
       Java虚拟机在运行Java程序时,会管理着一块内存区域:运行时数据区,在运行时数据区里,会根据用途进行划分:
       Java虚拟机栈(栈区):存放java方法执行的局部变量
       本地方法栈:native方法
       Java堆(堆区):存放java对象实例
       方法区:存放常量、静态变量等数据
       程序计数器:线程私有的内存区域,实现异常处理及线程恢复
       不能回收的对象肯定存在运行时数据区内,那么GC Roots也就存在运行时数据区里,那么可以作为GC Roots的对象如下:
       1.Java stack中引用的对象

public class ClassA {
    public static  void main(String[] args) {
         ClassA a = new ClassA();
         a = null;
    }
}

      a是栈帧中的本地变量,a就是GC Root,由于a=null,a与new ClassA()对象断开了链接,所以对象会被回收。
       2.方法区中静态引用指向的对象

public class ClassA {
    public static ClassA r;
    public static void main(String[] args){
       ClassA a = new ClassA();
       a.r = new ClassA();
       a = null;
    }
}

      栈帧中的本地变量a=null,由于a断开了与GC Root对象(a对象)的联系,所以a对象会被回收。由于给ClassA的成员变量r赋值了变量的引用,并且r成员变量是静态的,所以r就是一个GC Root对象,所以r指向的对象不会被回收。
       3.方法区中常量引用指向的对象

public class ClassA {
    public static final ClassA r = new ClassA();
    public static void main(String[] args){
       ClassA a = new ClassA();
       a = null;
    }
}

      常量r引用的对象不会因为a引用的对象的回收而被回收。
       4.Native方法中JNI引用指向的对象

JNIEXPORT void JNICALL Java_com_pecuyu_jnirefdemo_MainActivity_newStringNative(JNIEnv *env, jobject instance,jstring jmsg) {
...
   // 缓存String的class
   jclass jc = (*env)->FindClass(env, STRING_PATH);
   //实例化该类
   jobject job = env->AllocObject(jc);
}

       5.Thread-活着的线程
      https://www.cnblogs.com/tangZH/p/10955429.html
        以上分析了常见的内存泄露场景及以及影响内存回收的GC Roots,平时在开发中除了要规范编码习惯之外,还可以引入LeakCanary来检测应用中存在内存泄露的地方。

三.LeakCanary使用

        LeakCanary是Square公司开源的一个库,借助它开发人员可以在App运行的过程中检测内存泄露,它把对象内存泄露的引用链也给开发人员分析出来了,借助它去修复内存泄露非常方便,接下来一起来看一下LeakCanary如何使用及源码分析。

a.引用

        在引入LeakCanary的时候,只需要在app/build.gradle中加入下面这行配置即可:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'

        从引入方式可以看到,用的是debugImplementation方式,即只在debug模式的编译和最终的debug apk打包时有效,LeakCanary在分析时会影响一定性能的,影响app的运行速度,因此只在debug模式下使用。

b.启动

        直接从代码分析,通过查看引入库的源码AndroidManifest.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher" >

    <uses-sdk android:minSdkVersion="14" />

    <application>
        <provider
            android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
            android:authorities="${applicationId}.leakcanary-installer"
            android:enabled="@bool/leak_canary_watcher_auto_install"
            android:exported="false" />
    </application>

</manifest>

        我们知道,在Android应用进程启动时,入口在ActivityThread的main(),通过跟AMS交互,会执行handleBindApplication()方法,在该方法内部会执行如下逻辑:

private void handBindApplication() {
    ......
    ......
    if (!data.restrictedBackupMode) {
        if (!ArrayUtils.isEmpty(data.providers)) {
            installContentProviders(app, data.providers);
            .......
        }
    }
    ......
    ......
}

        也就是说在启动application的时候会启动该application下的所有ContentProvider,LeakCanary正好利用了ContentProvider自动启动这一点,继承ContentProvier,在应用启动的时候就被启动了,不用开发者手动通过代码启动(类似init()之类)了。

internal sealed class AppWatcherInstaller : ContentProvider() {

  internal class MainProcess : AppWatcherInstaller()

  internal class LeakCanaryProcess : AppWatcherInstaller()

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
  ........
  .........
}

        以下可以看到:LeakCanary是用kotlin写的,在ContentProvider的onCreate()方法内执行了AppWatcher.manualInstall(application),检测内存泄露逻辑就从这个方法开始了,接下来通过这个方法一步一步对源码进行分析。

四.LeakCanary源码分析

a.AppWatcher.kt
fun manualInstall(application: Application) {
    InternalAppWatcher.install(application)
}

        AppWatcher内部的manualInstall()方法中调用了InternalAppWatcher中的方法:

b.InternalAppWatcher.kt
init {
    val internalLeakCanary = try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
          .get(null)
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
    @kotlin.Suppress("UNCHECKED_CAST")
    onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
  }

val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true }
  )

fun install(application: Application) {
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application
    if (isDebuggableBuild) {
      SharkLog.logger = DefaultCanaryLog()
    }

    val configProvider = { AppWatcher.config }
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
}

        通过以上可以看到,在install方法内,会先检测是否是主线程,是否已经初始化,接下来执行了ActivityDestroyWatcher.install及FragmentDestroyWatcher.install方法,从字面意思来看,就是检测Activity和Fragment在销毁后是否被回收;最后执行了onAppWatcherInstalled(application),通过init{}内部逻辑可以看到onAppWatcherInstalled是InternalLeakCanary的对象,执行了InternalLeakCanary的方法,后面i.HeapDumpTrigger.kt中会进行分析。

c.ActivityDestroyWatcher.kt
internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }
    }

  companion object {
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

       在install()内部通过执行Application.registerActivityLifecycleCallbacks()注册Activity生命周期监听,然后在onActivityDestroyed()中进行objectWatcher.watch(activity,....)进行检测。

d.FragmentDestroyWatcher.kt
internal object FragmentDestroyWatcher {

  private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
  private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
    "leakcanary.internal.AndroidXFragmentDestroyWatcher"

  private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME =StringBuilder("android.").append("support.v4.app.Fragment").toString()
  private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME ="leakcanary.internal.AndroidSupportFragmentDestroyWatcher"

  fun install(
    application: Application,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ) {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
          AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
      )
    }

    getWatcherIfAvailable(
        ANDROIDX_FRAGMENT_CLASS_NAME,
        ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
        objectWatcher,
        configProvider
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

    getWatcherIfAvailable(
        ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
        ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
        objectWatcher,
        configProvider
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

    if (fragmentDestroyWatchers.size == 0) {
      return
    }

    application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    })
  }

  private fun getWatcherIfAvailable(
    fragmentClassName: String,
    watcherClassName: String,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ): ((Activity) -> Unit)? {

    return if (classAvailable(fragmentClassName) &&
        classAvailable(watcherClassName)
    ) {
      val watcherConstructor = Class.forName(watcherClassName)
          .getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
      @Suppress("UNCHECKED_CAST")
      watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit

    } else {
      null
    }
  }

  private fun classAvailable(className: String): Boolean {
    return try {
      Class.forName(className)
      true
    } catch (e: Throwable) {
      false
    }
  }
}

       在install()内部对多种Fragment添加了检测,并通过反射方式创建对应的实例:
       Android O以上,添加了AndroidOFragmentDestroyWatcher
       AndroidX中,添加了AndroidXFragmentDestroyWatcher
       Support包中,添加了AndroidSupportFragmentDestroyWatcher
       可以看到,在FragmentDestroyWatcher内部也调用了application.registerActivityLifecycleCallbacks()监听了onActivityCreated()进行了activity的赋值,是因为Fragment是依赖Activity存在的,且注册监听需要通过FragmentManager,FragmentManager需要通过Activity来获取到。
       通过对比以上三个kt文件的源码可以发现,公共部分如下,拿AndroidOFragmentDestroyWatcher.kt这个文件进行分析:

e.AndroidOFragmentDestroyWatcher.kt
internal class AndroidOFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        objectWatcher.watch(
            fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
        )
      }
    }
  }

  override fun invoke(activity: Activity) {
    val fragmentManager = activity.fragmentManager//其他两个是supportFragmentManager
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
  }
}

       三种Fragment在获取fragmentManager时方式有点不一样,但是都是利用fragmentManager.registerFragmentLifecycleCallbacks注册监听,在onFragmentDestroy()中检测Fragment对象是否泄露,在在onFragmentViewDestroyed()里面检测Fragment View对象是否泄露;

f.AndroidXFragmentDestroyWatcher.kt
    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
    }

       由于Androidx中引入了ViewModel的执行,所以在AndroidXFragmentDestroyWatcher中的onFragmentCreated()中执行了ViewModelClearedWatcher的install方法;

g.ViewModelClearedWatcher.kt
override fun onCleared() {
    if (viewModelMap != null && configProvider().watchViewModels) {
      viewModelMap.values.forEach { viewModel ->
        objectWatcher.watch(
            viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
        )
      }
    }
  }

companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, objectWatcher, configProvider) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }

       在Fragement被销毁的时候,会对ViewModel执行clear(),接着执行onCleared(),因此在onCleared()里面对ViewModel进行回收检测;

g.ObjectWatcher.kt
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

private val queue = ReferenceQueue<Any>()

@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) " ($description)" else "") +
          " with key $key"
    }

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

       在分析watch()方法前,先看一下检测原理:
       Java中的WeakReference是弱引用类型,每当发生GC时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收,同时或者稍后的时间这个WeakReference会被入队到ReferenceQueue中,LeakCanary中检测内存泄露就是基于这个原理。
       ObjectWatcher的watch()中对应的主要实现要点为:
       1.当一个对象需要被回收时,生成一个唯一的key,将它们封装进KeyedWeakReference中,并传入自定义的ReferenceQueue;
       2.将key和KeyedWeakReference放入一个map中;
       3.过一会儿之后(默认是5秒)主动触发GC,将自定义的ReferenceQueue中的KeyedWeakReference全部移除(它们所引用的对象已被回收),并同时根据这些KeyedWeakReference的key将map中的KeyedWeakReference也移除掉;
       4.此时如果map中还有KeyedWeakReference剩余,那么就是没有入队的,也就是说这些KeyedWeakReference所对应的对象还没被回收,这里就产生了内存泄露;

  private fun removeWeaklyReachableObjects() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }

       removeWeaklyReachableObjects()是从队列中不断取出弱引用ref,然后从map中根据ref的key作为索引来删除ref;

@Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

       moveToRetained()是试图根据弱引用的key来从map中找到对应的弱引用,如果未找到,说明对象已经被释放;如果找到的话,说明该对象没有被释放,即内存泄露了,然后执行onObjectRetained()通知,会回调InternalLeakCanary.kt中对应的方法;

h.InternalLeakCanary.kt

       前面在分析InternalAppWatcher.kt中有执行onAppWatcherInstalled(application)方法,即执行到了InternalLeakCanary内的方法,一起看一下:

  override fun invoke(application: Application) {
    _application = application

    checkRunningInDebuggableBuild()

    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))

    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
    ........
  }

       在invoke()内部,主要做了以下几件事:
       1.检测是不是Debug版本;
       2.通过objectWatcher将自己加入addOnObjectRetainedListener,前面分析到,如果有内存泄露的话,会回调onObjectRetained();
       3.创建AndroidHeapDumper对象,生成内存泄露关联的hprof文件,供开发者分析;
       4.创建HandlerThread及对应的后台Handler,创建HeapDumpTrigger实例,传入对应的参数;
       接下来看一下回调方法的执行流程:

override fun onObjectRetained() = scheduleRetainedObjectCheck()

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }
i.HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects()
    }, delayMillis)
  }

       在scheduleRetainedObjectCheck()里面通过创建的后台Handler执行checkRetainedObjects();

private fun checkRetainedObjects() {
        ........
        .......
     //--------------------------分析1-----------------------------
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    //--------------------------分析2-----------------------------
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    //-------------------------分析3---------------------------------
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    ........
    ........

    // //--------------------------分析4-----------------------------
    dumpHeap(retainedReferenceCount, retry = true)
  }

       通过以上可以看到:
       分析1:还剩多少对象没被回收,这些对象可能不是已经泄露的;
       分析2:手动触发GC,这里触发GC时还延迟了100ms,给那些回收了的对象入引用队列一点时间,好让结果更准确,再看看还剩多少对象没被回收;
       分析3:执行checkRetainedCount(),这里有2种情况返回true:a. 未被回收的对象数是0,展示无泄漏的通知;2. 当retainedReferenceCount小于5个,展示有泄漏的通知(app可见或不可见超过5秒),延迟2秒再进行检查checkRetainedObjects();
       分析4:开始dump,通过 Debug.dumpHprofData(filePath) dump heap,开始dump heap之前还得objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) 清除一下这次dump开始之前的所有引用,最后是用HeapAnalyzerService这个IntentService去分析heap,具体在HeapAnalyzerService#runAnalysis();
       HeapAnalyzerService里调用的是 Shark 库对 heap 进行分析,分析的结果再返回到 DefaultOnHeapAnalyzedListener.onHeapAnalyzed 进行分析结果入库、发送通知消息。

总结一下

image.png

       以上就是内存泄露及LeakCanary源码分析,在分析过程中参考了https://blog.csdn.net/xfhy_/article/details/111072036,非常感谢该作者的分析!

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

推荐阅读更多精彩内容