LeakCanary可能被你忽略的点

说起leakcanary大家应该都很熟悉,问起原理应该都知道在对象被销毁时通过WeakReference+ReferenceQueue检测对象是否被回收,延迟二次检测后还没被回收则认为是嫌疑对象,然后dump heap并对其进行分析...

但是你知道leakcanary可以检测哪些对象吗?又是如何获取这些即将销毁的对象呢?

先说问题1的结论:

leakcanary2.6版本之前只能对Activity,Fragment进行监控。

leakcanary2.6版本以后增加了对ViewModel,RootView,Service的监控。

至于如何检测这些对象的销毁时机,下面以leakcanary-android:2.7代码为例做简单的探讨。

1,初始化

众所周知,leakcanary从2.0版本开始就不需要手动初始化了,其主要是通过ContentProvider来实现免初始化:

<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()中进行了具体的初始化工作:

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并调用了每个item的install(),那么watchersToInstall是什么呢?看它的默认实现appDefaultWatchers(application)

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

这里就是返回了一个包含四个Watcher组成的List,分别对Activity,Fragment,ViewModel,RootView,Service的销毁进行监控,拿到即将销毁的对象通过WeakReference和ReferenceQueue方式进行内存泄漏的初步判断,最后Dump HeapProfile进行具体分析。

下面就看看这些Watcher是如何实现监控对象销毁过程的。

2,ActivityWatcher

ActivityWatcher非常简单,通过Application注册Activity的生命周期回调,来监控每一个Activity的销毁,在Activity销毁时通过reachabilityWatcher将当前Activity对象添加到监控队列,然后进行具体分析。

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

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

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

3,FragmentAndViewModelWatcher

这个Watcher实现了对Fragment和ViewModel的销毁监控,首先看一下对Fragment的销毁监控:

3.1 监控Fragment销毁

Fragment有三种:framework自带的,supportv4包中的和androidx中的。因此需要对这三种情况分别处理,不过思路都是一样的,差别就在于导包。那么就看一下framework自带的Fragment如何监控。

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时调用fragmentDestroyWatchers中的每个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加入监控队列
        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加入监控队列
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

  override fun invoke(activity: Activity) {
    val fragmentManager = activity.fragmentManager
    // 通过fragmentManager注册Fragment的生命周期回调
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
  }
}

使用的高阶函数,直接看invoke(),在其中获取Activity的fragmentManager,并注册Fragment的生命周期回调。

在回调onFragmentViewDestroyed中获取Fragment中的view,将view加入监控队列。

在回调onFragmentDestroyed中将Fragment加入监控队列。

3.2 监控ViewModel销毁

在对AndroidX中Fragment监控时实现了对ViewModel的监控,因为只有AndroidX中才提供了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加入监控队列
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }
    ...
  }

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      // 注册Fragment生命周期回调
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // 将activity中的ViewModel加入监控队列
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

ViewModel存在于Activity和Fragment中,因此需要对两者的ViewModel进行监控,首先在invoke()中注册了Fragment生命周期回调,并在回调的onFragmentCreated通过ViewModelClearedWatcher对该Fragment中的ViewModel进行监控,然后直接通过ViewModelClearedWatcher对当前Activity进行了监控。

接着看一下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实例中的mMap对象
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null
    }
  }

  override fun onCleared() {
    // 遍历mMap对象将其中的每个ViewModel对象加入监控队列
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      // 创建ViewModelProvider并设置一个Factory,
      val provider = ViewModelProvider(storeOwner, object : Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          // 创建ViewModelClearedWatcher并传递参数
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}

注意ViewModelClearedWatcher是一个ViewModel,在install()中创建了ViewModelClearedWatcher的实例,在其初始化时反射获取当前ViewModelStoreOwner对象的用于保存ViewModel的mMap对象,然后在其onCleared时遍历mMap将其中的每个ViewModel对象加入监控队列。

3.3 小结:

1,对于Fragment的监控分了三种情况,分别是framework自带的,supportv4包中的和androidx中的。

2,Fragment的监控是在Activity创建时,获取当前Activity的fragmentManager,通过fragmentManager添加Fragment的生命周期回调,在回调中分别将Fragment对象以及其中的View添加到监控队列。

3,对于ViewModel的监控需要对Activity和Fragment中的ViewModel分别进行。

4,通过当前ViewModelStoreOwner实例创建ViewModel对象,则该ViewModel对象会跟随ViewModelStoreOwner实例一起销毁。

5,通过反射获取当前ViewModelStoreOwner中用于存放ViewModel的集合mMap,在4中创建的ViewModel销毁时遍历该mMap,将其每个对象都添加到监控队列中。

4,RootViewWatcher

RootViewWatcher监测的是DecorView,在Activity,Dialog,ToolTip和Toast等创建过程中都涉及到DecorView的创建,那怎么获取到这玩意的添加和销毁呢?

熟悉View相关流程的应该知道,在ActivityThread中执行完Activity的onResume后会将其DecorView添加到WindowManagerGlobal的一个集合中,可以通过反射获取到这个集合,对这个集合进行代理即可监听DecorView的添加和删除,给DecorView设置AttachStateChangeListener即可监听DecorView的AttachedDetached状态。

LeakCanary的代码过于复杂,下面用简单的代码实现其大体流程:

class RootViewSpy {

    fun install() {
        val windowManagerClass = Class.forName("android.view.WindowManagerGlobal")
        // 1,反射获取WindowManagerGlobal实例对象
        val windowManagerInstance = windowManagerClass.getMethod("getInstance").invoke(null)

        // 2,反射获取WindowManagerGlobal实例对象中的mViews集合
        val mViewsField =
            windowManagerClass.getDeclaredField("mViews").apply { isAccessible = true }
        val mViews = mViewsField.get(windowManagerInstance) as ArrayList<View>

        // 3,将mViews中的内容存入代理集合中
        delegatingViewList.apply { addAll(mViews) }
        // 4,用代理集合替换原始mViews对象
        mViewsField.set(windowManagerInstance, delegatingViewList)
    }

    // 代理对象
    private val delegatingViewList = object : ArrayList<View>() {
        override fun add(rootView: View): Boolean {
            // 给DecorView添加AttachStateChange状态监听
            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进行动态代理也可以监听DecorView的添加和删除

小结

1,反射获取WindowManagerGlobal中用于存DecorView的集合

2,对这个集合进行代理,监控DecorView的添加过程

3,对每个DecorView添加AttachStateChangeListener,监测其AttachedDetached过程

4,由于前面已经有了Activity的检测,RootViewWatcher主要会对Toast,ToolTip及Dialog(默认不检测)的RootView进行监测

5,ServiceWatcher

监控服务的销毁需要对Service的Stop流程有所了解,Service的Stop有三种情况:Activity中stopService(),Service中stopSelf()和unBindService()。这三种情况通过不同方式进入AMS,但最终都会通过Binder方式调用ApplicationThread的scheduleStopService()方法,ApplicationThread会通过handler发送消息给ActivityThread;ActivityThread中执行Service的Stop相关逻辑,最后ActivityThread会通知AMS做最后收尾工作。大体流程如下:

process.png

要监听Service的Stop有两个点:

  1. 在ActivityThread收到ApplicationThread消息时也就是上图中的Hook点1,通过给ActivityThread中的Handler对象添加callback来实现,此时Service并没有开始执行stop相关操作,因此可以获取其实例。

  2. 在ActivityThread中执行完Service的Stop后会通过binder调用通知AMS完成最后的工作,可以通过Hook AMS来监听到。此时服务已经执行了onDestory(),可能无法在获取到其实例了,因此需要在hook点1处保存service实例,然后在此处获取实例。

LeakCanary在Hook点1处获取即将stop的Service的实例,并通过弱引用保存,然后在Hook点2处获取实例的弱引用,进而对其监听。

代码使用了高阶函数,大体如下:

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

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

    // 反射获取ActivityThread实例
    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 ->
                // 创建一个Handler的Callback对象
                Handler.Callback { msg ->
                    if (msg.what == STOP_SERVICE) {
                        val key = msg.obj as IBinder
                        // 根据key(token)从mService中获取Service对象
                        activityThreadServices[key]?.let {
                            onServicePreDestroy(key, it)
                        }
                    }
                    mCallback?.handleMessage(msg) ?: false
                }
            }
            swapActivityManager { activityManagerInterface, activityManagerInstance ->
                // 动态代理
                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)) {
                            // 当调用AMS的serviceDoneExecuting方法时执行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")
        // 用动态代理对象替换原对象
        mInstanceField[activityManagerSingletonInstance] =
            swap(iActivityManagerInterface, activityManagerInstance!!)
    }

    companion object {
        private const val STOP_SERVICE = 116

        private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
    }
}

有一点需要注意,两个Hook点的方法参数都是IBinder类型的token,那如何根据token获取其实例呢?查看ActivityThread代码:

public final class ActivityThread {
    ...
    // 保存所有Service实例及其对应IBinder
    final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
    ...
    // ActivityThread的当前实例
    private static ActivityThread sCurrentActivityThread;
    ...
}

其中mServices保存所有Service实例及其对应IBinder,sCurrentActivityThread是ActivityThread的当前实例并且是静态的,因此可以直接反射获取sCurrentActivityThread实例,然后可以反射获取mServices对象,根据Hook点的token参数即可获取Service实例。

小结
  1. 反射获取ActivityThread中的mServices实例,方便后面获取每个Service的实例
  2. 获取ActivityThread中的handler,给这个handler添加一个callback,即可实现对这个handler的hook
  3. 在第2步中,通过token参数从mServices中获取即将stop的Service的实例,然后通过弱引用保存
  4. Hook AMS,在ActivityThread执行完Service的stop操作后通知AMS时从2中的弱引用集合中获取Service的引用,将其加入监控队列
  5. 关于Hook AMS过程这里不做详细介绍,可以参考https://juejin.cn/post/7006951885089292296

6,最后

以上已经获取到了检测对象销毁的时机,接下来就是判断这些被销毁的对象是否发生了泄漏。至于如何dump heap,如何分析,如何通知用户等不是本文的重点,相关的文章也挺多的,这里不再具体分析。

LeakCanary虽然是傻瓜式的工具,可以很方便的帮我们检测内存泄漏问题,但是要想使用好他就要知道它可以检测哪些对象的泄漏。

大家都觉得反射、hook是洪水猛兽,但LeakCanary中却各种反射、各种hook,了解了这些思路方案对于解决一些特殊问题还是很有帮助的。

版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。
本文链接:[https://www.jianshu.com/p…)

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

推荐阅读更多精彩内容