Android LeakCanary源碼分析

1. LeakCanary的介紹和使用

1.1 簡(jiǎn)介

  • LeakCanray是Square開(kāi)源的Java內(nèi)存泄漏分析工具菩貌,用于在開(kāi)發(fā)階段檢測(cè)Android應(yīng)用中常見(jiàn)中的內(nèi)存泄漏金闽。
  • 它支持以下五種Android場(chǎng)景中的內(nèi)存泄漏監(jiān)測(cè):
    1. 已銷(xiāo)毀的 Activity 對(duì)象(進(jìn)入 DESTROYED 狀態(tài))
    2. 已銷(xiāo)毀的 Fragment 對(duì)象和 Fragment View 對(duì)象(進(jìn)入 DESTROYED 狀態(tài))
    3. 已清除的的 ViewModel 對(duì)象(進(jìn)入 CLEARED 狀態(tài))
    4. 已銷(xiāo)毀的的 Service 對(duì)象(進(jìn)入 DESTROYED 狀態(tài))
    5. 已從 WindowManager 中移除的 RootView 對(duì)象

1.2 使用

  • 只需要在 build.gradle 中添加 LeakCanary 依賴(lài)即可(LeakCanary內(nèi)部默認(rèn)使用了ContentProvider實(shí)現(xiàn)無(wú)侵入初始化)。注意:LeakCanary 是只在開(kāi)發(fā)環(huán)境使用的工具息拜,所以記得使用 debugImplementation 來(lái)配置依賴(lài)显设。
// 普通ContentProvider方式
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'

// 繼承startup的方式剪返,與上面二選一扁誓,由于是開(kāi)發(fā)環(huán)境,所以意義不大
// debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.12'

// 可選,使用WorkerManager多進(jìn)程分析堆快照提升分析速度
debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.12'

2. 源碼分析

2.1 初始化

  • 1.x 時(shí)期的 LeakCanary 是需要在 Application.onCreate()中加入初始化代碼的尿瞭,2.x之后LeakCanary借助了ContentProvider 的啟動(dòng)機(jī)制來(lái)間接調(diào)用初始化 API闽烙, 實(shí)現(xiàn)了無(wú)侵入的 LeakCanary 初始化。
  • 具體的位置是在 leakcanary-object-watcher-android 這個(gè)子module中
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher"
    >
 <application>
   <!--可以通過(guò)配置leak_canary_watcher_auto_install變量設(shè)置是否自行初始化声搁,默認(rèn)為true -->
   <provider
      android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
      android:authorities="${applicationId}.leakcanary-installer"
      android:enabled="@bool/leak_canary_watcher_auto_install"
      android:exported="false"/>
 </application>

internal class MainProcessAppWatcherInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // LeakCanary初始化代碼
    AppWatcher.manualInstall(application)
    return true
  }
  ...
}

  • 真正的初始化代碼為AppWatcher.manualInstall(application)
// AppWatcher.kt
/** LeakCanary初始化 */
@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), // 默認(rèn)5s后進(jìn)行泄漏檢測(cè)
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  checkMainThread()
  ...
  // 初始化 InternalLeakCanary 內(nèi)部引擎
  LeakCanaryDelegate.loadLeakCanary(application)
  // 遍歷五種監(jiān)聽(tīng)器進(jìn)行分別注冊(cè)
  watchersToInstall.forEach {
    it.install()
  }
  ...
}

/** 創(chuàng)建監(jiān)聽(tīng)集合 */
fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    // 對(duì)應(yīng)5中Android泄漏場(chǎng)景(Activity黑竞、Fragment和ViewModel、View疏旨、Service)
    // 傳入的reachabilityWatcher均為ReachabilityWatcher的唯一實(shí)現(xiàn)類(lèi)ObjectWatcher
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

  • 分為四個(gè)監(jiān)控類(lèi)分別對(duì)Activity很魂、Fragment和ViewModel、RootView以及Service分別監(jiān)控檐涝,接下來(lái)分析下這四個(gè)類(lèi)是如何具體監(jiān)控的

2.2 四大監(jiān)控類(lèi)實(shí)現(xiàn)五種泄漏場(chǎng)景的監(jiān)控

2.2.1 ActivityWatcher

  • ActivityWatcher主要實(shí)現(xiàn)對(duì)Activity的監(jiān)控
/** Activity的泄漏監(jiān)聽(tīng) */
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

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

  override fun install() {
    // 注冊(cè)監(jiān)聽(tīng)的方式
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

  • Activity的監(jiān)聽(tīng)比較簡(jiǎn)單遏匆,直接使用提供的全局監(jiān)聽(tīng)的Api application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback)即可,然后在每個(gè)Activity的onDestroy中將activity交給objectWatcher去分析即可

2.2.2 FragmentAndViewModelWatcher

  • FragmentAndViewModelWatcher主要實(shí)現(xiàn)對(duì)Fragment谁榜、Fragment view以及ViewModel的監(jiān)控
/**
 * 主要負(fù)責(zé)Fragment的泄漏監(jiān)聽(tīng)(通過(guò)Fragment.onDestroy())
 * Fragment View的泄漏監(jiān)聽(tīng)(通過(guò)Fragment.onDestroyView())
 * ViewModel的泄漏監(jiān)聽(tīng)(通過(guò)ViewModel.onCleared())
 */
class FragmentAndViewModelWatcher(
    private val application: Application,
    private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

    /** Fragment監(jiān)測(cè)集合(主要包含幾種包下的Fragment) */
    // 集合的類(lèi)型是(Activity)->Unit的函數(shù)類(lèi)型
    private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
        val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

        // Android O(8.0)后使用AndroidOFragmentDestroyWatcher監(jiān)聽(tīng)
        if (SDK_INT >= O) {
            fragmentDestroyWatchers.add(
                AndroidOFragmentDestroyWatcher(reachabilityWatcher)
            )
        }

        // androidx包下的Fragment使用AndroidXFragmentDestroyWatcher監(jiān)聽(tīng)
        getWatcherIfAvailable(
            ANDROIDX_FRAGMENT_CLASS_NAME,
            ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
            reachabilityWatcher
        )?.let {
            fragmentDestroyWatchers.add(it)
        }

        // android.support.v4包下的Fragment使用AndroidSupportFragmentDestroyWatcher監(jiān)聽(tīng)
        getWatcherIfAvailable(
            ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
            ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
            reachabilityWatcher
        )?.let {
            fragmentDestroyWatchers.add(it)
        }
        fragmentDestroyWatchers
    }

    private val lifecycleCallbacks =
        object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
            override fun onActivityCreated(
                activity: Activity,
                savedInstanceState: Bundle?
            ) {
                // 監(jiān)聽(tīng)每個(gè)Activity的onActivityCreated(),再在其中進(jìn)行Fragment監(jiān)聽(tīng)
                for (watcher in fragmentDestroyWatchers) {
                    // 由于fragmentDestroyWatchers里面本身存儲(chǔ)的是一個(gè)(Activity)->Unit的函數(shù)類(lèi)型
                    // 所以這里可以直接使用watcher(activity)幅聘,直接調(diào)用,參數(shù)為activity窃植,
                    // 實(shí)際執(zhí)行的是watcher中的invoke()
                    watcher(activity)
                }
            }
        }

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

    override fun uninstall() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
    }
    ...
}

  • Fragment 與 Fragment View 監(jiān)控 主要是通過(guò) FragmentAndViewModelWatcher 實(shí)現(xiàn)帝蒿,首先是通過(guò) Application.registerActivityLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Activity.onCreate()事件,再通過(guò) FragmentManager.registerFragmentLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Fragment 的生命周期
  • 這里根據(jù)Android版本以及Fragment包不同分了三種狀態(tài)巷怜,分別是AndroidOFragmentDestroyWatcher葛超、AndroidXFragmentDestroyWatcherAndroidSupportFragmentDestroyWatcher丛版,這里以AndroidXFragmentDestroyWatcher為例
/** Androidx包下的Fragment巩掺、FragmentView以及ViewModel監(jiān)聽(tīng) */
internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      // 注冊(cè)Fragment級(jí)別的ViewModel Hook
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null) {
        // 監(jiān)聽(tīng)FragmentView.onDestroy()將Fragment.View交給ObjectWatcher分析
        reachabilityWatcher.expectWeaklyReachable(
          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
    ) {
      // 監(jiān)聽(tīng)Fragment.onDestroy()將Fragment交給ObjectWatcher分析
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

  override fun invoke(activity: Activity) {
    // 這段代碼會(huì)在Activity.onCreate()中執(zhí)行
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      // 注冊(cè)Fragment生命周期監(jiān)聽(tīng)
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // 注冊(cè)Activity級(jí)別的ViewModel Hook
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

  • 這里面還包含了ViewModel的監(jiān)聽(tīng)邏輯,分別在 Activity.onCreate() 和 Fragment.onCreate()中使用 ViewModelClearedWatcher進(jìn)行監(jiān)聽(tīng)页畦,由于系統(tǒng)并未提供ViewModel全局監(jiān)聽(tīng)的方法胖替,所以 ViewModelClearedWatcher 是通過(guò)Hook方式實(shí)現(xiàn)的,我們看下ViewModelClearedWatcher
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  // 直接通過(guò)反射獲取ViewModelStore類(lèi)中的map變量(后面改成mMap)豫缨,即可獲得作用域中的所有ViewModel對(duì)象
  private val viewModelMap: Map<String, ViewModel>? = try {
    val storeClass = ViewModelStore::class.java
    val mapField = try {
      storeClass.getDeclaredField("map")
    } catch (exception: NoSuchFieldException) {
      storeClass.getDeclaredField("mMap")
    }
    mapField.isAccessible = true
    @Suppress("UNCHECKED_CAST")
    mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } catch (ignored: Exception) {
    SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
    null
  }

  override fun onCleared() {
    // 遍歷當(dāng)前作用域所有ViewModel對(duì)象
    viewModelMap?.values?.forEach { viewModel ->
      // 使用ObjectWatcher.expectWeaklyReachable
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    // 在storeOwner作用域?qū)嵗疺iewModelClearedWatcher對(duì)象
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          // 直接在storeOwner作用域?qū)嵗疺iewModelClearedWatcher對(duì)象
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}

  • 由此可見(jiàn):ViewModel的監(jiān)測(cè)是在Activity.onCreate()/Fragment.onCreate()中分別創(chuàng)建一個(gè)自定義的ViewModel(即ViewModelClearedWatcher)独令,然后監(jiān)聽(tīng)ViewModelClearedWatcher的onCleared(),通過(guò)Hook ViewModelStore的方式獲取相應(yīng)應(yīng)作用域(Activity/Fragment)中的所有ViewModel對(duì)象好芭,并最終交給ObjectWatcher進(jìn)行監(jiān)控

2.2.3 RootViewWatcher

  • RootViewWatcher是處理RootView內(nèi)存泄漏的監(jiān)聽(tīng)器燃箭,由于系統(tǒng)并未提供全局RootView的監(jiān)聽(tīng),所以LeakCannary還是通過(guò)Hook方式來(lái)處理的舍败,只不過(guò)這里沒(méi)有自行Hook而是利用了square的另一個(gè)開(kāi)源庫(kù)curtains來(lái)處理的招狸,主要通過(guò)Hook WMS 服務(wù)內(nèi)部的 WindowManagerGlobal(sdk>16)或者 WindowManagerImpl(sdk<=16)來(lái)獲取所有的RootView新增或移除的時(shí)機(jī)
/**
 * RootView泄漏監(jiān)聽(tīng)敬拓,主要利用了Curtains庫(kù)實(shí)現(xiàn)對(duì)
 */
class RootViewWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val listener = OnRootViewAddedListener { rootView ->
    // 判斷rootView的窗口類(lèi)型
    // 是否需要使用RootViewWatcher監(jiān)聽(tīng)
    val trackDetached = when(rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
          // 由于Activity已經(jīng)有ActivityWatcher監(jiān)聽(tīng),這里直接返回false,即無(wú)需通過(guò)RootViewWatcher監(jiān)聽(tīng)
          is Activity -> false
          is Dialog -> {
            // Use app context resources to avoid NotFoundException
            // https://github.com/square/leakcanary/issues/2137
            // 通過(guò)配置開(kāi)啟裙戏,默認(rèn)不開(kāi)啟
            val resources = rootView.context.applicationContext.resources
            resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
          }
          // Probably a DreamService
          // 屏保等
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      POPUP_WINDOW -> false
      // Tooltip乘凸、Toast等進(jìn)行監(jiān)聽(tīng)
      TOOLTIP, TOAST, UNKNOWN -> true
    }
    if (trackDetached) {
      // 注冊(cè)監(jiān)聽(tīng)事件
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

        val watchDetachedView = Runnable {
          // 接收發(fā)送的消息,使用ObjectWatcher對(duì)rootView進(jìn)行監(jiān)聽(tīng)
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }

        override fun onViewAttachedToWindow(v: View) {
          // 添加時(shí)移除消息
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          // 監(jiān)聽(tīng)RootView的移除事件累榜,使用Handler發(fā)送消息處理
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

  override fun install() {
    // 注冊(cè)RootView監(jiān)聽(tīng)
    Curtains.onRootViewsChangedListeners += listener
  }

  override fun uninstall() {
    Curtains.onRootViewsChangedListeners -= listener
  }
}

  • 主要是針對(duì)Dialog(有監(jiān)聽(tīng)開(kāi)關(guān)配置)营勤、DreamService、Tooltip壹罚、Toast等類(lèi)型的RootView進(jìn)行監(jiān)聽(tīng)葛作,通過(guò)調(diào)用rootView.addOnAttachStateChangeListener監(jiān)聽(tīng)onViewDetachedFromWindow方法,監(jiān)聽(tīng)RootView的移除事件猖凛,在移除事件中將RootView交給ObjectWatcher進(jìn)行監(jiān)控

2.2.4 ServiceWatcher

  • ServiceWatcher是處理Service內(nèi)存泄漏的監(jiān)聽(tīng)器赂蠢,系統(tǒng)也并未提供全局Service的移除監(jiān)聽(tīng),所以LeaakCanary也是通過(guò)Hook方式實(shí)現(xiàn)的
@SuppressLint("PrivateApi")
class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {

  private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()

  private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }

  private val activityThreadInstance by lazy {
    activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
  }

  private val activityThreadServices by lazy {
    val mServicesField =
      activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }

    @Suppress("UNCHECKED_CAST")
    mServicesField[activityThreadInstance] as Map<IBinder, Service>
  }

  private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null
  private var uninstallActivityManager: (() -> Unit)? = null

  override fun install() {
    checkMainThread()
    check(uninstallActivityThreadHandlerCallback == null) {
      "ServiceWatcher already installed"
    }
    check(uninstallActivityManager == null) {
      "ServiceWatcher already installed"
    }
    try {
      // Hook ActivityThread類(lèi)中的mH.mCallback
      swapActivityThreadHandlerCallback { mCallback ->
        uninstallActivityThreadHandlerCallback = {
          swapActivityThreadHandlerCallback {
            mCallback
          }
        }
        Handler.Callback { msg ->
          if (msg.obj !is IBinder) {
            return@Callback false
          }

          // 監(jiān)聽(tīng)Service.onStop()事件消息
          if (msg.what == STOP_SERVICE) {
            val key = msg.obj as IBinder
            // activityThreadServices是通過(guò)反射獲取的ActivityThread類(lèi)中的mServices成員變量<IBinder, Service>
            activityThreadServices[key]?.let {
              // 服務(wù)銷(xiāo)毀前的處理辨泳,這里主要是暫存
              onServicePreDestroy(key, it)
            }
          }
          // Hook后繼續(xù)執(zhí)行Framework本身的邏輯
          mCallback?.handleMessage(msg) ?: false
        }
      }

      // Hook AMS IActivityManager
      swapActivityManager { activityManagerInterface, activityManagerInstance ->
        uninstallActivityManager = {
          swapActivityManager { _, _ ->
            activityManagerInstance
          }
        }
        Proxy.newProxyInstance(
          activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
        ) { _, method, args ->
          // 代理serviceDoneExecuting()方法
          if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
            val token = args!![0] as IBinder
            if (servicesToBeDestroyed.containsKey(token)) {
              // 處理Service銷(xiāo)毀客年,主要是將service交給ObjectWatcher進(jìn)行監(jiān)控
              onServiceDestroyed(token)
            }
          }
          // 繼續(xù)執(zhí)行serviceDoneExecuting()本身的方法
          try {
            if (args == null) {
              method.invoke(activityManagerInstance)
            } else {
              method.invoke(activityManagerInstance, *args)
            }
          } catch (invocationException: InvocationTargetException) {
            throw invocationException.targetException
          }
        }
      }
    } catch (ignored: Throwable) {
      SharkLog.d(ignored) { "Could not watch destroyed services" }
    }
  }

  override fun uninstall() {
    checkMainThread()
    uninstallActivityManager?.invoke()
    uninstallActivityThreadHandlerCallback?.invoke()
    uninstallActivityManager = null
    uninstallActivityThreadHandlerCallback = null
  }

  private fun onServicePreDestroy(
    token: IBinder,
    service: Service
  ) {
    servicesToBeDestroyed[token] = WeakReference(service)
  }

  private fun onServiceDestroyed(token: IBinder) {
    servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
      serviceWeakReference.get()?.let { service ->
        reachabilityWatcher.expectWeaklyReachable(
          service, "${service::class.java.name} received Service#onDestroy() callback"
        )
      }
    }
  }

  /**
   * Hook修改ActivityThread類(lèi)中的mH.mCallback
   * swap 是一個(gè) lambda 表達(dá)式,參數(shù)為原對(duì)象漠吻,返回值為注入的新對(duì)象
   */
  private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
    val mHField =
      activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
    val mH = mHField[activityThreadInstance] as Handler

    val mCallbackField =
      Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
    val mCallback = mCallbackField[mH] as Handler.Callback?
    mCallbackField[mH] = swap(mCallback)
  }

  /**
   * Hook修改ActivityThread類(lèi)中的mH.mCallback
   * swap 是一個(gè) lambda 表達(dá)式,參數(shù)為 IActivityManager 的 Class 對(duì)象和接口原實(shí)現(xiàn)對(duì)象司恳,返回值為注入的新對(duì)象
   */
  @SuppressLint("PrivateApi")
  private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
    val singletonClass = Class.forName("android.util.Singleton")
    val mInstanceField =
      singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }

    val singletonGetMethod = singletonClass.getDeclaredMethod("get")

    val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      "android.app.ActivityManager" to "IActivityManagerSingleton"
    } else {
      "android.app.ActivityManagerNative" to "gDefault"
    }

    val activityManagerClass = Class.forName(className)
    val activityManagerSingletonField =
      activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
    val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]

    val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)

    val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
    // 將swap的返回值作為新對(duì)象途乃,實(shí)現(xiàn) Hook
    mInstanceField[activityManagerSingletonInstance] =
      swap(iActivityManagerInterface, activityManagerInstance!!)
  }

  companion object {
    private const val STOP_SERVICE = 116

    private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
  }
}

  • Hook的大致步驟為:
    • Hook主線(xiàn)程消息循環(huán)的 mH.mCallback 回調(diào),監(jiān)聽(tīng)其中的STOP_SERVICE消息扔傅,將即將 Destroy 的 Service 對(duì)象暫存起來(lái)(由于 ActivityThread.H 中沒(méi)有 DESTROY_SERVICE 消息耍共,所以不能直接監(jiān)聽(tīng)到 onDestroy() 事件,需要下面的步驟)猎塞;
    • 使用動(dòng)態(tài)代理 Hook AMS 與 App 通信的的 IActivityManager Binder 對(duì)象试读,代理其中的 serviceDoneExecuting() 方法,視為 Service.onDestroy() 的執(zhí)行時(shí)機(jī)荠耽,拿到暫存的 Service 對(duì)象交給 ObjectWatcher 監(jiān)控钩骇。

2.3 ObjectWatcher如何監(jiān)控內(nèi)存泄漏

2.3.1 Java四大引用相關(guān)知識(shí)

  • 強(qiáng)引用

    • 普通的最常見(jiàn)的引用,當(dāng)對(duì)象具有強(qiáng)引用且未被置空铝量,虛擬機(jī)即使OOM也不會(huì)回收
  • 軟引用

    • 當(dāng)內(nèi)存充足時(shí)虛擬機(jī)不會(huì)回收它倘屹,內(nèi)存不足時(shí)會(huì)自動(dòng)回收,常用于圖片緩存
    SoftReference<String> softReference = new SoftReference<>(str);
    
    
  • 弱引用

    • 無(wú)論內(nèi)存是否充足慢叨,只要手動(dòng)調(diào)用垃圾回收System.gc()或等待虛擬機(jī)自動(dòng)GC纽匙,弱引用就會(huì)被回收。主要用來(lái)放置在容易引起內(nèi)存泄漏的位置拍谐,如Android中的Handler
    WeakReference<String> weakReference = new WeakReference<>(str);
    
    
  • 虛引用

    • 虛引用并不會(huì)決定對(duì)象的生命周期烛缔。如果一個(gè)對(duì)象僅持有虛引用馏段,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收践瓷。主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)院喜,使用 PhantomReference 來(lái)創(chuàng)建
  • 引用隊(duì)列ReferenceQueue

    • 當(dāng)gc準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還僅有軟引用(或弱引用当窗,或虛引用)指向它够坐,就會(huì)在回收該對(duì)象之前,把這個(gè)軟引用(或弱引用崖面,或虛引用)加入到與之關(guān)聯(lián)的引用隊(duì)列(ReferenceQueue)中元咙。如果一個(gè)軟引用(或弱引用,或虛引用)對(duì)象本身在引用隊(duì)列中巫员,就說(shuō)明該引用對(duì)象所指向的對(duì)象被回收了;
// 構(gòu)造一個(gè)強(qiáng)引用  
Object obj = new Object();  
// 創(chuàng)建引用隊(duì)列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();  
// 利用強(qiáng)引用和引用隊(duì)列構(gòu)造弱引用  
WeakReference<Object> weakReference = new WeakReference<>(obj, referenceQueue);  
System.out.println("調(diào)用GC前弱引用:" + weakReference.get()); // java.lang.Object@6108b2d7  
System.out.println("調(diào)用GC前引用隊(duì)列:" + referenceQueue.poll()); // null  
  
// 將強(qiáng)引用手動(dòng)置null  
obj = null;  
// 手動(dòng)GC或虛擬機(jī)自動(dòng)GC都會(huì)回收弱引用庶香,這里手動(dòng)調(diào)用GC  
System.gc();  
  
System.out.println("調(diào)用GC后弱引用:" + weakReference.get()); // null  
System.out.println("調(diào)用GC后引用隊(duì)列:" + referenceQueue.poll()); // java.lang.Object@6108b2d7

2.3.2 ObjectWatcher對(duì)象監(jiān)控

class ObjectWatcher constructor(
    private val clock: Clock,
    private val checkRetainedExecutor: Executor,
    private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {

    private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

    /** 被監(jiān)控對(duì)象的映射表 */
    private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

    /** 弱引用引用隊(duì)列,與KeyedWeakReference相關(guān)聯(lián)简识,對(duì)象正常銷(xiāo)毀會(huì)存在這里面 */
    private val queue = ReferenceQueue<Any>()

    ...
    /** 監(jiān)控對(duì)象泄漏 */
    @Synchronized
    override fun expectWeaklyReachable(
        watchedObject: Any,
        description: String
    ) {
        if (!isEnabled()) {
            return
        }
        // 移除watchedObjects中未泄漏的對(duì)象
        removeWeaklyReachableObjects()
        // 構(gòu)造KeyedWeakReference 引用對(duì)象
        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
        // 默認(rèn)5秒后執(zhí)行檢查
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }
    ...

    @Synchronized
    private fun moveToRetained(key: String) {
        removeWeaklyReachableObjects()
        // 移除watchedObjects中未泄露的對(duì)象后剩余的判定為發(fā)生泄漏
        val retainedRef = watchedObjects[key]
        if (retainedRef != null) {
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            // 回調(diào)通知LeakCanary內(nèi)部處理
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    /** 移除隊(duì)列中未泄漏的對(duì)象 */
    private fun removeWeaklyReachableObjects() {
        var ref: KeyedWeakReference?
        do {
            ref = queue.poll() as KeyedWeakReference?
            if (ref != null) {
                watchedObjects.remove(ref.key)
            }
        } while (ref != null)
    }
}

/** 弱引用包裝類(lèi) */
class KeyedWeakReference(
  /** 被監(jiān)控對(duì)象 */
  referent: Any,
  /** 映射表的Key */
  val key: String,
  /** 描述 */
  val description: String,
  /** 監(jiān)控開(kāi)始時(shí)間(引用創(chuàng)建時(shí)間) */
  val watchUptimeMillis: Long,
  /** 關(guān)聯(lián)的引用隊(duì)列 */
  referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
  referent, referenceQueue
) {
  /** 判定對(duì)象為泄漏對(duì)象的時(shí)間赶掖,-1表示非泄漏對(duì)象或還未判定完畢 */
  @Volatile
  var retainedUptimeMillis = -1L

  override fun clear() {
    super.clear()
    retainedUptimeMillis = -1L
  }

  companion object {
    /** 記錄最近一次觸發(fā)Heap Dump的時(shí)間 */
    @Volatile
    @JvmStatic var heapDumpUptimeMillis = 0L
  }
}

  • 主要經(jīng)歷了三個(gè)步驟
  1. 為被監(jiān)控對(duì)象 watchedObject 創(chuàng)建一個(gè) KeyedWeakReference 弱引用,并存儲(chǔ)到 <UUID, KeyedWeakReference> 的映射表中七扰;
  2. postDelay 五秒后檢查引用對(duì)象是否出現(xiàn)在引用隊(duì)列中奢赂,出現(xiàn)在隊(duì)列則說(shuō)明被監(jiān)控對(duì)象未發(fā)生泄漏。隨后颈走,移除映射表中未泄露的記錄膳灶,更新泄漏的引用對(duì)象的 retainedUptimeMillis 字段以標(biāo)記為泄漏
  3. 通過(guò)回調(diào) onObjectRetained 告知 LeakCanary 內(nèi)部發(fā)生新的內(nèi)存泄漏

2.4 Dump heap獲取內(nèi)存快照文件

  • ObjectWatcher 判定被監(jiān)控對(duì)象發(fā)生泄漏后,會(huì)通過(guò)接口方法 OnObjectRetainedListener.onObjectRetained() 回調(diào)到 LeakCanary 內(nèi)部的管理器 InternalLeakCanary
  • 由于分析工作比較耗時(shí)間立由,LeakCanary自然不會(huì)每次發(fā)現(xiàn)內(nèi)存泄漏對(duì)象都進(jìn)行分析工作轧钓,而會(huì)進(jìn)行兩個(gè)攔截:
  • 1. 泄漏對(duì)象計(jì)數(shù)未達(dá)到閾值,或者進(jìn)入后臺(tái)時(shí)間未達(dá)到閾值
  • 2. 計(jì)算距離上一次 HeapDump 未超過(guò) 60S
// InternalLeakCanary.kt
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}

// HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  // 避免重復(fù)postDelayed
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    checkRetainedObjects()
  }, delayMillis)
}

private fun checkRetainedObjects() {
  val iCanHasHeap = HeapDumpControl.iCanHasHeap()

  val config = configProvider()

  if (iCanHasHeap is Nope) {
    if (iCanHasHeap is NotifyingNope) {
      // Before notifying that we can't dump heap, let's check if we still have retained object.
      // 泄漏計(jì)數(shù)
      var retainedReferenceCount = objectWatcher.retainedObjectCount
      // 泄漏計(jì)數(shù)>0時(shí)主動(dòng)調(diào)用GC
      if (retainedReferenceCount > 0) {
        // 這個(gè)方法是調(diào)用RunTime.getRuntime().GC()并休眠等待100ms
        gcTrigger.runGc()
        // GC后再獲取泄漏計(jì)數(shù)
        retainedReferenceCount = objectWatcher.retainedObjectCount
      }

      val nopeReason = iCanHasHeap.reason()
      // 這里會(huì)判斷泄漏計(jì)數(shù)是否>5(默認(rèn)閾值)
      val wouldDump = !checkRetainedCount(
        retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
      )

      if (wouldDump) {
        val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
        // 回調(diào)onEvent
        onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
        // 展示通知
        showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = uppercaseReason
        )
      }
    } else {
      SharkLog.d {
        application.getString(
          R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
        )
      }
    }
    return
  }

  var retainedReferenceCount = objectWatcher.retainedObjectCount

  if (retainedReferenceCount > 0) {
    gcTrigger.runGc()
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }

  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  // 計(jì)算距離上一次HeapDump時(shí)間未超過(guò)60s會(huì)攔截
  if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
    onRetainInstanceListener.onEvent(DumpHappenedRecently)
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
    )
    scheduleRetainedObjectCheck(
      delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
    )
    return
  }

  // 移除通知
  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  // 觸發(fā)dumpHeap分析
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}

2.5 分析堆快照dumpHeap

  • LeakCanary 已經(jīng)成功生成 .hprof 堆快照文件锐膜,并且發(fā)送了一個(gè) LeakCanary 內(nèi)部事件 HeapDump毕箍。LeakCanary 的配置項(xiàng)中設(shè)置了多個(gè)事件消費(fèi)者EventListener,這三個(gè)會(huì)根據(jù) App 當(dāng)前的依賴(lài)項(xiàng)而選擇最優(yōu)的執(zhí)行策略:
  • 1 - WorkerManager 多進(jìn)程分析
  • 2 - WorkManager 異步分析
  • 3 - 異步線(xiàn)程分析(兜底策略)
// LeakCanary.kt
val eventListeners: List<EventListener> = listOf(
  LogcatEventListener,
  ToastEventListener,
  LazyForwardingEventListener {
    if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
  },
  when {
     // WorkManager多進(jìn)程分析
      RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
        RemoteWorkManagerHeapAnalyzer
      // WorkManager 異步分析
      WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
      // 異步線(xiàn)程分析(兜底策略)
      else -> BackgroundThreadHeapAnalyzer
  }
),

2.5.1 WorkerManager 多進(jìn)程分析

object RemoteWorkManagerHeapAnalyzer : EventListener {

  private const val REMOTE_SERVICE_CLASS_NAME = "leakcanary.internal.RemoteLeakCanaryWorkerService"

  // 這里通過(guò)RemoteLeakCanaryWorkerService這個(gè)類(lèi)是否加載成功來(lái)判斷
  // 是否有'com.squareup.leakcanary:leakcanary-android-process:2.9.1'這個(gè)依賴(lài)
  internal val remoteLeakCanaryServiceInClasspath by lazy {
    try {
      Class.forName(REMOTE_SERVICE_CLASS_NAME)
      true
    } catch (ignored: Throwable) {
      false
    }
  }

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      val application = InternalLeakCanary.application
      // 創(chuàng)建并分發(fā) WorkManager 多進(jìn)程請(qǐng)求
      val heapAnalysisRequest =
        OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply {
          val dataBuilder = Data.Builder()
            .putString(ARGUMENT_PACKAGE_NAME, application.packageName)
            .putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME)
          setInputData(event.asWorkerInputData(dataBuilder))
          with(WorkManagerHeapAnalyzer) {
            addExpeditedFlag()
          }
        }.build()
      SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
      val workManager = WorkManager.getInstance(application)
      workManager.enqueue(heapAnalysisRequest)
    }
  }
}

internal class RemoteHeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) :
  RemoteListenableWorker(appContext, workerParams) {

  override fun startRemoteWork(): ListenableFuture<Result> {
    val heapDump = inputData.asEvent<HeapDump>()
    val result = SettableFuture.create<Result>()
    heapAnalyzerThreadHandler.post {
      // 分析堆快照
      val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(heapDump, isCanceled = {
        result.isCancelled
      }) { progressEvent ->
        if (!result.isCancelled) {
          // 發(fā)送分析進(jìn)度事件
          InternalLeakCanary.sendEvent(progressEvent)
        }
      }
      if (result.isCancelled) {
        SharkLog.d { "Remote heap analysis for ${heapDump.file} was canceled" }
      } else {
        // 發(fā)送分析完成事件
        InternalLeakCanary.sendEvent(doneEvent)
        result.set(Result.success())
      }
    }
    return result
  }

  override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
    return applicationContext.heapAnalysisForegroundInfoAsync()
  }
}

2.5.2 WorkManager異步分析

object WorkManagerHeapAnalyzer : EventListener {

  // 判斷是否包含WorkManager
  internal val validWorkManagerInClasspath by lazy {
    try {
      Class.forName("androidx.work.WorkManager")
      val dataBuilderClass = Class.forName("androidx.work.Data$Builder")
      dataBuilderClass.declaredMethods.any { it.name == "putByteArray" }.apply {
        if (!this) {
          SharkLog.d { "Could not find androidx.work.Data$Builder.putByteArray, WorkManager should be at least 2.1.0." }
        }
      }
    } catch (ignored: Throwable) {
      false
    }
  }

  // setExpedited() requires WorkManager 2.7.0+
  private val workManagerSupportsExpeditedRequests by lazy {
    try {
      Class.forName("androidx.work.OutOfQuotaPolicy")
      true
    } catch (ignored: Throwable) {
      false
    }
  }

  internal fun OneTimeWorkRequest.Builder.addExpeditedFlag() = apply {
    if (workManagerSupportsExpeditedRequests) {
      setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    }
  }

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      val heapAnalysisRequest = OneTimeWorkRequest.Builder(HeapAnalyzerWorker::class.java).apply {
        setInputData(event.asWorkerInputData())
        addExpeditedFlag()
      }.build()
      SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
      val application = InternalLeakCanary.application
      WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
    }
  }
}

internal class HeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) :
  Worker(appContext, workerParams) {
  override fun doWork(): Result {
    // 分析堆快照
    val doneEvent =
      AndroidDebugHeapAnalyzer.runAnalysisBlocking(inputData.asEvent()) { event ->
        // 發(fā)送分析進(jìn)度事件
        InternalLeakCanary.sendEvent(event)
      }
    // 發(fā)送分析完成事件
    InternalLeakCanary.sendEvent(doneEvent)
    return Result.success()
  }
 ...
}

2.5.3 普通異步線(xiàn)程分析BackgroundThreadHeapAnalyzer

object BackgroundThreadHeapAnalyzer : EventListener {

  internal val heapAnalyzerThreadHandler by lazy {
    val handlerThread = HandlerThread("HeapAnalyzer")
    handlerThread.start()
    Handler(handlerThread.looper)
  }

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      heapAnalyzerThreadHandler.post {
        // 分析堆快照
        val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
          // 發(fā)送分析進(jìn)度事件
          InternalLeakCanary.sendEvent(event)
        }
        // 發(fā)送分析完成事件
        InternalLeakCanary.sendEvent(doneEvent)
      }
    }
  }
}

  • 可以看到三種方式中最終LeakCanary 是通過(guò)子線(xiàn)程或者子進(jìn)程執(zhí)行 AndroidDebugHeapAnalyzer.runAnalysisBlocking 方法來(lái)分析堆快照的道盏,并在分析過(guò)程中和分析完成后發(fā)送回調(diào)事件而柑。
  • 堆快照分析最終是交給 Shark 中的 HeapAnalizer 完成的,這里就不再展開(kāi)分析了捞奕,核心流程是:
  • 1牺堰、在堆快照中尋找泄漏對(duì)象,默認(rèn)是尋找 KeyedWeakReference 類(lèi)型對(duì)象颅围;
  • 2伟葫、分析 KeyedWeakReference 對(duì)象的最短引用鏈,并按照引用鏈簽名分組院促,按照 Application Leaks 和 Library Leaks 分類(lèi)筏养;
  • 3斧抱、返回分析完成事件。

3. 總結(jié)

  1. 利用ContentProvider進(jìn)行初始化
  2. 注冊(cè) 5 種 Android 泄漏場(chǎng)景的監(jiān)控
    • Activity是利用application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback)監(jiān)聽(tīng)全局Activity生命周期
    • Fragment首先是通過(guò) Application.registerActivityLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Activity.onCreate()事件渐溶,再通過(guò) FragmentManager.registerFragmentLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Fragment 的生命周期
    • ViewModel是通過(guò)Hook ViewModelStore的方式實(shí)現(xiàn)的
    • Service是通過(guò)Hook mH.mCallback 回調(diào)辉浦,監(jiān)聽(tīng)其中的STOP_SERVICE消息來(lái)實(shí)現(xiàn)的
    • RootView是通過(guò)借助curtains庫(kù)Hook WMS 服務(wù)內(nèi)部的 WindowManagerGlobal實(shí)現(xiàn)
  3. 收到銷(xiāo)毀回調(diào)后,根據(jù)要回收對(duì)象創(chuàng)建 KeyedWeakReference 并關(guān)聯(lián) ReferenceQueue茎辐,延遲 5 秒檢查相關(guān)對(duì)象是否被回收宪郊,如果未被回收則開(kāi)啟服務(wù),dump heap 獲取內(nèi)存快照.hprof文件
  4. 通過(guò) Shark 庫(kù)解析.hprof 文件拖陆,獲取泄漏對(duì)象弛槐,計(jì)算泄漏對(duì)象到 GC roots 的最短路徑
  5. 合并多個(gè)泄漏路徑并輸出分析結(jié)果,將結(jié)果展示到可視化界面
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末依啰,一起剝皮案震驚了整個(gè)濱河市乎串,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌速警,老刑警劉巖叹誉,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闷旧,居然都是意外死亡长豁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)忙灼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蕉斜,“玉大人,你說(shuō)我怎么就攤上這事缀棍。” “怎么了机错?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵爬范,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我弱匪,道長(zhǎng)青瀑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任萧诫,我火速辦了婚禮斥难,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帘饶。我一直安慰自己哑诊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布及刻。 她就那樣靜靜地躺著镀裤,像睡著了一般竞阐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暑劝,一...
    開(kāi)封第一講書(shū)人閱讀 49,802評(píng)論 1 290
  • 那天骆莹,我揣著相機(jī)與錄音,去河邊找鬼担猛。 笑死幕垦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的傅联。 我是一名探鬼主播先改,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纺且!你這毒婦竟也來(lái)了盏道?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤载碌,失蹤者是張志新(化名)和其女友劉穎猜嘱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體嫁艇,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朗伶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了步咪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片论皆。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖猾漫,靈堂內(nèi)的尸體忽然破棺而出点晴,到底是詐尸還是另有隱情,我是刑警寧澤悯周,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布粒督,位于F島的核電站,受9級(jí)特大地震影響禽翼,放射性物質(zhì)發(fā)生泄漏屠橄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一闰挡、第九天 我趴在偏房一處隱蔽的房頂上張望锐墙。 院中可真熱鬧,春花似錦长酗、人聲如沸溪北。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刻盐。三九已至掏膏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敦锌,已是汗流浹背馒疹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乙墙,地道東北人颖变。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像听想,于是被迫代替她去往敵國(guó)和親腥刹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

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