源码学习记录-从android.view.View开始

View是安卓一切视图的基础,我觉得先从这个点开始学习,向下扩展会让我更容易理解。

//包
package android.view;
//类
android.view.View

1、 实现接口 Drawable.Callback , 用来创建动画绘制时,用来实现调度和动画修改操作的,分为以下三个接口:

//绘制自身
void invalidateDrawable(@NonNull Drawable who);
//计划执行下一次动画
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
//取消执行前面计划
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);

这个接口是在 setBackground(Drawable background) 中注册监听的。其实现如下:

    public void setBackground(Drawable background) {
        setBackgroundDrawable(background);
    }

    @Deprecated
    public void setBackgroundDrawable(Drawable background) {
        ...
        boolean requestLayout = false;

        mBackgroundResource = 0;

        //如果已有背景,如果此View绑定到了Window上,则先使其不可见。
        if (mBackground != null) {
            if (isAttachedToWindow()) {
                mBackground.setVisible(false, false);
            }
            //然后再清空回调监听
            mBackground.setCallback(null);
            //再清空原背景的所有绘制任务
            unscheduleDrawable(mBackground);
        }

        if (background != null) {
            ...
            //设置当前背景的布局方向            
            background.setLayoutDirection(getLayoutDirection());
            //这里获取背景的padding值并根据布局方向设置布局参数。
            if (background.getPadding(padding)) {
                resetResolvedPaddingInternal();
                switch (background.getLayoutDirection()) {
                    case LAYOUT_DIRECTION_RTL:
                        mUserPaddingLeftInitial = padding.right;
                        mUserPaddingRightInitial = padding.left;
                        internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
                        break;
                    case LAYOUT_DIRECTION_LTR:
                    default:
                        mUserPaddingLeftInitial = padding.left;
                        mUserPaddingRightInitial = padding.right;
                        internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
                }
                mLeftPaddingDefined = false;
                mRightPaddingDefined = false;
            }

            // 判断当前背景大小是否与上一次绘制的背景大小相等,不想等则标记为要重新绘制。
            if (mBackground == null
                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                requestLayout = true;
            }

            //赋值
            mBackground = background;
            //判断是否需要状态更新
            if (background.isStateful()) {
                background.setState(getDrawableState());
            }
            //判断是否已添加到窗口中
            if (isAttachedToWindow()) {
                //设置是否可见
                background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
            }

            applyBackgroundTint();

            // 最后设置监听
            background.setCallback(this);

            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                requestLayout = true;
            }
        } else {
            //这里的分支是做移除背景操作
            mBackground = null;
            if ((mViewFlags & WILL_NOT_DRAW) != 0
                    && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
                mPrivateFlags |= PFLAG_SKIP_DRAW;
            }

           //这里会请求布局一次
            requestLayout = true;
        }

        //再次计算一次透明度
        computeOpaqueFlags();

        //是否需要重新布局
        if (requestLayout) {
            requestLayout();
        }

        //标记背景大小发生改变
        mBackgroundSizeChanged = true;

        //绘制
        invalidate(true);
        invalidateOutline();
    }

2、 实现接口 KeyEvent.Callback , 实现所有按键监听回调,接口如下:

boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);

具体实现方式如下:

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {//检测
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {//检测是否标记为禁用
                return true;
            }
//判断是否可点击,或可长点击
            if (((mViewFlags & CLICKABLE) == CLICKABLE
                    || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    && (event.getRepeatCount() == 0)) {
                // 计算出中心点
                final float x = getWidth() / 2f;
                final float y = getHeight() / 2f;
                setPressed(true, x, y);//按压
                checkForLongClick(0, x, y);//检测是否为长点击
                return true;
            }
        }
        return false;
    }
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }
            if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
                setPressed(false);//取消按压状态

                if (!mHasPerformedLongPress) {
                    // 如果有长按,则将长按回调移除
                    removeLongPressCallback();
                    return performClick();
                }
            }
        }
        return false;
    }

3、 下面开始从View的构造函数开始正是入手,部分源码如下:


    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        this(context);//处理context赋值等操作

        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
       
            ···
            //初始化XML参数
            ···

        a.recycle();

        // Needs to be called after mViewFlags is set
        if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
            recomputePadding();
        }

        if (x != 0 || y != 0) {//是否需要滚动
            scrollTo(x, y);
        }

        if (transformSet) {//设置一些偏移,旋转放大缩小等参数
            setTranslationX(tx);
            setTranslationY(ty);
            setTranslationZ(tz);
            setElevation(elevation);
            setRotation(rotation);
            setRotationX(rotationX);
            setRotationY(rotationY);
            setScaleX(sx);
            setScaleY(sy);
        }

        if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
            setScrollContainer(true);//设置内容是否可以滚动
        }
    }

以下 performClick() 用来实现OnClickListener操作,并作出一系列关联操作:

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {//如果设置了监听,则播放声音,并实现
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        //发送事件类型状态
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

callOnClick() 只调用实现监听,并不做一系列相关操作:

    public boolean callOnClick() {
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            li.mOnClickListener.onClick(this);
            return true;
        }
        return false;
    }

4、 这里有个厉害的了 onTouchEvent(MotionEvent event) ,处理触摸事件,通常自定义View都会重写它,这里来按下源码。

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
         
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        if (mTouchDelegate != null) {//代理分发
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //判断是否可用点击、长按、还是上下文点击
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                  boolean focusTaken = false;//焦点获取
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                    ···
                         if (!post(mPerformClick)) {//分发点击事件
                                 performClick();
                         }

                    ...
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (isInScrollingContainer) {//这里会先判断是否为内部滚动事件
                        ...
                    } else {
                        // 如果不是则,直接反馈按压状态,并检测是否为长按操作
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);//解除按压状态
                    removeTapCallback();移除回调
                    removeLongPressCallback();移除长按回调
                    //重置状态
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
               // Be lenient about moving outside of buttons
//这里有个mTouchSlop,是通过ViewConfiguration.get(context).getScaledTouchSlop();得到的。
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

5、 其实我觉得最牛的方法应该是 void setFlags(int flags, int mask) 虽然他是包类方法,但是这个方法的作用太强大了,所有的标志位设置,几乎都经过它, flags 表示应该设置的值,mask 表示改变的值的范围,源码部分分析如下:

    void setFlags(int flags, int mask) {
        final boolean accessibilityEnabled =
                AccessibilityManager.getInstance(mContext).isEnabled();
        final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();

        int old = mViewFlags;//先临时保存一份旧的
        mViewFlags = (mViewFlags & ~mask) | (flags & mask);

        int changed = mViewFlags ^ old;//判断旧的和新的是否发生改变
        if (changed == 0) {
            return;//如果没有改变则返回
        }
        int privateFlags = mPrivateFlags;

        /* Check if the FOCUSABLE bit has changed */
        if (((changed & FOCUSABLE_MASK) != 0) &&
                ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {//判断是否改变了焦点
           ...
        }

        final int newVisibility = flags & VISIBILITY_MASK;
        if (newVisibility == VISIBLE) {//是否为VISIBLE
            if ((changed & VISIBILITY_MASK) != 0) {
                //是否发生改变
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(true);//重新绘制
                ...
            }
        }

        /* Check if the GONE bit has changed */
        if ((changed & GONE) != 0) {
            needGlobalAttributesUpdate(false);
            requestLayout();

            ...
        }

        /* Check if the VISIBLE bit has changed */
        if ((changed & INVISIBLE) != 0) {
            needGlobalAttributesUpdate(false);
            ...
        }

        if ((changed & VISIBILITY_MASK) != 0) {
            // If the view is invisible, cleanup its display list to free up resources
            if (newVisibility != VISIBLE && mAttachInfo != null) {
                cleanupDraw();
            }

            if (mParent instanceof ViewGroup) {
                ((ViewGroup) mParent).onChildVisibilityChanged(this,
                        (changed & VISIBILITY_MASK), newVisibility);
                ((View) mParent).invalidate(true);
            } else if (mParent != null) {
                mParent.invalidateChild(this, null);
            }
            ...
        }

        //后面是针对其它标记位进行判断

        if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
            destroyDrawingCache();
        }

        if ((changed & DRAWING_CACHE_ENABLED) != 0) {
            destroyDrawingCache();
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            invalidateParentCaches();
        }

        if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
            destroyDrawingCache();
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        if ((changed & DRAW_MASK) != 0) {
            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
                if (mBackground != null
                        || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                } else {
                    mPrivateFlags |= PFLAG_SKIP_DRAW;
                }
            } else {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
            requestLayout();
            invalidate(true);
        }

        if ((changed & KEEP_SCREEN_ON) != 0) {
            if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
                mParent.recomputeViewAttributes(this);
            }
        }

        if (accessibilityEnabled) {
            ...
            //通知
        }
    }

6、 bringToFront() 将视图置顶,这个方法有时也很常用,我们来看下它的内部,调用父类方法,如下:

    public void bringChildToFront(View child) {
        final int index = indexOfChild(child);//查询孩子的脚标
        if (index >= 0) {//判断是否存在
            removeFromArray(index);//先移除,然后再添加进来
            addInArray(child, mChildrenCount);
            child.mParent = this;
            requestLayout();//重新布局
            invalidate();//重新绘制
        }
    }

7、 dispatchAttachedToWindow(AttachInfo info, int visibility) 这个方法应该是当View绑定上窗口时,系统自动调用的。部分分析如下:

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;记录绑定次数

        ...

        // Transfer all pending runnables.
        if (mRunQueue != null) {对绑定前操作的所有任务队列,在这里进行统一处理。
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();//调用此方法,View子类都可重写此方法。

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            //通知监听
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                // Calling onVisibilityAggregated directly here since the subtree will also
                // receive dispatchAttachedToWindow and this same call
                onVisibilityAggregated(vis == VISIBLE);
            }
        }

        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);

        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);
    }

8、 相对绑定,肯定会有个解绑 dispatchDetachedFromWindow() ,如下:

    void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
                if (isShown()) {
                    // Invoking onVisibilityAggregated directly here since the subtree
                    // will also receive detached from window
                    onVisibilityAggregated(false);
                }
            }
        }

        onDetachedFromWindow();//对外继承使用
        onDetachedFromWindowInternal();//内部调用

        ...

        //其它通知
    }

9、 实在是看不下去了,最后来讨论下 getViewTreeObserver(), 这是一个注册监听视图树的观察者(observer),在视图树种全局事件改变时得到通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等。

内部接口

    interface  ViewTreeObserver.OnGlobalFocusChangeListener         
  //当在一个视图树中的焦点状态发生改变时,所要调用的回调函数的接口类
 
  interface  ViewTreeObserver.OnGlobalLayoutListener
  //当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类
 
  interface  ViewTreeObserver.OnPreDrawListener
  //当一个视图树将要绘制时,所要调用的回调函数的接口类
 
  interface  ViewTreeObserver.OnScrollChangedListener
  //当一个视图树中的一些组件发生滚动时,所要调用的回调函数的接口类
 
  interface  ViewTreeObserver.OnTouchModeChangeListener
  //当一个视图树的触摸模式发生改变时,所要调用的回调函数的接口类

可使用的公共方法

/**注册一个回调函数,当在一个视图树中的焦点状态发生改变时调用这个回调函数。
  * 参数 listener    将要被添加的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false
  */
 public void addOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener listener)
     
 
 /**注册一个回调函数,当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时调用这个回调函数。
  *参数 listener    将要被添加的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false
  */
 public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
  
 
  
 /**注册一个回调函数,当一个视图树将要绘制时调用这个回调函数。
  *参数 listener    将要被添加的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false
  */
 public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
 
    
 /**注册一个回调函数,当一个视图发生滚动时调用这个回调函数。
  *参数 listener    将要被添加的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false
  */
 public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)  
 
 
 /**注册一个回调函数,当一个触摸模式发生改变时调用这个回调函数。
  *参数 listener    将要被添加的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false
  */
 public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
 
  
 //当整个布局发生改变时通知相应的注册监听器。如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在GONE状态下,它可以被手动的调用
 public final void dispatchOnGlobalLayout ()
    
 /**当一个视图树将要绘制时通知相应的注册监听器。如果这个监听器返回true,则这个绘制将被取消并重新计划。如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在一个GONE状态下,它可以被手动的调用
  *返回值  当前绘制能够取消并重新计划则返回true,否则返回false。
  */
 public final boolean dispatchOnPreDraw ()
 
 /**指示当前的ViewTreeObserver是否可用(alive)。当observer不可用时,任何方法的调用(除了这个方法)都将抛出一个异常。如果一个应用程序保持和ViewTreeObserver一个历时较长的引用,它应该总是需要在调用别的方法之前去检测这个方法的返回值。
  *返回值 但这个对象可用则返回true,否则返回false   
  */
 public boolean isAlive ()
    
     
 /**移除之前已经注册的全局布局回调函数。
  *参数 victim 将要被移除的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false   
  */
 public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
  
 /**移除之前已经注册的焦点改变回调函数。
  *参数 victim 将要被移除的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false 
  */
 public void removeOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener victim)
  
 /**移除之前已经注册的预绘制回调函数。
  *参数 victim 将要被移除的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false  
  */
 public void removeOnPreDrawListener (ViewTreeObserver.OnPreDrawListener victim)
  
 /**移除之前已经注册的滚动改变回调函数。
  *参数 victim 将要被移除的回调函数
  *异常 IllegalStateException       如果isAlive() 返回false 
  */
 public void removeOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener victim)
 
 /**移除之前已经注册的触摸模式改变回调函数
  *参数 victim 将要被移除的回调函数
  *异常  IllegalStateException       如果isAlive() 返回false
  */
 public void removeOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener victim)

作者:Zyao89;转载请保留此行,谢谢;

个人博客:http://zyao89.me

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

推荐阅读更多精彩内容