探索Android开源框架 - 7. LeakCanary使用及源码解析

简介

  • LeakCanary是Square公司研发的一个可视化的内存泄漏分析工具

LeakCanary2.x

  • 从2.x版本开始有了比较大的升级
  1. 完全使用Kotlin重写;
  2. 使用新的Heap分析工具Shark,替换了之前的haha,按官方的说法,内存占用减少了10倍
  3. 泄露类型分组

使用

添加依赖

  • 最新的LeakCanary只需引入依赖,不需要初始化代码,就能执行内存泄漏检测;
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

如何确定内存泄露的对象

  • 原理是我们常用的 WeakReference,其参数构造函数支持传入一个 ReferenceQueue,当其关联的对象回收时,会将 WeakReference 加入 ReferenceQueue 中。LeakCanary 的做法是继承 ReferenceQueue,增加一个值为 UUID 的属性 key,同时将每个需要监测的对象 WeakReference 以此 UUID 作为键加入一个 map 中。这样,在 GC 过后,removeWeaklyReachableObjects 方法通过遍历 ReferenceQueue,通过 key 值删除 map 中已回收的对象,剩下的对象就基本可以确定发生了内存泄露。

为什么 LeakCanary 不能用于线上

  • 每次内存泄漏以后,都会生成并解析 hprof 文件,容易引起手机卡顿等问题
  • 多次调用 GC,可能会对线上性能产生影响
  • hprof 文件较大,信息回捞成问题

源码解析

AppWatcherInstaller

  • 为什么只需添加依赖,不用在Application中初始化了呢?
  • 我在之前的文章Android Jetpack系列--5. App Startup使用详解有详细介绍过借助ContentProvider自动调用初始化接口,从而避免显示的初始化;
  • 我们来看看AppWatcherInstaller的代码,果然是通过继承ContentProvider来获取应用的 Context,并在 onCreate 中执行初始化逻辑;
internal sealed class AppWatcherInstaller : ContentProvider() {
    override fun onCreate(): Boolean {
        val application = context!!.applicationContext as Application
        AppWatcher.manualInstall(application)
        return true
    }
    。。。
}
  • 注册在Manifest文件中的ContentProvider会在应用启动时,由ActivityThread创建并初始化;
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher"
    >
  <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>
  • 哎?AppWatcherInstaller后面怎么还有个$MainProcess,通过看样子是个内部类呢,回到AppWatcherInstaller中发现原来有两个子类
/**
 * 使用 leakcanary-android 模块时,会默认使用这个,表示在当前 App 进程中使用
 */
internal class MainProcess : AppWatcherInstaller()

/**
 * 当使用 leakcanary-android-process 模块代替 leakcanary-android 模块时,则会使用这个类
 */
internal class LeakCanaryProcess : AppWatcherInstaller()

AppWatcher

  • 上面也说了LeakCanary是通过AppWatcherInstaller继承ContentProvider获取应用的Context,并在onCreate中初始化, 那么我们就来看看onCreate中调用的唯一的初始化方法AppWatcher.manualInstall做了些什么吧
@JvmOverloads
fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
    //检查是否在主线程中
    checkMainThread()
    //如果已经初始化过则抛异常
    if (isInstalled) {
      throw IllegalStateException(
        "AppWatcher already installed, see exception cause for prior install call", installCause
      )
    }
    //检查参数,保存延迟时间应大于零,默认是5秒
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    installCause = RuntimeException("manualInstall() first called here")
    this.retainedDelayMillis = retainedDelayMillis
    //测试模式开启log
    if (application.isDebuggableBuild) {
      LogcatSharkLog.install()
    }
    // 通过反射获取InternalLeakCanary单例对象
    LeakCanaryDelegate.loadLeakCanary(application)

    //注册观察对象
    watchersToInstall.forEach {
      it.install()
    }
}
  • 上面最重要的就是调用watchersToInstall.forEach进行观察对象的注册,其中watchersToInstall在方法入参中设有默认值为appDefaultWatchers(application),其代码如下:
fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
    return listOf(
      //默认初始化以下四个监听器
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
}
  • appDefaultWatchers中默认设置了四个监听器, 可以监听包括Activity,Fragment, ViewModel, RootView, Service;
  • 以ActivityWatcher为例,我们来看看是如何实现的
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

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

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}
  • 通过调用registerActivityLifecycleCallbacks注册Activity 生命周期回调的监听,并在onActivityDestroyed回调中,通过调用reachabilityWatcher.expectWeaklyReachable将每个Activity对象加到观察列表,
  • 那么这个reachabilityWatcher又是什么呢?是通过appDefaultWatchers传过来的,appDefaultWatchers的方法入参有给他设置一个默认值objectWatcher,代码如下
val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
)
  • objectWatcher是一个ObjectWatcher的对象,那么就去这个类里面看看它的expectWeaklyReachable方法的实现吧
@Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
) {
    if (!isEnabled()) {
      return
    }
    //将已经被 GC 的对象从 watchedObjects 集合中删除
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    //封装成一个KeyedWeakReference对象
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    //添加到集合中
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
}
  • 可以看到最终是封装成一个KeyedWeakReference对象并添加到集合中,它继承于 WeakReference,弱引用是不会阻止 GC 回收对象的,同时可以在构造函数中传递一个 ReferenceQueue,用于对象被 GC 后存放的队列。
  • 上面代码的最后通过checkRetainedExecutor.execute执行了moveToRetained,checkRetainedExecutor最终是通过mainHandler.postDelayed执行的moveToRetained,moveToRetained代码如下
@Synchronized private fun moveToRetained(key: String) {
    //删除已经 GC 的对象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      //剩下的对象就可以认为是被保留(没办法 GC)的对象,回调通知事件
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
}

InternalLeakCanary

  • 上面代码中的onObjectRetainedListeners是哪来的呢?还得回到AppWatcher.manualInstall方法中,其中调用watchersToInstall.forEach之前,调用了LeakCanaryDelegate.loadLeakCanary(application)通过反射获取了InternalLeakCanary单例对象,代码如下
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
    try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
}
  • 上面代码最终会调用实际会调用InternalLeakCanary.invoke()方法,代码如下
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
    )
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    addDynamicShortcut(application)

    // We post so that the log happens after Application.onCreate()
    mainHandler.post {
      // https://github.com/square/leakcanary/issues/1981
      // We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
      // which blocks until loaded and that creates a StrictMode violation.
      backgroundHandler.post {
        SharkLog.d {
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }
}
  • 上面代码调用AppWatcher.objectWatcher.addOnObjectRetainedListener(this)添加一个监听,这里入参用的是this,也就是InternalLeakCanary,那么来看一下InternalLeakCanary中对OnObjectRetainedListener的onObjectRetained方法的实现
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
}
  • 其中heapDumpTrigger在上面的invoke方法中有初始化,代码如下
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
)
  • GcTrigger 通过调用 Runtime.getRuntime().gc() 方法触发虚拟机进行 GC 操作
interface GcTrigger {

  fun runGc()

  object Default : GcTrigger {
    override fun runGc() {
      Runtime.getRuntime()
        .gc()
      enqueueReferences()
      System.runFinalization()
    }

    private fun enqueueReferences() {
      try {
        Thread.sleep(100)
      } catch (e: InterruptedException) {
        throw AssertionError()
      }
    }
  }
}
  • AndroidHeapDumper的dumpHeap方法通过调用 Debug.dumpHprofData() 方法从虚拟机中 dump hprof 文件,代码如下
internal class AndroidHeapDumper(
  context: Context,
  private val leakDirectoryProvider: LeakDirectoryProvider
) : HeapDumper {

  private val context: Context = context.applicationContext

  override fun dumpHeap(): DumpHeapResult {
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump

    val waitingForToast = FutureResult<Toast?>()
    showToast(waitingForToast)

    if (!waitingForToast.wait(5, SECONDS)) {
      SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
      return NoHeapDump
    }

    val notificationManager =
      context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    if (Notifications.canShowNotification) {
      val dumpingHeap = context.getString(R.string.leak_canary_notification_dumping)
      val builder = Notification.Builder(context)
        .setContentTitle(dumpingHeap)
      val notification = Notifications.buildNotification(context, builder, LEAKCANARY_LOW)
      notificationManager.notify(R.id.leak_canary_notification_dumping_heap, notification)
    }

    val toast = waitingForToast.get()

    return try {
      val durationMillis = measureDurationMillis {
        Debug.dumpHprofData(heapDumpFile.absolutePath)
      }
      if (heapDumpFile.length() == 0L) {
        SharkLog.d { "Dumped heap file is 0 byte length" }
        NoHeapDump
      } else {
        HeapDump(file = heapDumpFile, durationMillis = durationMillis)
      }
    } catch (e: Exception) {
      SharkLog.d(e) { "Could not dump heap" }
      // Abort heap dump
      NoHeapDump
    } finally {
      cancelToast(toast)
      notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
    }
  }
  ...
}
  • 其中使用的是 android.os.Debug 类的 Debug.dumpHprofData(heapDumpFile.absolutePath),导出一个Hprof文件;
  • AndroidHeapDumper的dumpHeap方法会在HeapDumpTrigger.dumpHeap中作为when语句块的条件调用,代码如下
private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is NoHeapDump -> {
        if (retry) {
          SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
          scheduleRetainedObjectCheck(
            delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
          )
        } else {
          SharkLog.d { "Failed to dump heap, will not automatically retry" }
        }
        showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
            R.string.leak_canary_notification_retained_dump_failed
          )
        )
      }
      is HeapDump -> {
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
        )
      }
    }
}
  • 如果AndroidHeapDumper.dumpHeap的结果是HeapDump,会将结果转交给HeapAnalyzerService完成结果展示

HeapAnalyzerService

  • 那么来看一下HeapAnalyzerService.runAnalysis的实现,是启动了一个前台的service
fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null,
      heapDumpReason: String = "Unknown"
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
      heapDumpDurationMillis?.let {
        intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
      }
      startForegroundService(context, intent)
}
  • HeapAnalyzerService继承了ForegroundService,ForegroundService又继承自IntentService,既然归根结底是IntentService,那么就要看看onHandleIntent方法是如何处理任务的
override fun onHandleIntent(intent: Intent?) {
    onHandleIntentInForeground(intent)
}
  • ForegroundService.onHandleIntent中调用了抽象方法onHandleIntentInForeground,而HeapAnalyzerService中提供了具体的实现,代码如下
 override fun onHandleIntentInForeground(intent: Intent?) {
    if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
      SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
      return
    }

    // Since we're running in the main process we should be careful not to impact it.
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
    val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    val heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA)
    val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)

    val config = LeakCanary.config
    val heapAnalysis = if (heapDumpFile.exists()) {
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    val fullHeapAnalysis = when (heapAnalysis) {
      is HeapAnalysisSuccess -> heapAnalysis.copy(
        dumpDurationMillis = heapDumpDurationMillis,
        metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
      )
      is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
  • 其中调用了analyzeHeap方法,其中HeapAnalyzer使用Shark来解析heapDumpFile
private fun analyzeHeap(
    heapDumpFile: File,
    config: Config
  ): HeapAnalysis {
    val heapAnalyzer = HeapAnalyzer(this)

    val proguardMappingReader = try {
      ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
    } catch (e: IOException) {
      null
    }
    return heapAnalyzer.analyze(
      heapDumpFile = heapDumpFile,
      leakingObjectFinder = config.leakingObjectFinder,
      referenceMatchers = config.referenceMatchers,
      computeRetainedHeapSize = config.computeRetainedHeapSize,
      objectInspectors = config.objectInspectors,
      metadataExtractor = config.metadataExtractor,
      proguardMapping = proguardMappingReader?.readProguardMapping()
    )
}
  • onHandleIntentInForeground方法的最后通过config.onHeapAnalyzedListener .onHeapAnalyzed(fullHeapAnalysis)触发相关通知显示以及点击后的 LeakActivity 数据展示。
//LeakCanary.Config
val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),

//DefaultOnHeapAnalyzedListener
companion object {
    fun create(): OnHeapAnalyzedListener =
      DefaultOnHeapAnalyzedListener { InternalLeakCanary.application }
}

override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }

    val db = LeaksDbHelper(application).writableDatabase
    val id = HeapAnalysisTable.insert(db, heapAnalysis)
    db.releaseReference()

    val (contentTitle, screenToShow) = when (heapAnalysis) {
      is HeapAnalysisFailure -> application.getString(
        R.string.leak_canary_analysis_failed
      ) to HeapAnalysisFailureScreen(id)
      is HeapAnalysisSuccess -> {
        val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
        val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
        application.getString(
          R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
        ) to HeapDumpScreen(id)
      }
    }

    if (InternalLeakCanary.formFactor == TV) {
      showToast(heapAnalysis)
      printIntentInfo()
    } else {
      showNotification(screenToShow, contentTitle)
    }
}

private fun showNotification(
    screenToShow: Screen,
    contentTitle: String
) {
    val pendingIntent = LeakActivity.createPendingIntent(
      application, arrayListOf(HeapDumpsScreen(), screenToShow)
    )

    val contentText = application.getString(R.string.leak_canary_notification_message)

    Notifications.showNotification(
      application, contentTitle, contentText, pendingIntent,
      R.id.leak_canary_notification_analysis_result,
      LEAKCANARY_MAX
    )
}

参考

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章

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

推荐阅读更多精彩内容