LeakCanary可能被你忽略的點(diǎn)

說起leakcanary大家應(yīng)該都很熟悉棺聊,問起原理應(yīng)該都知道在對象被銷毀時(shí)通過WeakReference+ReferenceQueue檢測對象是否被回收克伊,延遲二次檢測后還沒被回收則認(rèn)為是嫌疑對象训柴,然后dump heap并對其進(jìn)行分析...

但是你知道leakcanary可以檢測哪些對象嗎揽涮?又是如何獲取這些即將銷毀的對象呢?

先說問題1的結(jié)論:

leakcanary2.6版本之前只能對Activity,F(xiàn)ragment進(jìn)行監(jiān)控桦卒。

leakcanary2.6版本以后增加了對ViewModel,RootView匿又,Service的監(jiān)控方灾。

至于如何檢測這些對象的銷毀時(shí)機(jī),下面以leakcanary-android:2.7代碼為例做簡單的探討碌更。

1迎吵,初始化

眾所周知,leakcanary從2.0版本開始就不需要手動初始化了针贬,其主要是通過ContentProvider來實(shí)現(xiàn)免初始化:

<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>

在其onCreate()中進(jìn)行了具體的初始化工作:

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

接著看AppWatcher.manualInstall()中做了什么:

fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    ...
    watchersToInstall.forEach {
      it.install()
    }
}

刪繁就簡击费,這里主要是遍歷了watchersToInstall并調(diào)用了每個(gè)item的install(),那么watchersToInstall是什么呢桦他?看它的默認(rèn)實(shí)現(xiàn)appDefaultWatchers(application)

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
        // 監(jiān)控Activity
        ActivityWatcher(application, reachabilityWatcher),
        // 監(jiān)控Fragment和ViewModel
        FragmentAndViewModelWatcher(application, reachabilityWatcher),
        // 監(jiān)控RootView
        RootViewWatcher(reachabilityWatcher),
        // 監(jiān)控Service
        ServiceWatcher(reachabilityWatcher)
    )
}

這里就是返回了一個(gè)包含四個(gè)Watcher組成的List蔫巩,分別對Activity,F(xiàn)ragment快压,ViewModel圆仔,RootView,Service的銷毀進(jìn)行監(jiān)控蔫劣,拿到即將銷毀的對象通過WeakReference和ReferenceQueue方式進(jìn)行內(nèi)存泄漏的初步判斷坪郭,最后Dump HeapProfile進(jìn)行具體分析。

下面就看看這些Watcher是如何實(shí)現(xiàn)監(jiān)控對象銷毀過程的脉幢。

2歪沃,ActivityWatcher

ActivityWatcher非常簡單,通過Application注冊Activity的生命周期回調(diào)嫌松,來監(jiān)控每一個(gè)Activity的銷毀沪曙,在Activity銷毀時(shí)通過reachabilityWatcher將當(dāng)前Activity對象添加到監(jiān)控隊(duì)列,然后進(jìn)行具體分析萎羔。

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

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

  override fun install() {
      // 注冊Activity的生命周期回調(diào)
      application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

3液走,F(xiàn)ragmentAndViewModelWatcher

這個(gè)Watcher實(shí)現(xiàn)了對Fragment和ViewModel的銷毀監(jiān)控,首先看一下對Fragment的銷毀監(jiān)控:

3.1 監(jiān)控Fragment銷毀

Fragment有三種:framework自帶的贾陷,supportv4包中的和androidx中的缘眶。因此需要對這三種情況分別處理,不過思路都是一樣的髓废,差別就在于導(dǎo)包巷懈。那么就看一下framework自帶的Fragment如何監(jiān)控。

class FragmentAndViewModelWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
    // 添加對三種Fragment處理的Watcher
    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
        AndroidOFragmentDestroyWatcher(reachabilityWatcher)
      )
    }
    ...
    fragmentDestroyWatchers
  }

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        // 在ActivityCreate時(shí)調(diào)用fragmentDestroyWatchers中的每個(gè)Watcher
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    }

  override fun install() {
    // 注冊Activity的生命周期
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

}

接著看一下AndroidOFragmentDestroyWatcher中如何處理framework自帶的Fragment瓦哎。

internal class AndroidOFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null) {
        // 將Fragment中的view加入監(jiān)控隊(duì)列
        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
    ) {
      // 將Fragment加入監(jiān)控隊(duì)列
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

  override fun invoke(activity: Activity) {
    val fragmentManager = activity.fragmentManager
    // 通過fragmentManager注冊Fragment的生命周期回調(diào)
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
  }
}

使用的高階函數(shù)砸喻,直接看invoke()柔逼,在其中獲取Activity的fragmentManager,并注冊Fragment的生命周期回調(diào)割岛。

在回調(diào)onFragmentViewDestroyed中獲取Fragment中的view愉适,將view加入監(jiān)控隊(duì)列。

在回調(diào)onFragmentDestroyed中將Fragment加入監(jiān)控隊(duì)列癣漆。

3.2 監(jiān)控ViewModel銷毀

在對AndroidX中Fragment監(jiān)控時(shí)實(shí)現(xiàn)了對ViewModel的監(jiān)控维咸,因?yàn)橹挥蠥ndroidX中才提供了ViewModel。代碼如下:

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

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

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      // 將Fragment中的ViewModel加入監(jiān)控隊(duì)列
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }
    ...
  }

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      // 注冊Fragment生命周期回調(diào)
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // 將activity中的ViewModel加入監(jiān)控隊(duì)列
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

ViewModel存在于Activity和Fragment中惠爽,因此需要對兩者的ViewModel進(jìn)行監(jiān)控癌蓖,首先在invoke()中注冊了Fragment生命周期回調(diào),并在回調(diào)的onFragmentCreated通過ViewModelClearedWatcher對該Fragment中的ViewModel進(jìn)行監(jiān)控婚肆,然后直接通過ViewModelClearedWatcher對當(dāng)前Activity進(jìn)行了監(jiān)控租副。

接著看一下ViewModelClearedWatcher是如何處理的:

internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  private val viewModelMap: Map<String, ViewModel>?

  init {
    // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
    // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
    // does not have ViewModelStore#keys. All versions currently have the mMap field.
    viewModelMap = try {
      val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
      mMapField.isAccessible = true
      // 反射獲取ViewModelStore實(shí)例中的mMap對象
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null
    }
  }

  override fun onCleared() {
    // 遍歷mMap對象將其中的每個(gè)ViewModel對象加入監(jiān)控隊(duì)列
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      // 創(chuàng)建ViewModelProvider并設(shè)置一個(gè)Factory,
      val provider = ViewModelProvider(storeOwner, object : Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          // 創(chuàng)建ViewModelClearedWatcher并傳遞參數(shù)
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}

注意ViewModelClearedWatcher是一個(gè)ViewModel,在install()中創(chuàng)建了ViewModelClearedWatcher的實(shí)例较性,在其初始化時(shí)反射獲取當(dāng)前ViewModelStoreOwner對象的用于保存ViewModel的mMap對象用僧,然后在其onCleared時(shí)遍歷mMap將其中的每個(gè)ViewModel對象加入監(jiān)控隊(duì)列。

3.3 小結(jié):

1赞咙,對于Fragment的監(jiān)控分了三種情況责循,分別是framework自帶的,supportv4包中的和androidx中的攀操。

2院仿,F(xiàn)ragment的監(jiān)控是在Activity創(chuàng)建時(shí),獲取當(dāng)前Activity的fragmentManager速和,通過fragmentManager添加Fragment的生命周期回調(diào)歹垫,在回調(diào)中分別將Fragment對象以及其中的View添加到監(jiān)控隊(duì)列。

3健芭,對于ViewModel的監(jiān)控需要對Activity和Fragment中的ViewModel分別進(jìn)行县钥。

4秀姐,通過當(dāng)前ViewModelStoreOwner實(shí)例創(chuàng)建ViewModel對象慈迈,則該ViewModel對象會跟隨ViewModelStoreOwner實(shí)例一起銷毀。

5省有,通過反射獲取當(dāng)前ViewModelStoreOwner中用于存放ViewModel的集合mMap痒留,在4中創(chuàng)建的ViewModel銷毀時(shí)遍歷該mMap,將其每個(gè)對象都添加到監(jiān)控隊(duì)列中蠢沿。

4伸头,RootViewWatcher

RootViewWatcher監(jiān)測的是DecorView,在Activity舷蟀,Dialog恤磷,ToolTip和Toast等創(chuàng)建過程中都涉及到DecorView的創(chuàng)建面哼,那怎么獲取到這玩意的添加和銷毀呢?

熟悉View相關(guān)流程的應(yīng)該知道扫步,在ActivityThread中執(zhí)行完Activity的onResume后會將其DecorView添加到WindowManagerGlobal的一個(gè)集合中魔策,可以通過反射獲取到這個(gè)集合,對這個(gè)集合進(jìn)行代理即可監(jiān)聽DecorView的添加和刪除河胎,給DecorView設(shè)置AttachStateChangeListener即可監(jiān)聽DecorView的AttachedDetached狀態(tài)闯袒。

LeakCanary的代碼過于復(fù)雜,下面用簡單的代碼實(shí)現(xiàn)其大體流程:

class RootViewSpy {

    fun install() {
        val windowManagerClass = Class.forName("android.view.WindowManagerGlobal")
        // 1游岳,反射獲取WindowManagerGlobal實(shí)例對象
        val windowManagerInstance = windowManagerClass.getMethod("getInstance").invoke(null)

        // 2政敢,反射獲取WindowManagerGlobal實(shí)例對象中的mViews集合
        val mViewsField =
            windowManagerClass.getDeclaredField("mViews").apply { isAccessible = true }
        val mViews = mViewsField.get(windowManagerInstance) as ArrayList<View>

        // 3,將mViews中的內(nèi)容存入代理集合中
        delegatingViewList.apply { addAll(mViews) }
        // 4胚迫,用代理集合替換原始mViews對象
        mViewsField.set(windowManagerInstance, delegatingViewList)
    }

    // 代理對象
    private val delegatingViewList = object : ArrayList<View>() {
        override fun add(rootView: View): Boolean {
            // 給DecorView添加AttachStateChange狀態(tài)監(jiān)聽
            rootView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {

                override fun onViewAttachedToWindow(v: View) {
                    LogUtil.e("onViewAttachedToWindow")
                }

                override fun onViewDetachedFromWindow(v: View) {
                    LogUtil.e("onViewDetachedFromWindow")
                }
            })
            return super.add(rootView)
        }
    }
}

對WindowManagerImpl進(jìn)行動態(tài)代理也可以監(jiān)聽DecorView的添加和刪除

小結(jié)

1喷户,反射獲取WindowManagerGlobal中用于存DecorView的集合

2,對這個(gè)集合進(jìn)行代理访锻,監(jiān)控DecorView的添加過程

3摩骨,對每個(gè)DecorView添加AttachStateChangeListener,監(jiān)測其AttachedDetached過程

4朗若,由于前面已經(jīng)有了Activity的檢測恼五,RootViewWatcher主要會對Toast,ToolTip及Dialog(默認(rèn)不檢測)的RootView進(jìn)行監(jiān)測

5哭懈,ServiceWatcher

監(jiān)控服務(wù)的銷毀需要對Service的Stop流程有所了解灾馒,Service的Stop有三種情況:Activity中stopService(),Service中stopSelf()和unBindService()遣总。這三種情況通過不同方式進(jìn)入AMS睬罗,但最終都會通過Binder方式調(diào)用ApplicationThread的scheduleStopService()方法,ApplicationThread會通過handler發(fā)送消息給ActivityThread旭斥;ActivityThread中執(zhí)行Service的Stop相關(guān)邏輯容达,最后ActivityThread會通知AMS做最后收尾工作。大體流程如下:

process.png

要監(jiān)聽Service的Stop有兩個(gè)點(diǎn):

  1. 在ActivityThread收到ApplicationThread消息時(shí)也就是上圖中的Hook點(diǎn)1垂券,通過給ActivityThread中的Handler對象添加callback來實(shí)現(xiàn)花盐,此時(shí)Service并沒有開始執(zhí)行stop相關(guān)操作,因此可以獲取其實(shí)例菇爪。

  2. 在ActivityThread中執(zhí)行完Service的Stop后會通過binder調(diào)用通知AMS完成最后的工作算芯,可以通過Hook AMS來監(jiān)聽到。此時(shí)服務(wù)已經(jīng)執(zhí)行了onDestory()凳宙,可能無法在獲取到其實(shí)例了熙揍,因此需要在hook點(diǎn)1處保存service實(shí)例,然后在此處獲取實(shí)例氏涩。

LeakCanary在Hook點(diǎn)1處獲取即將stop的Service的實(shí)例届囚,并通過弱引用保存有梆,然后在Hook點(diǎn)2處獲取實(shí)例的弱引用,進(jìn)而對其監(jiān)聽意系。

代碼使用了高階函數(shù)淳梦,大體如下:

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

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

    // 反射獲取ActivityThread實(shí)例
    private val activityThreadInstance by lazy {
        activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
    }

    // 獲取ActivityThread中的mService對象
    private val activityThreadServices by lazy {
        val mServicesField =
            activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }

        mServicesField.get(activityThreadInstance) as Map<IBinder, Service>
    }

    fun install() {
        try {
            swapActivityThreadHandlerCallback { mCallback ->
                // 創(chuàng)建一個(gè)Handler的Callback對象
                Handler.Callback { msg ->
                    if (msg.what == STOP_SERVICE) {
                        val key = msg.obj as IBinder
                        // 根據(jù)key(token)從mService中獲取Service對象
                        activityThreadServices[key]?.let {
                            onServicePreDestroy(key, it)
                        }
                    }
                    mCallback?.handleMessage(msg) ?: false
                }
            }
            swapActivityManager { activityManagerInterface, activityManagerInstance ->
                // 動態(tài)代理
                Proxy.newProxyInstance(
                    activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
                ) { _, method, args ->
                    if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
                        val token = args!![0] as IBinder
                        if (servicesToBeDestroyed.containsKey(token)) {
                            // 當(dāng)調(diào)用AMS的serviceDoneExecuting方法時(shí)執(zhí)行onServiceDestroyed()
                            onServiceDestroyed(token)
                        }
                    }
                    try {
                        if (args == null) {
                            method.invoke(activityManagerInstance)
                        } else {
                            method.invoke(activityManagerInstance, *args)
                        }
                    } catch (invocationException: InvocationTargetException) {
                        throw invocationException.targetException
                    }
                }
            }
        } catch (ignored: Throwable) {
            LogUtil.e("Could not watch destroyed services")
        }
    }

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

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

    /**
     * hook ActivityThread的handler
     */
    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 }
        // 獲取mH的mCallback
        val mCallback = mCallbackField[mH] as Handler.Callback?
        // 替換mH的mCallback對象
        mCallbackField[mH] = swap(mCallback)
    }

    /**
     * hook ams binder proxy
     */
    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]

        // Calling get() instead of reading from the field directly to ensure the singleton is
        // created.
        // 獲取AMS的binder代理對象
        val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)
        
        val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
        // 用動態(tài)代理對象替換原對象
        mInstanceField[activityManagerSingletonInstance] =
            swap(iActivityManagerInterface, activityManagerInstance!!)
    }

    companion object {
        private const val STOP_SERVICE = 116

        private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
    }
}

有一點(diǎn)需要注意,兩個(gè)Hook點(diǎn)的方法參數(shù)都是IBinder類型的token昔字,那如何根據(jù)token獲取其實(shí)例呢爆袍?查看ActivityThread代碼:

public final class ActivityThread {
    ...
    // 保存所有Service實(shí)例及其對應(yīng)IBinder
    final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
    ...
    // ActivityThread的當(dāng)前實(shí)例
    private static ActivityThread sCurrentActivityThread;
    ...
}

其中mServices保存所有Service實(shí)例及其對應(yīng)IBinder,sCurrentActivityThread是ActivityThread的當(dāng)前實(shí)例并且是靜態(tài)的作郭,因此可以直接反射獲取sCurrentActivityThread實(shí)例陨囊,然后可以反射獲取mServices對象,根據(jù)Hook點(diǎn)的token參數(shù)即可獲取Service實(shí)例夹攒。

小結(jié)
  1. 反射獲取ActivityThread中的mServices實(shí)例蜘醋,方便后面獲取每個(gè)Service的實(shí)例
  2. 獲取ActivityThread中的handler,給這個(gè)handler添加一個(gè)callback咏尝,即可實(shí)現(xiàn)對這個(gè)handler的hook
  3. 在第2步中压语,通過token參數(shù)從mServices中獲取即將stop的Service的實(shí)例,然后通過弱引用保存
  4. Hook AMS编检,在ActivityThread執(zhí)行完Service的stop操作后通知AMS時(shí)從2中的弱引用集合中獲取Service的引用胎食,將其加入監(jiān)控隊(duì)列
  5. 關(guān)于Hook AMS過程這里不做詳細(xì)介紹,可以參考https://juejin.cn/post/7006951885089292296

6允懂,最后

以上已經(jīng)獲取到了檢測對象銷毀的時(shí)機(jī)厕怜,接下來就是判斷這些被銷毀的對象是否發(fā)生了泄漏。至于如何dump heap蕾总,如何分析粥航,如何通知用戶等不是本文的重點(diǎn),相關(guān)的文章也挺多的生百,這里不再具體分析递雀。

LeakCanary雖然是傻瓜式的工具,可以很方便的幫我們檢測內(nèi)存泄漏問題蚀浆,但是要想使用好他就要知道它可以檢測哪些對象的泄漏缀程。

大家都覺得反射、hook是洪水猛獸蜡坊,但LeakCanary中卻各種反射杠输、各種hook,了解了這些思路方案對于解決一些特殊問題還是很有幫助的秕衙。

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上原文出處鏈接和本聲明僵刮。
本文鏈接:[http://www.reibang.com/p…)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末据忘,一起剝皮案震驚了整個(gè)濱河市鹦牛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勇吊,老刑警劉巖曼追,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異汉规,居然都是意外死亡礼殊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門针史,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晶伦,“玉大人,你說我怎么就攤上這事啄枕』榕悖” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵频祝,是天一觀的道長泌参。 經(jīng)常有香客問我,道長常空,這世上最難降的妖魔是什么沽一? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮漓糙,結(jié)果婚禮上锯玛,老公的妹妹穿的比我還像新娘。我一直安慰自己兼蜈,他們只是感情好攘残,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著为狸,像睡著了一般歼郭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辐棒,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天病曾,我揣著相機(jī)與錄音,去河邊找鬼漾根。 笑死泰涂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辐怕。 我是一名探鬼主播逼蒙,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寄疏!你這毒婦竟也來了是牢?” 一聲冷哼從身側(cè)響起僵井,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驳棱,沒想到半個(gè)月后批什,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡社搅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年驻债,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片形葬。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡合呐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荷并,到底是詐尸還是另有隱情合砂,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布源织,位于F島的核電站翩伪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谈息。R本人自食惡果不足惜缘屹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侠仇。 院中可真熱鬧轻姿,春花似錦、人聲如沸逻炊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽余素。三九已至豹休,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桨吊,已是汗流浹背威根。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留视乐,地道東北人洛搀。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像佑淀,于是被迫代替她去往敵國和親留美。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

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