Android中的智能指针

指针

在传统的C++编程中,指针的使用一直是一把双刃剑。指针赋予了我们直接操作硬件地址的能力,但同时也带来了诸多问题。其中最常见的问题可以归纳为三个:

1、指针没有初始化。

2、new了对象以后没有及时delete。

3、野指针。指针指向的对象已经被delete了,但是指针并没有被置空。这种情况多发生在多个指针同时指向同一个对象,其中某个指针释放了对象的情况。

为了解决上述这些问题,Android引入了引用计数技术,只有当引用计数为0的时候,才允许并且自动释放对象。但传统的引用计数技术,对于父子循环依赖的情况是无能为力的。因此又衍生出了强弱引用计数技术。因为强弱引用计数其实包含了普通引用计数的部分,所以我们就直接介绍强弱引用计数的实现方式。

强弱引用计数

引用计数需要解决的关键点有三点:

一、如何得知一个新的引用指向了该对象,如何得知指向该对象的旧引用已经失效

二、该对象的引用计数值如何维护,存储在哪里,何时进行修改。

三、释放该对象的合适的时机是什么时候?

Android中的强弱引用计数的总体思路是比较简单的。

1、为所有的需要使用到引用计数的对象提供一个基类,这个基类中有一个内部类专门负责维护该对象的强引用计数和弱引用计数。

2、定义一个强指针类sp,重载其所有赋值相关操作,当把对象赋值给sp的时候,sp会增加该对象的引用计数,当sp不再指向该对象的时候,会在sp的析构函数中减少该对象的引用计数

3、定义弱指针类wp,和sp类似,唯一的区别是没有重载解引用操作。因此当wp需要真正使用所指向的对象时需要提升至强指针。

4、对象主要有两种生命周期管理方式。一是由强指针控制生命周期,当强引用计数为0时就回收该对象。弱指针不能再提升为强指针。一种是由弱指针控制生命周期,只有当强引用计数和弱引用计数同时为0的时候,才可以回收该对象。

主要思路如上4点,剩下的都是具体的实现方式以及某些特殊情况的处理。比如从来没有强指针指向过该对象,而生命周期又由强指针控制的情况等等。

举个栗子

举一个不恰当的生活的例子来类比一下:

A是个烧饼铺的老板,能够制作烧饼并且出售。

为了扩大销量,A允许他人签订代理合同进行分销。也允许他人仅仅签订意向合同,在之后再签订正式的代理合同。但是签订代理合同之前必须先签订意向合同。

以上是约束条件。

这个时候B和A签订了一份代理合同放在A那里。C也和A签订了一份代理合同放在A那里。于是A就知道他有两个销售代理,那他们存续期间自己是不能关门的,因为他们随时可能需要取货。

这时候,D也想做A的代理但又下不了决心,于是跟A签订了一份意向合同,只是表示自己有这个想法,如果决定了再签订正式的代理合同。

这个时候,A手里就有三份意向合同,两份代理合同。

假设B, C决定不再做A的代理,取消了跟A的代理以及意向合同。这时候A手里就只剩下和D的意向合同了。

此时就分化为几种情况。

A决定关门了。也就是说A关不关门,只看自己手上的代理合同,而不看意向合同。而当D想要前来正式签订代理合同的时候发现A已经关门了,代理合同签订失败。

这就是生命周期由强引用计数控制,而强引用计数为0,弱指针提升为强指针失败的例子。

A没有关门,而是等待D前来取消了意向合同才关门。

这就是生命周期由弱引用计数控制,而弱引用计数变化为0的情况。

而如果A没有关门,直到D前来又签订了一份正式的代理合同。

那么这就是生命周期由弱引用计数控制,而弱指针成功提升为强指针的情况。

从这个例子中我们可以看到一个问题:如果A手里的意向合同有联系方式的话,是不是可以直接打电话告诉D,自己打算关门了而不用等D上门来签合同的时候才知道呢?也即RefBase中除了引用计数的值之外,是不是也可以保存一个wp和sp列表呢?

1、引用产生和失效的判断

针对第一点,Android中定义了两个类模板类sp和wp。分别是strongPointer和weakPointer的缩写。他们内部都持有一个T* m_ptr的对象引用。并且重载了所有赋值和构造相关的函数。也就是说当出现诸如sp<Object> sp_object = object;的调用时,sp类的重载函数就会被调用。而这些重载函数内部的主要逻辑就是增加object对象的强引用计数值(增加强引用计数的同时,弱引用计数也会同时增加)。wp也是同理,只不过增加的是弱引用计数值。于是通过sp和wp去引用一个对象,就解决了如何得知新的引用产生的问题。同理,引用失效的时机就在sp和wp的析构函数中,其主要逻辑也就是减少object的强弱引用计数。

所以我们这里明确,要使用强弱引用技术,就必须使用sp和wp去引用一个对象。普通的指针是没有这种效果的。sp和wp的代码如下:

sp

template<typename T>

class sp

{

    public:

           typedef typename RefBase::weakref_type weakref_type; //暂时可以忽略

           inline sp():m_ptr(0) {}  //构造函数


          /*以下这段就是赋值相关的操作符重载以及构造相关的重载函数,当对sp赋值或者构造sp的时候,这些函数就会被调用。其中除了各函数原本的逻辑之外,最重要的逻辑就是修改引用对象的引用计数*/

           sp(T* other);     

           sp(const sp<T>& other); 

           template<typename U> sp(U* other); 

           template<typename U> sp(const sp<U>& other);

           sp& operator = (T* other);

           sp& operator = (const sp<T> & other);

           template<typename U> sp& operator = (const sp<U>& other);

           template<typename U> sp& operator = (U* other);

          /*以上*/

          ~sp;   //析构函数   减少所引用对象的引用计数

          //解引用部分,也即调用sp->以及sp.操作时,实际调用的是其所指向的对象m_ptr的操作。

          inline T& operator* () const {return *m_ptr;}

          inline T* operator->() const {return m_ptr;}

          inline T* get() const {return m_ptr;}

          ...

          //其余比较操作符重载的部分,因为对于sp的比较实际上应该是m_ptr之间的比较

          ...

     private:

           sp(T* p,  weakref_type* refs);   //暂时可以忽略

           T* m_ptr;  //指向的真实的对象。

}

由于C++提供了操作符重载操作,所以sp重载了->和*等操作符,使得对于sp的操作等同于直接对于m_ptr的操作。同时重载了赋值操作符=,使得赋值的时候我们有机会进行一些操作。构造函数以及拷贝构造函数也做了适当的处理。

wp

wp的主要实现和sp一致,重载比较操作符,解引用操作符,赋值操作符等。在构造函数,拷贝函数以及赋值时增加引用对象的引用计数。在析构函数中减少引用对象的引用计数。其主要代码如下:

template <typename T>

class wp

{

    public:

        typedef typename RefBase::weakref_type weakref_type;

        inline wp(): m_ptr(0) {}

        wp(T* other);

        wp(const wp<T>& other);

        wp(const sp<T>& other);

        template<typename U> wp(U* other);

        template<typename U> wp(const sp<U>& other);

        template<typename U> wp(const wp<U>& other);

        wp& operator = (T* ohter);

        wp& operator = (const wp<T>& other);

       wp& operator = (const sp<T>& other);

       template<typename U> wp& operator = (U* other);

       template<typename U> wp& operator = (const wp<U>& other);

       template<typename U> wp&operator = (const sp<U>& other);

       ~wp();

       sp<T> promote() const;   //用于将弱引用提升为强引用

       //其余操作符重载部分

       ...

private:

        T* m_ptr;   //引用的对象

        weakref_type* m_refs;

}

可以看到wp和sp的主要结构是一致的。但是wp多了一个promote函数以及m_refs成员变量。前者是为了将弱引用提升为强引用用的。后者稍后会提及。

2、引用计数值的维护

在1中,我们知道当sp或者wp被赋值以及析构的时候,都会去修改对象的引用计数值。但是这个引用计数究竟是放在哪里的?放在wp或者sp中是不合适的,因为指向同一个对象的sp或者wp可能同时有多个。那既然所有的sp和wp都能够获得引用的对象,那我们能够想到的就是由所引用的对象自己去维护这个引用计数了。因此,Android为所有需要实现引用计数的对象提供了一个基类。RefBase. RefBase拥有一个weakref_impl类型的成员变量mRefs,weakref_impl又具有int_32_t类型的mStrong以及mWeak两个成员变量。他们分别负责记录一个对象的强引用计数和弱引用计数。所有关于引用计数的操作最终实际上就是修改这两个变量的值。需要注意的是weakref_impl是继承了weakref_type类型。weakref_type类型是在RefBase中定义的。

RefBase

class RefBase 

{

public:

    void  incStrong(const void* id) const;

    void decStrong(const void* id) const;


    class weakref_type 

    {

     public: 

         RefBase*  refBase() const;

         void incWeak(const void* id);

         void decWeak(const void* id);

         bool attemptIncStrong(const void* id);

     }


protected:

   RefBase();  

   virtual ~RefBase();

   emum {

                                                                         //默认情况该对象的生命周期由强引用计数控制

        OBJECT_LIFETIME_WEAK = 0x0001,   //该对象的生命周期由弱引用计数控制

        OBJECT_LIFETIME_FOREVER = 0x0003;  //该对象的生命周期由用户自己维护

   };

   void  extendObjectLifetime(int32_t mode);   //修改该对象的生命周期控制条件

   enum {

       FIRST_INC_STRONG = 0x0001;

   }

    virtual void onFirstRef();   //第一次有引用指向了这个对象。Andorid系统中很多组件的初始化都是放在这个函数里的。

    virtual void onLastStrongRef(const void* id);  //强引用计数变为了0.

    virtual bool onIncStrongRef(const void* id);

    virtual void onLastWeakRef(const void* id);  //弱引用计数变为了0.

private :

    RefBase(const RefBase& o);

    RefBase operator = (const Refbase& o);

    weakref_impl* const mRefs;  //真正维护强弱引用计数的对象

}

weakref_impl

class RefBase::weakref_impl : public RefBase::weakref_type 

{

    public:

       volatile int32_t mStrong;

       volatile int32_t mWeak;

       RefBase* const mBase;

       volatile int32_t mFlags;

       weakref_impl(RefBase* base)

           :mStrong(INITIAL_STRONG_VALUE)

            ,mWeak(0)

            ,mBase(base)

            ,mFlgas(0) 

        {}

        //debug相关函数

       ...

}

通过一张类图,我们可以更为清晰的了解三者之间的关系。

我们在观察下incStrong, decStrong, incWeak, decWeak的实现。

incStrong

void RefBase::incStrong(const void * id) const

{

    weakref_impl* const refs = mRefs;

    refs->incWeak(id);  //增加强引用的时候必须同时增加弱引用。所以弱引用计数的数值是肯定大于强引用计数的。而弱引用计数为0的时候,强引用计数肯定为0.

    const int32_t c= android_atomic_inc(&refs->mStrong);    //c是atomic_inc之前的值

    if (c != INITIAL_STRONG_VALUE) {  //说明不是第一次

         return;

    }

    android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);  //如果是第一次,需要减去INITAL_STRONG_VALUE。

    const_cast<RefBase*>(this)->onFirstRef();  

}


给refs->mStrong设定一个INITIAL_STRONG_VALUE的初始值的意义在于区分这个值是不是被修改过。而如果初始值是0的话,就分不清楚是本来就为0,还是经过一系列增增减减后又变为了0.这跟后面的生命周期有直接关系。如果mStrong的值为INITAL_STRONG_VALUE,那就说明没有任何强引用引用过这个对象。

incWeak

void RefBase::weakref_type::incWeak(const void* id)

{

    weakref_impl* const impl = static_cast<weakref_impl*>(this);

    const int32_t c = android_atomic_inc(&impl->mWeak);

}

decStrong

void RefBase::decStrong(const void* id) const

{

    weakref_impl* const refs = mRefs;

    const int32_t c = android_atomic_dec(&refs->mStrong);

    refs->decWeak(id);

    ...

}

decWeak

void RefBase::weakref_type::decWeak(const void* id)

{

    weakref_impl* const impl = static_cast<weakref_impl>(this);

    const int32_t c = android_atomic_dec(&impl->mWeak);

   ...

}

可以看到IncStrong, incWeak, decStrong, decWeak的主要逻辑都是修改weakref_impl的成员变量mStrong以及mWeak的值。

至此,结合第1节以及第2节的内容,我们可以得出操作强弱引用计数的大体步骤。

1、需要使用引用计数的对象必须继承自RefBase。 RefBase中有个实际类型为weakref_impl,父类型为weakref_type的mRefs成员,其包含两个成员变量mStrong和mWeak. 所有RefBase上调用的incStrong, incWeak, decWeak, decStrong最终的操作都是修改这两个值。

2、要使用引用计数,必须用spwp去指向一个继承自RefBase的对象。在sp和wp初始化操作以及析构函数中,会调用RefBase相关的操作引用计数的方法。

简化后的理解就是:

sp或者wp初始化或者析构------>获取RefBase类型的成员变量m_ptr------>获取RefBase的weakref_impl类型的mRefs成员变量------>修改mStrong以及mWeak的值。

3、对象的生命周期

前两节中,我们已经清楚了对象是怎么感知到指向自身的引用变化以及引用计数的数值更改的流程。但是这个引用计数值究竟是如何变化的,又怎么和对象的生命周期产生关联的,我们还没有提及。

主要是两点。一是RefBase对象回收的时机,二是weakref_impl对象回收的时机。

在第二节中,我们提到过。RefBase的生命周期有三种形式:OBJECT_LIFETIME_STRONG,OBJECT_LIFETIME_WEAK,OBJECT_LIFETIME_FOREVER。分别代表强引用计数控制生命周期,弱引用计数控制生命周期,以及用户自己控制其生命周期。

OBJECT_LIFETIME_STRONG

这种方式下。RefBase对象的生命周期完全由强引用计数控制。所以我们很容易的就能想到当weakref_imp的mStrong值为0的时候也就可以释放RefBase对象了。因此我们可以在decStrong中去进行判断。事实上也是如此。

void RefBase::decStrong(const void* id) const

{

    weakref_impl* const refs = mRefs;

    const int32_t c = android_atomic_dec(&refs->mStrong);

    if (c == 1) {

         const_cast<RefBase*>(this)->onLastStrongRef(id);

        //生命周期由强引用计数控制,且强引用计数值为0,那么就直接回收这个对象。

         if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

              delete this;

         }

    }

    refs->decWeak(id);

}

当RefBase的生命周期控制方式不为OBJECT_LIFETIME_WEAK时,也即完全由强引用计数决定时,那么在强引用计数为0时就可以直接delete this了。

OBJECT_LIFETIME_WEAK

如果RefBase的生命周期由弱引用计数控制。那么在强引用计数为0的时候,就不能直接delete对象。而必须延迟到decWeak中去进行判断。当

void RefBase::weakref_type::decWeak(const void* id)

{

    weakref_impl* const impl = static_cast<weakref_impl*>(this);

    const int32_t c = android_atomic_dec(&impl->mWeak);

    if (c != 1) return;  //弱引用计数不为0,就直接返回

    if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { 

        if (impl->mStrong == INITIAL_STRONG_VALUE)  

             //弱引用计数为0,且强引用计数为初始值,且生命周期不由弱引用计数控制

            //就需要删除该对象。因为没有强引用指向过该对象,也就不可能有decStrong的机会去回收这个对象。

              delete impl->mBase;  

         else

              //生命周期由强引用计数控制,那么现在弱引用计数为0,强引用计数也必然为0,那么在decStrong中该对象已经被回收过了。

              //只需要删除weakref_impl对象即可

               delete impl;  

    } else {  //生命周期由弱引用计数控制

            impl->mBase->onLastWeakRef(id);

            //生命周期由弱引用计数控制,且弱引用计数为0,那么就需要回收该对象。

            if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {

                 delete impl->mBase;

            }

    }

}

整理一下,RefBase对象的回收时机有以下几种情况:

1、生命周期由强引用计数控制,有强引用指向过该对象,那么对象在decStrong中发现mStrong为0时回收。

2、生命周期由强引用计数控制,从来没有强引用指向过该对象,有弱引用指向过该对象。那么对象在decWeak中发现mWeak为0时回收。

3、生命周期由弱引用计数控制,mWeak为0时回收该对象。

weakref_impl的回收时机则有两个,一是在RefBase的析构函数中。另一个则是在decWeak中的delete impl处。

如果是由弱引用计数控制RefBase生命周期的话,那么当RefBase对象析构时,那弱引用计数肯定为0,强引用计数也肯定为0,所以在RefBase的析构函数中就可以安全回收weakref_impl对象。

如果是由强引用计数控制RefBase生命周期的话,那么当RefBase对象析构时,强引用计数为0,弱引用计数不一定为0,那么就不能够在RefBase析构函数中去回收weakref_impl。而只能在弱引用计数为0时去进行回收。

因此现在RefBase的生命周期彻底和其内部的weakref_impl的mWeak和mStrong的值,以及wp和sp的构造和析构函数联系在一起了。

简而言之就是:

wp以及sp的析构函数会触发所引用的RefBase对象的decStrong或者decWeak函数,从而改变RefBase的weakref_impl类型的成员变量的mWeak以及mStrong值。

从而触发RefBase对象的回收逻辑。

弱引用提升为强引用

在上一节中,比较特别的一种情况就是强引用计数为0,弱引用计数不为0的情况,且生命周期由强引用计数控制的情况。在这种情况下,实际上RefBase对象已经被回收了,但是仍然有弱引用指向了这个对象。那么怎么防止弱引用使用这个已经被回收的对象呢?

Android中使用的方法是不重载wp类的解引用操作符,operator*以及operator->,使得wp无法直接使用m_ptr对象,必须调用wp的promote方法将wp升级为sp才能够使用。因此wp的promote方法是关键。

template<typename T>

sp<T> wp<T>::promote() const

{

     return sp<T><m_ptr, m_refs);  //调用强引用的构造器

}

template<typename T>

sp<T>::sp(T* p, weakref_type* refs):m_ptr(p && refs->attemptIncStrong(this)) ? p : 0) {}  //试图调用弱引用所指向对象的weakref_impl成员的attemptIncStrong方法。

bool RefBase:;weakref_type::attemptIncStrong(const void* id) 

{

    incWeak(id);

    weakref_impl* const impl = static_cast<weakref_impl*>(this);

    int32_t curCount = impl->mStrong;

   //如果强引用计数不为0,且不为初始值,那肯定是可以增加强引用计数的。

    while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {  //自旋

       //cmpxchg是个原子操作,全称是compareAndChange,只有mStrong的值为curCount时,才会将其值修改为curCount + 1, 否则修改失败。

      //这样子便可以防止其他线程先一步修改了mStrong的值。在ConcurrentHashmap以及轻量级锁中都用到了这个原子操作。

        if (android_atomic_cmpxchg(curCount, curCount + 1, &impl->mStrong) == 0) {

              //修改curCount值与前面while中判断的间隙期间,引用计数值没有变化,那么实际上是可以直接返回true了,不会进入后面的分支条件

              break;

        }

        //修改curCount值与前面while中判断的间隙期间,引用计数值发生变化了。将curCount更新为mStrong的最新值,再次尝试修改。

        curCount = impl->mStrong; 

    }

    //这里退出循环有两种情况,一种是修改成功break了,那么是不会进入下面的条件的。

   //另一个是修改失败了并且获取最新的引用计数值时发现其值为0了。那么会进入下面的分支。

    if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {  //之前强引用计数为0,或者为初始值

         bool allow;

         if (curCount == INITIAL_STRONG_VALUE) {  

            //该对象之前并没有被强引用指向过,那么只要生命周期不由弱引用控制,那么该对象就不可能被回收过。那么就可以提升为强指针

           //如果该对象生命周期由弱引用控制,显然该对象也未被回收,那么就看调用该对象的onIncStrongAttempted方法看该对象是否允许被提升为强指针。

          //onIncStrongAttempted方法参数为FIRST_INC_STRONG时都是返回true的

             allow = (impl->mFlags&OBJECT_LIFETIME_WEAK)  != OBJECT_LIFETIME_WEAK || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);

         } else {

           //该对象的强引用计数为0,那么只有生命周期弱引用控制的时候,才可能没有被回收,因为当前正有一个弱引用指向对象。

          //那么就调用该对象的onIncStrongAttempted方法看该对象是否允许被提升为强指针。

          //onIncStrongAttempted方法参数为FIRST_INC_STRONG时都是返回true的

            allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);

         }

        if (!allow) {

             decWeak(id);

             return false;

        }

       curCount = android_atomic_inc(&impl->mStrong);  //如果允许的话就增加强引用计数

    }

    //修正mStrong 的值并且调用onFirstRef方法。

    if (curCount == INITIAL_STRONG_VALUE) {  

         android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);

         impl->mBase->onFirstRef();  

    }

    return true;

}

//可以看到当参数为FIRST_INC_STRONG时,是一直返回true的

bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id) 

{

    return (flags& FIRST_INC_STRONG) ? true : false;

}

所以结合这段将弱指针提升为强指针的代码,我们可以看到:

attemptIncStrong的主要逻辑就是判断该对象是不是已经回收了,实际上只要是该对象没有被回收,那么都是可以被提升到强指针的。(由于这段代码里onIncStrongAttempted都是返回true,所以可以忽略)

如果强引用计数值大于0且不等于INITIAL_STRONG_VALUE,那么就肯定可以提升为强指针。这也是while那段逻辑所判断的。

如果强引用计数等于0,那么只有生命周期由弱引用计数控制的时候才没有被回收。

如果强引用计数等于INITIAL_STRONG_VALUE,那么也是可以被提升为强指针的。

以上的代码和解说基本上都是照搬《Android系统源代码情景分析》中的,简化了某些不必要的代码部分。但是仍然过于繁琐。我觉得作为一个Android应用层开发的程序员,对于框架层的代码,还是抱着不求甚解的态度去阅读比较好。抓大放小,理解关键实现方式,而不应该对其中的细节纠缠过多。因为细节在你真正去实现的时候,你自然会发现也能够解决,并不是阻挡你实现的关键障碍。


大致情况就是这样子。如果以后找到更好的表述方式的话会再进行改进。

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

推荐阅读更多精彩内容