Android Window机制探索

一.Window 概述

Window表示一个窗口的概念,Android手机中所有的视图都是通过Window来呈现的,像常用的Activity,Dialog,PopupWindow,Toast,他们的视图都是附加在Window上的,所以可以这么说 ——「Window是View的直接管理者。」

window1.jpg

Window

一个顶级窗口查看和行为的一个抽象基类。这个类的实例作为一个顶级 View 添加到 Window Manager。它提供了一套标准的 UI 方法,比如添加背景,标题等等。当你需要用到 Window 的时候,你应该使用它的唯一实现类 PhoneWindow。

PhoneWindow

Window的具体实现类是PhoneWindow 在启动Activity的attach方法中被创建,Activity中setContentView实际上是调用 PhoneWindow 的setContentView 方法。并且 PhoneWindow 中包含着成员变量 DecorView。

//Activity  
public void setContentView(@LayoutRes int layoutResID) { 
    getWindow().setContentView(layoutResID); 
    initWindowDecorActionBar(); 
} 

//getWindow获取到的是mWindow          
//在attach方法里,mWindow = new PhoneWindow(this, window);

DecorView(FrameLayout)

作为顶级View,DecorView一般情况下它内部会包含一个竖直方向的LinearLayout,上面的标题栏(titleBar),下面是内容栏。通常我们在Activity中通过setContentView所设置的布局文件就是被加载到id为android.R.id.content的内容栏里(FrameLayout)

二.setContentView

setContentView主要包含三个操作:1.初始化 2.填充Layout 3.通知Activity布局改变。

//PhoneWindow 

    @Override 
    public void setContentView(int layoutResID) { 
        if (mContentParent == null) { 

//1.初始化 
        //创建DecorView对象和mContentParent对象  
            installDecor(); 
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 
            mContentParent.removeAllViews();//Activity转场动画相关 
        } 

//2.填充Layout 
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, 
                    getContext()); 
            transitionTo(newScene);//Activity转场动画相关 
        } else { 
        //将Activity设置的布局文件,加载到mContentParent中 
            mLayoutInflater.inflate(layoutResID, mContentParent); 
        } 

        //让DecorView的内容区域延伸到systemUi下方,防止在扩展时被覆盖,达到全屏、沉浸等不同体验效果。 
        mContentParent.requestApplyInsets(); 

//3.通知Activity布局改变 
        final Callback cb = getCallback(); 
        if (cb != null && !isDestroyed()) { 

        //触发Activity的onContentChanged方法   
            cb.onContentChanged(); 
        } 
        mContentParentExplicitlySet = true; 
    }

1.初始化:installDecor()---创建DecorView对象和mContentParent对象

这个方法主要完成DecorView的创建和mContentParent对象,前面已经介绍过,DecorView是Activity中的顶级View,包括TitleBar和ContentView。mContentParent是com.android.internal.R.id.content所对应的ViewGroup即DecorView中的ContentView。

//PhoneWindow  --> setContentView() 
private void installDecor() { 
        if (mDecor == null) { 
        //调用该方法创建new一个DecorView 
            mDecor = generateDecor(); 
        }else { 
            mDecor.setWindow(this); 
        } 
        if (mContentParent == null) { 
        //根据主题theme设置对应的xml布局文件以及Feature(包括style,layout,转场动画,属性等)到DecorView中。 
        //并将mContentParent绑定至id为ID_ANDROID_CONTENT(com.android.internal.R.id.content)的ViewGroup 
        //mContentParent在DecorView添加的xml文件中 
            mContentParent = generateLayout(mDecor); 
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate. 
            mDecor.makeOptionalFitsSystemWindows();           
            //添加其他资源 
            //设置转场动画 
        } 
} 
mDecor = generateDecor()---创建DecorView
protected DecorView generateDecor(){
    return new DecorView(getContext(), -1);
}
mContentParent = generateLayout(mDecor);---创建mContentParent
//PhoneWindow --> setContentView()  -->installDecor()  


 protected ViewGroup generateLayout(DecorView decor) { 
        // Apply data from current theme. 
        //获取当前的主题,加载默认资源和布局 
        TypedArray a = getWindowStyle(); 

        ... 
        //根据theme的设定,找到对应的Feature(包括style,layout,转场动画,属性等) 
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { 
            requestFeature(FEATURE_NO_TITLE);//无titleBar 
        }  

        ... 

        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { 
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));//设置全屏 
        } 

        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, 
                false)) { 
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS 
                    & (~getForcedWindowFlags()));//透明状态栏 
        } 

        //根据当前主题,设定不同的Feature 
        ... 

        int layoutResource; 
        int features = getLocalFeatures(); 

        //由于布局较多,我们拿有titleBar的例子来看 
        if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 

            ... 
                layoutResource = R.layout.screen_title; 

        }  
        ... 
        else {//无titleBar 
            layoutResource = R.layout.screen_simple; 
        } 

        mDecor.startChanging(); 

        //将布局layout,添加至DecorView中 
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 

        //从布局中获取`ID_ANDROID_CONTENT`,并关联至contentParent 
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 

        ... 
        //配置完成,DecorView根据已有属性调整布局状态 
        mDecor.finishChanging(); 

        return contentParent; 
    }

这个方法的前面会根据主题来做不同的处理,最后通过mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)将布局layout添加至DecorView中,并关联mContentParent。

整个setContentView的过程如下:
window2.jpg

二.Window的类型

Window有三种类型,分别是应用Window、子Window、系统window。应用Window对应着一个Activity。子Window不能单独存在,他需要附属在特定的父Window之中,比如常见的一些Dialog。系统Window是要声明权限才能创建的Window,比如Toast和系统状态栏。

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window上面。在三类window中,应用Window的层级范围是1-99,子Window的层级范围是1000-1999,系统Window的层级范围是2000-2999,这些层级范围对应这WindowManager.LayoutParam的type参数。当我们需要使用系统Window时,需要声明权限。

三.Window的内部机制

Window是一个抽象的概念,每一个Window都对应着一个View和ViewRootImpl,Window和View通过ViewRootImpl来建立联系。因此Window并不是不存在的,它是以View的形式存在。这点从ViewManager的定义也可以看得出来,它只提供三个接口方法:addView、updateViewLayout、removeView,这些方法都是针对View的。这说明View才是Window存在的实体。

Window的创建过程

首先要分析Window的创建过程,就必须了解Activity的启动过程。
Activity的启动过程很复杂,最终会由ActivityThread中的handleLaunchActivity()来完成整个启动过程。
在这个方法中会通过performLaunchActivity()方法创建Activity,performLaunchActivity()内部通过类加载器创建Activity的实例对象,并调用其attach()方法为其关联运行过程中所依赖的一系列上下文环境变量以及创建与绑定窗口。

//ActivityThread

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ... 
    //获取WindowManagerService的Binder引用(proxy端)。
    WindowManagerGlobal.initialize();    
    //会调用Activity的onCreate,onStart,onResotreInstanceState方法
    Activity a = performLaunchActivity(r, customIntent);    if (a != null) {
        ... 
        //会调用Activity的onResume方法.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        ... 
    } 

}

//ActivityThread

   private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {        
    //通过类加载器创建Activity
        Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        ...       

        //通过LoadedApk的makeApplication方法来创建Application对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);        
    if (activity != null) {
            ... 
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window);
            ... 

            //onCreate
            mInstrumentation.callActivityOnCreate(activity, r.state);            
            //onStart
            activity.performStart();

        }        
            return activity;
    }

在Activity的attach()方法里,系统会创建Activity所属的Window对象并为其设置回调接口,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中的方法很多,下面举几个比较眼熟的方法。

public interface Callback { 

        public boolean dispatchTouchEvent(MotionEvent event); 

        public View onCreatePanelView(int featureId); 

        public boolean onMenuItemSelected(int featureId, MenuItem item); 

        public void onContentChanged(); 

        public void onWindowFocusChanged(boolean hasFocus); 

        public void onAttachedToWindow(); 

        public void onDetachedFromWindow(); 
    } 

//Activity 

final void attach(...) { 
        //绑定上下文 
        attachBaseContext(context); 

        //创建Window,PhoneWindow是Window的唯一具体实现类 
        mWindow = new PhoneWindow(this, window);
        //此处的window==null,但不影响 
        mWindow.setWindowControllerCallback(this); 
        mWindow.setCallback(this); 
       ...        //设置WindowManager 
        mWindow.setWindowManager( 
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
                mToken, mComponent.flattenToString(), 
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
        if (mParent != null) { 
            mWindow.setContainer(mParent.getWindow()); 
        } 
        //创建完后通过getWindowManager就可以得到WindowManager实例 
        mWindowManager = mWindow.getWindowManager();//其实它是WindowManagerImpl 

    } 


    @Override 
    public Object getSystemService(@ServiceName @NonNull String name) { 
        ...        if (WINDOW_SERVICE.equals(name)) { 
            return mWindowManager; 
        }  
        return super.getSystemService(name); 
    }

在ActivityThread中的attach方法会创建Window的实现类PhoneWindow,并且通过getWindowManage得到WindowManager实例,也就是WindowManagerImpl。
因为PhoneWindow中并没有setWindowManager()方法,所以我们打开Window类看看。

//Window 
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, 
            boolean hardwareAccelerated) { 
    mAppToken = appToken; 
    mAppName = appName; 
    mHardwareAccelerated = hardwareAccelerated 
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); 
    if (wm == null) { 
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
    } 
    //在此处创建mWindowManager  
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 
} 
//在WindowManagerImpl类中 
public WindowManagerImpl createLocalWindowManager(Window parentWindow) { 
    return new WindowManagerImpl(mContext, parentWindow); 
}

到了这里Window已经创建完毕,在上面的performLaunchActivity()方法中attach方法后我们可以看到调用了onCreate()方法:

//ActivityThread --> performLaunchActivity
mInstrumentation.callActivityOnCreate(activity, r.state);

回调Activity的onCreate方法,通过setContentView()将view添加到DecorView的mContentParent中,也就是将资源布局文件和phoneWindow关联。

经过了上面几个过程,Window和DecorView已经被创建并初始化完毕,Activity的布局文件也成功添加到了DecorView的mContentParent中,但这个时候的DecorView还没有被WindowManager正式添加到Window中。

这里需要理解的是,Window更多表示的是一种抽象功能集合,虽然说早在Activity的attach方法中window就已经被创建了,但是这个时候由于DecorView并没有被WindowManager识别,所以这个时候的Window暂时无法提供具体功能。
总的来说,Window可以成功使用有2个标志:
①View绘制完毕,可以呈现给用户。
②View可以接收外界信息(触摸事件等)。

Window的添加过程

PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View,作为窗口添加到 WmS 的过程是由 WindowManager 来完成的。WindowManager 的具体实现是 WindowManagerImpl。

下面我们继续来分析handleLaunchActivity()方法中handleResumeActivity()的执行过程。

//ActivityThread 

final void handleResumeActivity(IBinder token, 
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { 

        //把activity数据记录更新到ActivityClientRecord 
        ActivityClientRecord r = mActivities.get(token); 

        r = performResumeActivity(token, clearHide, reason); 

        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; 
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 

             ... 
                if (a.mVisibleFromClient && !a.mWindowAdded) { 
                    a.mWindowAdded = true; 
                    wm.addView(decor, l);//把decor添加到窗口上(划重点) 
                } 
            }  
                //屏幕参数发生了改变 
                performConfigurationChanged(r.activity, r.tmpConfig); 

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

                    if (r.activity.mVisibleFromClient) { 
                        ViewManager wm = a.getWindowManager(); 
                        View decor = r.window.getDecorView(); 
                        wm.updateViewLayout(decor, l);//更新窗口状态 
                    } 

                ...                if (r.activity.mVisibleFromClient) { 
                    //已经成功添加到窗口上了(绘制和事件接收),设置为可见 
                    r.activity.makeVisible(); 
                } 
            //通知ActivityManagerService,Activity完成Resumed 
             ActivityManagerNative.getDefault().activityResumed(token); 
        }  
    }

在上面代码中,首先配置ActivityClientRecord,之后将DecorView设置为INVISIBLE,因为View并未绘制完成,当前的DecorView只是一个有结构的空壳。

然后通过WindowManagerImpl将DecorView正式的添加到窗口上wm.addView(decor, l);,这一步非常非常重要,因为它包括了2个比较重要和常见的过程:Window的添加过程和View的绘制流程。

window3.png

窗口的添加过程如上图所示,我们知道 WmS 运行在单独的进程中。这里 IWindowSession 执行的 addtoDisplay 操作应该是 IPC 调用。接下来的Window添加过程,我们会知道每个应用窗口创建时,最终都会创建一个 ViewRootImpl 对象。

ViewRootImpl 是一很重要的类,类似 ApplicationThread 负责跟AmS通信一样,ViewRootImpl 的一个重要职责就是跟 WmS 通信,它通静态变量 sWindowSession(IWindowSession实例)与 WmS 进行通信。

每个应用进程,仅有一个 sWindowSession 对象,它对应了 WmS 中的 Session 子类,WmS 为每一个应用进程分配一个 Session 对象。WindowState 类有一个 IWindow mClient 参数,是由 Session 调用 addToDisplay 传递过来的,对应了 ViewRootImpl 中的 W 类的实例。

简单的总结一下,ViewRootImpl通过IWindowSession远程IPC通知WmS,并且由W类接收WmS的远程IPC通知。(这个W类和ActivityThread的H类同样精悍的命名,并且也是同样的工作职责!)

图解完Window的添加过程,对整个流程有一个印象和思路,那么下面继续分析源码。

在上面的handleResumeActivity()方法中,我们看到源码通过wm.addView(decor, l);操作DecorView和WindowManager.LayoutParams。上面讲解也说过,因为WindowManager是接口,真正具体实现类是windowManagerImpl。

//WindowManagerImpl 


    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); 

    @Override 
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 
        applyDefaultToken(params); 
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 
    } 

    @Override 
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 
        applyDefaultToken(params); 
        mGlobal.updateViewLayout(view, params); 
    } 

    @Override 
    public void removeView(View view) { 
        mGlobal.removeView(view, false); 
    }

我们看到这个WindowManagerImpl对于Window(或者可以说是View)的操作都是交由WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。这种工作模式是桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。
在WindowManagerImpl的全局变量中通过单例模式初始化了WindowManagerGlobal,也就是说一个进程就只有一个WindowManagerGlobal对象。

//WindowManagerGlobal 


   public void addView(View view, ViewGroup.LayoutParams params, 
            Display display, Window parentWindow) { 
        if (view == null) { 
            throw new IllegalArgumentException("view must not be null"); 
        } 
        if (display == null) { 
            throw new IllegalArgumentException("display must not be null"); 
        } 
        if (!(params instanceof WindowManager.LayoutParams)) { 
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); 
        } 


        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; 
        if (parentWindow != null) { 
            //调整布局参数,并设置token 
            parentWindow.adjustLayoutParamsForSubWindow(wparams); 
        }  

        ViewRootImpl root; 
        View panelParentView = null; 

        synchronized (mLock) { 


            int index = findViewLocked(view, false); 
            if (index >= 0) { 
                if (mDyingViews.contains(view)) { 
                    //如果待删除的view中有当前view,删除它 
                    // Don't wait for MSG_DIE to make it's way through root's queue. 
                    mRoots.get(index).doDie(); 
                } 
                // The previous removeView() had not completed executing. Now it has. 
                //之前移除View并没有完成删除操作,现在正式删除该view 
            } 

            //如果这是一个子窗口个(popupWindow),找到它的父窗口。 
            //最本质的作用是使用父窗口的token(viewRootImpl的W类,也就是IWindow) 
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && 
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { 
                final int count = mViews.size(); 
                for (int i = 0; i < count; i++) { 
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) { 
                        panelParentView = mViews.get(i); 
                    } 
                } 
            } 
            //创建ViewRootImpl,并且将view与之绑定 
            root = new ViewRootImpl(view.getContext(), display); 

            view.setLayoutParams(wparams); 

            mViews.add(view);//将当前view添加到mViews集合中 
            mRoots.add(root);//将当前ViewRootImpl添加到mRoots集合中 
            mParams.add(wparams);//将当前window的params添加到mParams集合中 
        } 

         ...            //通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。 
            root.setView(view, wparams, panelParentView); 
    }

在WindowManagerGlobal的addView()方法里,最后调用ViewRootImpl的setView方法,处理添加过程。

//WindowManagerGlobal  -->  addView 
      //创建ViewRootImpl,并且将view与之绑定 
      root = new ViewRootImpl(view.getContext(), display); 

      //通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。 
      root.setView(view, wparams, panelParentView);

通过上面这个代码可知,WindowManagerGlobal将View的处理操作全权交给ViewRootImpl,而且上面我们也提到了,View成功添加到Window,无非就是展现视图和用户交互。

①View绘制完毕,可以呈现给用户。
②View可以接收外界信息(触摸事件等)。

在ViewRootImpl的setView()方法中,将会完成上面2个艰巨而又伟大的任务。

//ViewRootImpl  


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

                int res;  

                // Schedule the first layout -before- adding to the window 
                // manager, to make sure we do the relayout before receiving 
                // any other events from the system. 
                requestLayout();//View的绘制流程 

                if ((mWindowAttributes.inputFeatures 
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { 
                    //创建InputChannel 
                    mInputChannel = new InputChannel(); 
                } 

                try { 

                    //通过WindowSession进行IPC调用,将View添加到Window上 
                    //mWindow即W类,用来接收WmS信息 
                    //同时通过InputChannel接收触摸事件回调 
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, 
                            getHostVisibility(), mDisplay.getDisplayId(), 
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, 
                            mAttachInfo.mOutsets, mInputChannel); 
                } 

                ... 

                    //处理触摸事件回调 
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, 
                            Looper.myLooper()); 

                ... 
    }

在ViewRootImpl的setView()方法里,执行requestLayout()方法完成View的绘制流程,并且通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。

通过mWindowSession来完成Window的添加过程 ,mWindowSession的类型是IWindowSession,是一个Bindler对象,真正的实现类是Session,也就是Window的添加是一次IPC调用。(mWindowSession在ViewRootImpl的构造函数中通过WindowManagerGlobal.getWindowSession();创建)

同时将mWindow(即 W extends IWindow.Stub)发送给WmS,用来接收WmS信息。

window4.png
//Session 


    final WindowManagerService mService; 

   @Override 
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, 
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, 
            Rect outOutsets, InputChannel outInputChannel) { 

        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, 
                outContentInsets, outStableInsets, outOutsets, outInputChannel); 
    }

如此一来,Window的添加请求就交给WmS去处理了,在WmS内部会为每一个应用保留一个单独的Session。在WmS 端会创建一个WindowState对象用来表示当前添加的窗口。 WmS负责管理这里些 WindowState 对象。至此,Window的添加过程就结束了。

至于Window的删除和更新过程,举一反三,也是使用WindowManagerGlobal对ViewRootImpl的操作,最终也是通过Session的IPC跨进程通信通知到WmS。整个过程的本质都是同出一辙的。

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

推荐阅读更多精彩内容