Android View工作原理

前言

在Android知识体系中,View扮演了很重要的角色;它是Android在视觉上的呈现,Android本身提供了一套GUI库,但是我们的需求不止于系统自带的GUI,因此我们还需要自定义View。而自定义View过程中我们势必要对View的底层工作原理有所了解。这篇文章就记录一下View的测量、布局、绘制三大流程。

1.一些必要的基本概念

1.1 ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,连接WindowManagerDecorView的纽带;View的三大流程基于RootView来完成的;View的绘制流程是从ViewRoot的performTraversals方法开始的。

DecorView顶级View,内部一般为一个竖直方向的LinearLayout,分为2部分:titlebar以及android.R.id.content的FrameLayout。

结构图如下:

在这里插入图片描述
1.2 MeasureSpec

字面意思为“测量规格”;

它代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)。

SpecMode一共有3类:

  1. EXACTLY:match_parent和具体数值,父容器已经检测出View的精确大小

  2. AT_MOST:父容器制定一个可用大小,View的大小不能大于它

  3. UNSPECIFIED: 父容器不对View有任何限制

一般来说,View的MeasureSpec由本身的LayoutParams与父容器的MeasureSpec共同决定。DecorView的MeasureSpec由LayoutParams决定。父容器通过measure传递MeasureSpec给子View.

引用刚哥的《Android 开发艺术探索》的图:这张图涵括了View Measure中最核心的过程。

在这里插入图片描述

它在View的底层原理中起到的作用下面的篇幅在做具体的介绍。源码如下:所在包import android.view.View.MeasureSpec;


/**

    * A MeasureSpec encapsulates the layout requirements passed from parent to child.

    * Each MeasureSpec represents a requirement for either the width or the height.

    * A MeasureSpec is comprised of a size and a mode. There are three possible

    * modes:

    * <dl>

    * <dt>UNSPECIFIED</dt>

    * <dd>

    * The parent has not imposed any constraint on the child. It can be whatever size

    * it wants.

    * </dd>

    *

    * <dt>EXACTLY</dt>

    * <dd>

    * The parent has determined an exact size for the child. The child is going to be

    * given those bounds regardless of how big it wants to be.

    * </dd>

    *

    * <dt>AT_MOST</dt>

    * <dd>

    * The child can be as large as it wants up to the specified size.

    * </dd>

    * </dl>

    *

    * MeasureSpecs are implemented as ints to reduce object allocation. This class

    * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.

    */

    public static class MeasureSpec {

        private static final int MODE_SHIFT = 30;

        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */

        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})

        @Retention(RetentionPolicy.SOURCE)

        public @interface MeasureSpecMode {}

        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        public static final int EXACTLY    = 1 << MODE_SHIFT;

        public static final int AT_MOST    = 2 << MODE_SHIFT;



        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,

                                          @MeasureSpecMode int mode) {

            if (sUseBrokenMakeMeasureSpec) {

                return size + mode;

            } else {

                return (size & ~MODE_MASK) | (mode & MODE_MASK);

            }

        }

        public static int makeSafeMeasureSpec(int size, int mode) {

            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {

                return 0;

            }

            return makeMeasureSpec(size, mode);

        }

        public static int getMode(int measureSpec) {

            //noinspection ResourceType

            return (measureSpec & MODE_MASK);

        }

        public static int getSize(int measureSpec) {

            return (measureSpec & ~MODE_MASK);

        }

        static int adjust(int measureSpec, int delta) {

            final int mode = getMode(measureSpec);

            int size = getSize(measureSpec);

            if (mode == UNSPECIFIED) {

                // No need to adjust size for UNSPECIFIED mode.

                return makeMeasureSpec(size, UNSPECIFIED);

            }

            size += delta;

            if (size < 0) {

                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +

                        ") spec: " + toString(measureSpec) + " delta: " + delta);

                size = 0;

            }

            return makeMeasureSpec(size, mode);

        }

        public static String toString(int measureSpec) {

            int mode = getMode(measureSpec);

            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)

                sb.append("UNSPECIFIED ");

            else if (mode == EXACTLY)

                sb.append("EXACTLY ");

            else if (mode == AT_MOST)

                sb.append("AT_MOST ");

            else

                sb.append(mode).append(" ");

            sb.append(size);

            return sb.toString();

        }

    }

2.View的工作原理

上面介绍了View底层工作原理所必备的一些知识后,现在重点介绍View的工作原理:

View三大流程的入口(通过Log可以得知):是从onResume()之后开始的。跟踪ActivityThread的源码,在handleResumeActivity()中开始.

2.1 handleResumeActivity()

@Override

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,

            String reason) {

        ......

        if (r.window == null && !a.mFinished && willBeVisible) {

            r.window = r.activity.getWindow();

            View decor = r.window.getDecorView();

            decor.setVisibility(View.INVISIBLE);

            ViewManager wm = a.getWindowManager();

            WindowManager.LayoutParams l = r.window.getAttributes();

            a.mDecor = decor;

            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

            l.softInputMode |= forwardBit;

            if (r.mPreserveWindow) {

                a.mWindowAdded = true;

                r.mPreserveWindow = false;

                // Normally the ViewRoot sets up callbacks with the Activity

                // in addView->ViewRootImpl#setView. If we are instead reusing

                // the decor view we have to notify the view root that the

                // callbacks may have changed.

                ViewRootImpl impl = decor.getViewRootImpl();

                if (impl != null) {

                    impl.notifyChildRebuilt();

                }

            }

            if (a.mVisibleFromClient) {

                if (!a.mWindowAdded) {

                    a.mWindowAdded = true;

                    wm.addView(decor, l);

                } else {

                    // The activity will get a callback for this {@link LayoutParams} change

                    // earlier. However, at that time the decor will not be set (this is set

                    // in this method), so no action will be taken. This call ensures the

                    // callback occurs with the decor set.

                    a.onWindowAttributesChanged(l);

                }

            }

          ......

    }

,注意注释 // in addView->ViewRootImpl#setView. If we are instead reusing最终调用ViewRootImpl.setView();

2.2 ViewRootImpl.setView()

/**

    * We have one child

    */

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

        ......

        requestLayout();

        ......

    }

进入requestLayout()方法。

2.3 ViewRootImpl.requestLayout()

@Override

    public void requestLayout() {

        if (!mHandlingLayoutInLayoutRequest) {

            checkThread();

            mLayoutRequested = true;

            scheduleTraversals();

        }

    }

2.4 ViewRootImpl.performTraversals()

@Override

    public void performTraversals() {

      ......

      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

      ......

      performLayout(lp, mWidth, mHeight);

      ......

      performDraw();

      ......

    }

核心方法:View的工作主要流程就在此方法中。

归纳上方的流程为下图:

在这里插入图片描述
  • measure 测量,决定了测量后的宽高

  • layout 布局,确定四个定点坐标以及View的实际宽高

  • draw 绘制,决定View的显示

依次调用performMeasure、performLayout、performDraw方法,完成顶级View的measure、layout、draw;而在measure、layout、draw又会调用oMeasure、onLayout、onDraw对其子元素重复递归上过程,直到view不为ViewGroup时。

3.View的measure过程

  1. View的measure

具体流程:

在这里插入图片描述

一系列的方法会设置View宽高的测量值。其中getSuggestedMinimumWidth()会根据是存在背景来返回最小宽度。


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

    }

public static int getDefaultSize(int size, int measureSpec) {

        int result = size;

        int specMode = MeasureSpec.getMode(measureSpec);

        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {

        case MeasureSpec.UNSPECIFIED:

            result = size;

            break;

        case MeasureSpec.AT_MOST:

        case MeasureSpec.EXACTLY:

            result = specSize;

            break;

        }

        return result;

    }

protected int getSuggestedMinimumWidth() {

        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());

    }

从getDefault方法中可得结论:

直接继承View的自定义控件需重写onMeasure方法并设置wrap_content时的自身大小,否则使用wrap_content就相当于使用match_content。

  1. ViewGroup的measure

而ViewGroup需要完成本身的measure过程外,还要遍历所有子元素的measure方法,递归执行。本身未重写onMeasure()方法,提供measureChildren()方法遍历测量View树。具体继承自它的类重写onMeasure()对子View进行测量。

首先要确定DecorView的MeasureSpec用来传递给子View进行生成MeasureSpec。DecorView通过getRootMeasureSpec确定本身测量模式以及大小。其中windowSize就是屏幕的宽高。


    private static int getRootMeasureSpec(int windowSize, int rootDimension) {

        int measureSpec;

        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:

            // Window can't resize. Force root view to be windowSize.

            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);

            break;

        case ViewGroup.LayoutParams.WRAP_CONTENT:

            // Window can resize. Set max size for root view.

            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);

            break;

        default:

            // Window wants to be an exact size. Force root view to be that size.

            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);

            break;

        }

        return measureSpec;

    }

其次,通过measureChildren(int widthMeasureSpec, int heightMeasureSpec)遍历测量所有的View树。


// 遍历测量View树

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {

        final int size = mChildrenCount;

        final View[] children = mChildren;

        for (int i = 0; i < size; ++i) {

            final View child = children[i];

            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {

                measureChild(child, widthMeasureSpec, heightMeasureSpec);

            }

        }

    }

// 根据LayoutParams 以及 父View传递过来的MeasureSpec 创建自身的测量模式/规格

protected void measureChild(View child, int parentWidthMeasureSpec,

            int parentHeightMeasureSpec) {

        //  自身布局参数   

        final LayoutParams lp = child.getLayoutParams();

        // 获取当前所需测量child的宽度的测量模式

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

                mPaddingLeft + mPaddingRight, lp.width);

        // 获取当前所需测量child的高度的测量模式     

        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

                mPaddingTop + mPaddingBottom, lp.height);

// 最终调用measure,判断是View还是ViewGroup

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

    }

其中最重要的源码部分如下:此部分即可以归纳为上面引入的图片。


// spec 父Spec padding值  childDimension为布局文件中设置的参数

  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

  // 父specMode

        int specMode = MeasureSpec.getMode(spec);

        // 父specSize

        int specSize = MeasureSpec.getSize(spec);

// 父实际可用的空间

        int size = Math.max(0, specSize - padding);

// 最终size

        int resultSize = 0;

        // 最终模式

        int resultMode = 0;

// 结合父测量模式以及自身LayoutParams来创建child的测量模式

        switch (specMode) {

        // Parent has imposed an exact size on us

        case MeasureSpec.EXACTLY:

            if (childDimension >= 0) {

                resultSize = childDimension;

                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.MATCH_PARENT) {

                // Child wants to be our size. So be it.

                resultSize = size;

                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.WRAP_CONTENT) {

                // Child wants to determine its own size. It can't be

                // bigger than us.

                resultSize = size;

                resultMode = MeasureSpec.AT_MOST;

            }

            break;

        // Parent has imposed a maximum size on us

        case MeasureSpec.AT_MOST:

            if (childDimension >= 0) {

                // Child wants a specific size... so be it

                resultSize = childDimension;

                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.MATCH_PARENT) {

                // Child wants to be our size, but our size is not fixed.

                // Constrain child to not be bigger than us.

                resultSize = size;

                resultMode = MeasureSpec.AT_MOST;

            } else if (childDimension == LayoutParams.WRAP_CONTENT) {

                // Child wants to determine its own size. It can't be

                // bigger than us.

                resultSize = size;

                resultMode = MeasureSpec.AT_MOST;

            }

            break;

        // Parent asked to see how big we want to be

        case MeasureSpec.UNSPECIFIED:

            if (childDimension >= 0) {

                // Child wants a specific size... let him have it

                resultSize = childDimension;

                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.MATCH_PARENT) {

                // Child wants to be our size... find out how big it should

                // be

                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

                resultMode = MeasureSpec.UNSPECIFIED;

            } else if (childDimension == LayoutParams.WRAP_CONTENT) {

                // Child wants to determine its own size.... find out how

                // big it should be

                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

                resultMode = MeasureSpec.UNSPECIFIED;

            }

            break;

        }

        //noinspection ResourceType

        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

    }

总结:

  • 如果View的宽高是固定值,无论父MeasureSpec为何值,View的测量模式都为EXACTLY,且specSize都为实际值

  • 如果View的宽高是wrap_content,无论父MeasureSpec为何值(EXACTLY/AT_MOST),View的测量模式都为AT_MOST,且specSize都不笨办法超过父实际的size

  • 如果View的宽高是match_parent,父测量模式为EXACTLY,子View的测量模式也为EXACTLY;父测量模式为AT_MOST,子View的测量模式也为AT_MOST

到此整个View的Measure过程就结束了,测量完成后可通过getMeasuredWidth/Height()获取到View的测量宽高。整个流程的流程图如下(省略了好多细节,大体上走了一遍流程):

在这里插入图片描述

4.View的layout过程

Layout的作用是确定子元素的位置,layout方法确定本身所在位置,onLayout确定所有子元素的位置。跟踪ViewRootImpl中的performLayout方法。


private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,

            int desiredWindowHeight) {



......

// host 即为DecorView

        final View host = mView;

        if (host == null) {

            return;

        }

  ......

    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

    ......



    }

4.1 View中的layout方法

首先初始化LTRB,其中l,t,r,b分别为View的四个顶点的位置。接着调用onLayout确定子元素的位置。onLayout与onMeasure相似,具体的实现在具体的View中重写即可。onLayout中遍历调用setChildFrame(),内部调用的是 child.layout(left, top, left + width, top + height);通过递归完成View树的布局过程。


  /**

    * <p>Derived classes should not override this method.

    * Derived classes with children should override

    * onLayout. In that method, they should

    * call layout on each of their children.</p>

    *

    * @param l Left position, relative to parent

    * @param t Top position, relative to parent

    * @param r Right position, relative to parent

    * @param b Bottom position, relative to parent

    */

    @SuppressWarnings({"unchecked"})

    public void layout(int l, int t, int r, int b) {

        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {

            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);

            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

        }

        int oldL = mLeft;

        int oldT = mTop;

        int oldB = mBottom;

        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?

                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);  // 设置四个顶点的坐标。

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

        // 调用onLayout确定所有子元素的位置

            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {

                if(mRoundScrollbarRenderer == null) {

                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);

                }

            } else {

                mRoundScrollbarRenderer = null;

            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;

            if (li != null && li.mOnLayoutChangeListeners != null) {

                ArrayList<OnLayoutChangeListener> listenersCopy =

                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();

                int numListeners = listenersCopy.size();

                for (int i = 0; i < numListeners; ++i) {

                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);

                }

            }

        }

        ......

        ......

    }

把LinearLayout中onLayout的具体实现做分析:


@Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        if (mOrientation == VERTICAL) {

            layoutVertical(l, t, r, b);

        } else {

            layoutHorizontal(l, t, r, b);

        }

    }

// 具体的实现方法

void layoutVertical(int left, int top, int right, int bottom) {

        ......

        final int count = getVirtualChildCount();

        ......

        for (int i = 0; i < count; i++) {

            final View child = getVirtualChildAt(i);

            // 测量宽高

          final int childWidth = child.getMeasuredWidth();

          final int childHeight = child.getMeasuredHeight();

          ......

// 为子元素指定对应的位置

// 设置的就是测量的宽高

                setChildFrame(child, childLeft, childTop + getLocationOffset(child),

                        childWidth, childHeight);

            ......

            }

        }

    }

private void setChildFrame(View child, int left, int top, int width, int height) {

        child.layout(left, top, left + width, top + height);

    }

5.View的draw过程

Draw过程相对比较简单,即将View绘制到屏幕上。从RootViewImpl中跟踪。观察源码:


private void performDraw() {

        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {

            return;

        } else if (mView == null) {

            return;

        }

  ......

        boolean canUseAsync = draw(fullRedrawNeeded);

        ......

    }

    private boolean draw(boolean fullRedrawNeeded) {

......

          if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,

                scalingRequired, dirty, surfaceInsets)) {

              return false;

              }

......

        return useAsyncReport;

    }

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,

            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

......

          mView.draw(canvas);

......

    }

接着会调用到View的draw方法,其源码如下:


@CallSuper

    public void draw(Canvas canvas) {

        final int privateFlags = mPrivateFlags;

        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&

                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*

        * Draw traversal performs several drawing steps which must be executed

        * in the appropriate order:

        *

        *      1. Draw the background

        *      2. If necessary, save the canvas' layers to prepare for fading

        *      3. Draw view's content

        *      4. Draw children

        *      5. If necessary, draw the fading edges and restore layers

        *      6. Draw decorations (scrollbars for instance)

        */

        // Step 1, draw the background, if needed

        int saveCount;

        if (!dirtyOpaque) {

            drawBackground(canvas);

        }

        // skip step 2 & 5 if possible (common case)

        final int viewFlags = mViewFlags;

        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;

        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

        if (!verticalEdges && !horizontalEdges) {

            // Step 3, draw the content

            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children

            dispatchDraw(canvas);



            // Step 6, draw decorations (foreground, scrollbars)

            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight

            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {

                debugDrawFocus(canvas);

            }

            // we're done...

            return;

        }



        // Step 3, draw the content

        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children

        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers

        final Paint p = scrollabilityCache.paint;

        final Matrix matrix = scrollabilityCache.matrix;

        final Shader fade = scrollabilityCache.shader;



        // Step 6, draw decorations (foreground, scrollbars)

        onDrawForeground(canvas);



    }

注释已经解释的很清楚了,主要过程遵循如下几步:

  1. 绘制背景 drawBackground(canvas);

  2. 绘制内容 onDraw(canvas);

  3. 绘制children dispatchDraw(canvas);

  4. 绘制装饰 onDrawForeground(canvas);

View绘制过程的传递是通过dispatchDraw()来实现的,它会遍历调用所有子元素的draw方法。

View的工作原理就学习到这里了 !!! 太难了~!慢慢学习!!

感谢:

https://blog.csdn.net/xfhy_/article/details/90270630#comments

参考:《Android 开发艺术探索》

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

推荐阅读更多精彩内容