View绘制流程(一)

View绘制流程(二)

最近在学习View的绘制流程,看了几篇不错的博客(ViewRootImpl的独白,我不是一个View(布局篇)Android应用层View绘制流程与源码分析)自己对照源码,梳理了一遍。

相关类

  • Activity:一个Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务,例如拨号、拍照。
  • View:作为所有图形的基类。
  • ViewGroup:对View继承扩展为视图容器类。
  • Window:它概括了Android窗口的基本属性和基本功能。(抽象类)
  • PhoneWindow:Window的子类。
  • DecorView:界面的根View,PhoneWindow的内部类。
  • ViewRootImpl:ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁。
  • WindowManangerService:简称WMS,它的作用是管理所有应用程序中的窗口,并用于管理用户与这些窗口发生的的各种交互。

View树的绘制流程是在ViewRootImpl类的performTraversals()方法开始的

ViewRootImpl简介

ViewRootImpl是View中的最高层级,属于所有View的根(但ViewRootImpl不是View,只是实现了ViewParent接口),实现了View和WindowManager之间的通信协议。

ViewRootImpl的初始化

WindowManager继承ViewManger,从ViewManager这个类名来看就是用来对View类进行管理的,从ViewManager接口中的添加、更新、删除View的方法也可以看出来WindowManager对View的管理。
WindowManagerImplWindowManager的实现类。WindowManagerImpl内部方法实现都是由代理类WindowManagerGlobal完成,而WindowManagerGlobal是一个单例,也就是一个进程中只有一个WindowManagerGlobal对象服务于所有页面的View。

public final class WindowManagerGlobal {
    /*******部分代码省略**********/
    //所有Window对象中的View
    private final ArrayList<View> mViews = new ArrayList<View>();
    //所有Window对象中的View所对应的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //所有Window对象中的View所对应的布局参数
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    /*******部分代码省略**********/
}

WindowManagerGlobal在其内部存储着ViewRootImplView实例的映射关系(顺序存储)。

在Activity的onResume之后,当前Activity的Window对象中的View会被添加在WindowManager中。

public final class ActivityThread {
    /*******部分代码省略**********/
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        /*******部分代码省略**********/
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        if (r != null) {
            /*******部分代码省略**********/
            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;
                //window的类型:一个应用窗口类型(所有的应用窗口类型都展现在最顶部)。
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //将decor添加在WindowManager中
                    wm.addView(decor, l);
                }
            /*******部分代码省略**********/
        } else {
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null, false);
            } catch (RemoteException ex) {
            }
        }
    }
}
wm.addView(decor, l);方法的具体实现是在WindowManager的代理类WindowManagerGlobal
public final class WindowManagerGlobal {
    /*******部分代码省略**********/
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
         /*******部分代码省略**********/
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        //声明ViwRootImpl
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }
            /*******部分代码省略**********/
            //创建ViwRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            //将Window所对应的View、ViewRootImpl、LayoutParams顺序添加在WindowManager中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
        try {
            //把将Window所对应的View设置给创建的ViewRootImpl
            //通过ViewRootImpl来更新界面并完成Window的添加过程。
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            /*******部分代码省略**********/
        }
    }
}
创建ViewRootImpl实例后,将将Window所对应的ViewViewRootImplLayoutParams顺序添加在WindowManager中,然后将Window所对应的View设置给创建的ViewRootImplroot.setView(view, wparams, panelParentView);
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    /*******部分代码省略**********/
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                //ViewRootImpl成员变量view进行复制,以后操作的都是mView。
                mView = view;
                /*******部分代码省略**********/
                //Window在添加完之前先进行一次布局,确保以后能再接受系统其它事件之后重新布局。
                //对View完成异步刷新,执行View的绘制方法。
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //将该Window添加到屏幕。
                    //mWindowSession实现了IWindowSession接口,它是Session的客户端Binder对象.
                    //addToDisplay是一次AIDL的跨进程通信,通知WindowManagerService添加IWindow
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
                } catch (RemoteException e) {
                    /*******部分代码省略**********/
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                /*******部分代码省略**********/
                //设置当前View的mParent
                view.assignParent(this);
                /*******部分代码省略**********/
            }
        }
    }
}
requestLayout(); 方法请求view绘制,其过程主要是在ViewRootImplperformTraversals方法中。
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    /*******部分代码省略**********/
    //请求对界面进行布局
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    /*******部分代码省略**********/
    //安排任务
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
        }
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    //做任务
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
            try {
                //执行任务
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
}

整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法(这个方法巨长)开始的,该方法做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法。

private void performTraversals() {
        ......
        //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
        //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ......
        performDraw();
        ......
 }

这里的最外层根视图是DecorView,也就是mView,在WindowManagerGlobal 中的addview中传递过来的。

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 = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
        break;
        ......
    }
    return measureSpec;
}

该方法的是用来测Root View的。上面传入参数后这个函数走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法组装一个MeasureSpecMeasureSpecspecMode等于EXACTLYspecSize等于windowSize,也就是为何根视图总是全屏的原因。

View的测量

ViewRootImpl调用performMeasure执行Window对应的View的测量。

  • ViewRootImpl的performMeasure;
  • DecorView(FrameLayout)的measure;
  • DecorView(FrameLayout)的onMeasure;
  • DecorView(FrameLayout)所有子View的measure
private fun performMeasure(childWidthMeasureSpec: Int, childHeightMeasureSpec: Int) {
    if (mView == null) {
        return
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure")
    try {
        //mView在Activity中为DecorView(FrameLayout)
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW)
    }
}
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    ......
    //final方法,子类不可重写
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        //回调onMeasure()方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }
}

为整个View树计算实际的大小,然后设置实际的高和宽,每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。

int widthMeasureSpec:他由两部分组成,高2位表示MODE,定义在MeasureSpec类(View的内部类)中,有三种类型,MeasureSpec.EXACTLY表示确定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不确定。低30位表示size,也就是父View的大小。对于系统Window类的DecorVIew对象Mode一般都为MeasureSpec.EXACTLY ,而size分别对应屏幕宽高。对于子View来说大小是由父View和子View共同决定的。

//View的onMeasure默认实现方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

onMeasure默认的实现仅仅调用了setMeasuredDimension,它对View的成员变量mMeasuredWidthmMeasuredHeight变量赋值,measure的主要目的就是对View树中的每个View的mMeasuredWidthmMeasuredHeight进行赋值,所以一旦这两个变量被赋值意味着该View的测量工作结束。

默认的尺寸大小即传入的参数都是通过getDefaultSize返回的,我们就看一下该方法的实现。

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    //通过MeasureSpec解析获取mode与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;
}

specMode等于AT_MOSTEXACTLY就返回specSize

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的。也只有当Mode为MeasureSpec.UNSPECIFIED时才会使用该尺寸。

到此一次最基础的元素View的measure过程就完成了。

View实际是嵌套的,而且measure是递归传递的,所以每个View都需要measure,能够嵌套的View都是ViewGroup的子类,所以在ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量,measureChildren内部实质只是循环调用measureChildmeasureChildmeasureChildWithMargins的区别就是是否把marginpadding也作为子视图的大小。ViewGroup本身不调用measureChildWithMarginsmeasureChildren方法,由继承类通过for循环调用此方法进行子View的测量。下面看一下ViewGroup中稍微复杂的measureChildWithMargins方法。

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    //获取子视图的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //调整MeasureSpec
    //通过这两个参数以及本身的LayoutParams来共同决定子视图的测量规则
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
    //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

该方法就是对父视图提供的measureSpec参数结合子视图的LayoutParams参数进行了调整,然后再来调用child.measure()方法,具体通过方法getChildMeasureSpec来进行参数调整。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //获取当前Parent View的Mode和Size
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
    int size = Math.max(0, specSize - padding);
    //定义返回值存储变量
    int resultSize = 0;
    int resultMode = 0;
    //依据当前Parent的Mode进行switch分支逻辑
    switch (specMode) {
    // Parent has imposed an exact size on us
    //默认Root View的Mode就是EXACTLY
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            //如果child的layout_wOrh属性在xml或者java中给予具体大于等于0的数值
            //设置child的size为真实layout_wOrh属性值,mode为EXACTLY
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT
            // Child wants to be our size. So be it.
            //设置child的size为size,mode为EXACTLY
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT
            //设置child的size为size,mode为AT_MOST
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    ......
    //其他Mode分支类似
    }
    //将mode与size通过MeasureSpec方法整合为32位整数返回
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec的规则

getChildMeasureSpec的逻辑是通过其父View提供的MeasureSpec参数得到specModespecSize,然后根据计算出来的specMode以及子View的childDimension(layout_width或layout_height)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension

最终决定View的measure大小是View的setMeasuredDimension方法,所以我们可以通过setMeasuredDimension设定死值来设置View的mMeasuredWidthmMeasuredHeight的大小,但是一个好的自定义View应该会根据子视图的measureSpec来设置mMeasuredWidthmMeasuredHeight的大小,这样的灵活性更大

View测量总结

ActivityonResume之后,当前ActivityWindow对象中的View(DecorView)会被添加在WindowManager中。也就是在ActivityThreadhandleResumeActivity方法中调用wm.addView(decor, l);将DecorView添加到WindowManager中;

WindowManager继承ViewManager,它的实现类为WindowManagerImpl,该类中的方法的具体实现是由其代理类WindowManagerGlobal实现的;

在它的addView方法中会创建ViewRootImpl的实例,然后将Window对应的View(DecorView),ViewRootImpl,LayoutParams顺序添加在WindowManager中,最后将Window所对应的View设置给创建的ViewRootImpl,通过ViewRootImpl来更新界面并完成Window的添加过程;

设置view调用的是ViewRootImplsetView方法,在该方法中调用requestLayout();方法来异步执行view的绘制方法;之后将Window添加到屏幕,通过WMS(跨进程通信)

requestLayout方法中最终会调用ViewRootImplperformTraversals();方法,该方法做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法:performMeasureperformLayoutperformDraw;

performMeasure方法中调用的是Viewmeasure方法,该方法是final修饰,不能被子类重写,在该方法中实际调用的是ViewonMeasure方法,子类可以重写onMeasure方法来实现自己的测量规则。

View默认的onMeasure方法很简单只是调用了setMeasuredDimension方法,该方法的作用是给View的成员变量mMeasuredWidthmMeasuredHeight赋值,View的测量主要就是给这两个变量赋值,这两个变量一旦赋值,也就意味着测量过程的结束。

setMeasuredDimension方法传入的尺寸是通过getDefaultSize(int size, int measureSpec);方法返回的,在
getDefaultSize方法中解析measureSpecModeSize,如果Mode为MeasureSpec.AT_MOST或者MeasureSpec.EXACTLY,最终的size的值为解析后的size;如果ModeMeasureSpec.UNSPECIFIED,最终的size为建议的最小值=getSuggestedMinimumWidth,该方法的具体实现为return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());,建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的

measureSpec是由getRootMeasureSpec方法决定的:measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);根布局的大小是Window的大小,Window大小是不能改变的,总是全屏的。

View实际是嵌套的,而且measure是递归传递的,所以每个View都需要measure,能够嵌套的View都是ViewGroup的子类,所以在ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量,measureChildren内部实质只是循环调用measureChildmeasureChildmeasureChildWithMargins的区别就是是否把marginpadding也作为子视图的大小。

measureChildWithMargins方法的作用就是对父View提供的measureSpec参数结合子ViewLayoutParams参数进行了调整,然后再来调用child.measure()方法,具体通过方法getChildMeasureSpec方法来进行参数调整。计算出来自身的measureSpec作为调用其子视图measure方法的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension

最终决定Viewmeasure大小是ViewsetMeasuredDimension方法,该方法就是设置mMeasuredWidth和mMeasuredHeight的大小,ViewGroup在onMeasure 方法调用setMeasuredDimension之前调整了measureSpec

Kotlin项目实战

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

推荐阅读更多精彩内容