Activity 布局加载分析

关于Activity需要知道的更多内容

前言

关于Activity的创建以及执行回调onCreate方法我们已经知道了(看这里:https://www.jianshu.com/p/3de730c145be
),然后我们今天想知道我们在onCreate中调用setContentView方法的时候,xml是如何加载的,所以今天就跟着源码来看看。
ps:源码版本为 android-26

一般来说我们写一个XxxActivity都会去继承Activity、FragmentActivity、AppCompatActivity这3个的其中一个,我们今天就分别对这几个父类Activity去分析

  • 1、Activity的xml加载过程
  • 2、FragmentActivity的xml加载过程
  • 3、AppCompatActivity的xml加载过程

一、Activity的xml加载过程

进入我们setContentView方法

  ## Activity
  public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);//1、调用phoneWindow的setContentView方法
        initWindowDecorActionBar();
  }

这边注释的地方getWindow方法获取的一个window对象,它的实现类是PhoneWindow,它在Activity的attach中进行创建出来。

 ## Activity
 final void attach(Context context, ...,
            Window window, ActivityConfigCallback activityConfigCallback) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);//这边进行window对象的初始化
        ...
        mWindow.setColorMode(info.colorMode);
    }

我们跟进PhoneWindow,看看它里面的setContentView方法

##PhoneWindow
@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();//1、加载DecorView
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);//2、布局加载器
    }
    ...
}

注释1是加载DecorView,注释2通过布局加载器将xml解析成view树,并且将view树添加到mContentParent中

1、加载DecorView

### PhoneWindow
/**
 * 加载DecorView
 */
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);//1、创建DecorView
            ...
        } else {
            mDecor.setWindow(this);//2、DecorView绑定到Window
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//3、根据DecorView生成Layout,得到一个mContentParent
            ...
        }
    }

/*
 * 1、创建DecorView
 */
protected DecorView generateDecor(int featureId) {
    ... //这边去创建DecorView
    return new DecorView(context, featureId, this, getAttributes());
}

/*
 *2、调用DecorView的setWindow,传入PhoneWindow对象
 */
 void setWindow(PhoneWindow phoneWindow) {
        mWindow = phoneWindow;
        ...
 }


/*
 *3、根据DecorView生成Layout
 */
protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    //1、获取当前的主题,然后设置相关数据到window上
    TypedArray a = getWindowStyle();
    ...
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);//2、当前Window是否浮动在上面,默认为false
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);//3、传入window的宽、高
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }

    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {//4、window是否有title
        requestFeature(FEATURE_NO_TITLE);//5、请求指定Window的风格,它必须在setContentView之前
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestFeature(FEATURE_ACTION_BAR);
    }

    ...

    //6、这边会很多的设置窗口属性的判断和方法,具体可以大家可以去看源码

    ...

    // Inflate the window decor.
    //7、根据上面设置的窗口的属性 ,设置相应的 layoutResource
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        layoutResource = R.layout.screen_progress;
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                  R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
    }

    mDecor.startChanging();//8、开始DecorView的变化
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//9、根据布局文件xml加载得到View树

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//10、根据content id返回一个contentParent,是一个viewGroup
    

    mDecor.finishChanging(); //11、结束DecorView的变化

    return contentParent; //12、返回一个contentParent
}

在上面这段源码中,我们主要是做了几件事:

  • 1、注释1这里的样式通常是用来描述窗口的,通常view的样式是通过layout来描述的,而窗口的样式是通过Androidmanifest.xml来配置的,所以我们这里通过调用getWindowStyle方法,然后得到一个TypedArray对象,这个类我们比较熟悉,在我们自定义View的时候,如果需要自定义style,我们都会在初始化的时候得到一个TypedArray对象,然后去设置相关的样式。
  • 2、注释2返回一个boolean值,表示我们的window是否悬浮在上面,然后根据这个值去设置相关的window宽高和其他的标签,注释4和注释6的地方判断window是否有title或者是否没有ActionBar,然后通过注释5的地方调用requestFeature去设置相关属性,所以我们这边会是否能联想到我们去如果在Activity的onCreat方法里去设置全屏的时候,需要在setConentView之前去设置,因为在setContentView之后的话,installDecor方法就已经执行完毕了,那我们设置的window属性就没有作用了。
  • 3、注释7的地方根据上面设置的窗口的属性,来设置相关的layoutResource,我们可以去源码中查找相应的layout,具体位置在:frameworks/base/core/res/res/layout下


    image.png
##screen_simple
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

image.png
##screen_simple_overlay_action_mode
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
</FrameLayout>

我们能够发现这几个layout里面都包含了一个id为content的FrameLayout,它是用来添加我们自己的布局。

  • 4、注释9根据我们上面得到的布局文件加载onResourcesLoaded得到view树
##DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ...
   //创建一个DecorCaptionView,它是Decor的标题View, 它包含了标题和窗口控件按钮,它的可见性取决于工作空间和窗口类型
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);//1、用LayoutInflate来加载xml布局文件得到view
    //2、 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            //将 mDecorCaptionView 添加到DecorView
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        //获取的到root add到 mDecorCaptionView 中
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
        // 3、若mDecorCaptionView为 null, 则直接添加调用addView将 root 加到 DecorView 中
        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    // 4、强转成 ViewGroup, 传递给 mContentRoot
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}


private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
    ...
   //这里将源码稍微变动下,直接调用inflateDecorCaptionView方法
    DecorCaptionView decorCaptionView =  inflateDecorCaptionView(inflater)
    ...
    return decorCaptionView;
}

private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
    final Context context = getContext();
    inflater = inflater.from(context);
    //这边通过LayoutInflater来获到DecorCaptionView
    final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
            null);
    setDecorCaptionShade(context, view);
    return view;
}   

可以看到在onResourcesLoaded这里是我们做了4件事:

  • 4.1、注释1是根据LayoutInflate加载xml布局文件得到View对象 root
  • 4.2、注释2判断DecorCaptionView是否为null,不为null,先去添加DecorCaptionView,再去添加root(DecorCaptionView是一个标题view,这个类表示用于控制自由格式上窗口的特殊屏幕元素)
  • 4.3、注释3如果DecorCaptionView为null,就直接将root添加到DecorView中
  • 4.4、注释4是将root强转成一个ViewGroup并传递给mContentRoot
  • 5、在注释10这里根据我们的ID_ANDROID_CONTENT通过findViewById返回一个contentParent,它是我们在步骤3中的DecorView中mContentRoot中的FrameLayout的Id,也就是我们通过setContentView将布局添加进去的地方。
##Window
 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content

2、布局加载器加载xml生成View树

通过LayoutInflater布局加载器去生成View树

##LayoutInflater
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    ...
    final XmlResourceParser parser = res.getLayout(resource);//1、根据xml id 得到一个xml解析器资源
    try {
        return inflate(parser, root, attachToRoot);//2、传入xml解析器资源和mContentParent
    } finally {
        parser.close();
    }
}

  • 1、注释1得到一个xml解析器资源
  • 2、注释3 调用inflate返回view
##LayoutInflater
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ...
        View result = root;//1、它就是我们最终返回的view对象

        try {
            // Look for the root node.
            int type;

            ...
            
            final String name = parser.getName();//XmlPullParser解析xml,获取里面的标签name


            if (TAG_MERGE.equals(name)) {//这里获取的name如果是merge,则root不能为null,否则会抛出异常
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                //2、这边创建得到一个临时的view对象 temp
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                ...
                if (root != null && attachToRoot) {//3、这边判断如果root不为null,则将临时的view对象temp添加到root中
                    root.addView(temp, params);
                }

                if (root == null || !attachToRoot) {//4、如果root为null,将temp传递给我们的最终view
                    result = temp;
                }
            }

        }
        ...
        return result;
    }
}

这里通过一系列的判断和传递最终会得到一个View对象,它就是我们根据xml去解析加载出来的View树。

附加一张window和contentView的层次结构图

image.png

二、AppCompatActivity的xml加载

跟进AppCompatActivity的setContentView方法

##AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);//调用getDelegate的setContentView
}

这边的getDelegate返回的是一个AppCompatDelegate,它是一个抽象类,这边我们跟踪发现最后是在AppCompatDelegateImplV9中调用了setContentView方法

##AppCompatDelegateImplV9
@Override
public void setContentView(int resId) {
    ensureSubDecor();//1、创建DecorView
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//2、这边根据content id去返回一个contentParent
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//3、通过LayoutInflater布局加载器将xml解析得到View树,添加到contentParent中
    mOriginalWindowCallback.onContentChanged();
}

1、注释1创建DecorView

##AppCompatDelegateImplV9
private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();//调用createSubDecor方法
        ...
    }
}

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

    //获取当前的主题,然后设置相关数据到window上
    ...

    mWindow.getDecorView();//1、调用PhoneWindow的getDecorView方法

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;//定义一个viewgroup


    //2、根据window的样式不同去加载不同的layout
    if (!mWindowNoTitle) {
        if (mIsFloating) {
            ...
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);
            ...
        } else if (mHasActionBar) {
            ...
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                    .inflate(R.layout.abc_screen_toolbar, null);
            ...
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }

        ...
    }

    
    ...

    //3、下面具体分析
    // Make the decor optionally fit system windows, like the window's decor
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        // Change our content FrameLayout to use the android.R.id.content id.
        // Useful for fragments.
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);

        // The decorContent may have a foreground drawable set (windowContentOverlay).
        // Remove this as we handle it ourselves
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    //4、调用PhoneWindow的setContentView方法
    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);
    ...
    return subDecor;
}

在ensureSubDecor调用了createSubDecor方法:

  • 1、注释1调用了PhoneWindow的getDecorView方法,我们可以看到这里去调用了installDecor方法,这个方法我们在本篇的Activity的xml加载过程已经分析过了。
##PhoneWindow
@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}
  • 2、注释2根据不同的window主题样式去加载不同的layout,然后通过布局加载器将xml解析成subDecor,每一个layout中都会include一个名为abc_screen_content_include.xml的layout,这个layout包含id为action_bar_activity_content的ContentFrameLayout。
源码位置:/frameworks/support/v7/appcompat/res/layout/abc_dialog_title_material.xml

##abc_screen_simple_overlay_action_mode
<android.support.v7.widget.FitWindowsFrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/action_bar_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

    <include layout="@layout/abc_screen_content_include" />

    <android.support.v7.widget.ViewStubCompat
            android:id="@+id/action_mode_bar_stub"
            android:inflatedId="@+id/action_mode_bar"
            android:layout="@layout/abc_action_mode_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

</android.support.v7.widget.FitWindowsFrameLayout>

##abc_screen_content_include
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>
  • 3、在注释3调用调用findViewById找到id为action_bar_activity_content的contentView,然后在通过PhoneWindow找到id为content的windowContentView,然后去循环遍历windowContentView,将它的的子View添加到contentView中去,然后在把windowContentView的id设置为-1,contentView的id设置为原来windowContentView的id content,这个地方可以简单的理解为就是将DecorView(windowContentView)中的view转移到subDecor(contentView)中。

  • 4、注释4调用PhoneWindow的setContentView方法,将subDecor传递进去,然后将subDecor存放到mContentParent中,这边我们可以发现它和上一部分(Activity)的区别,在Activity中mContentParent是直接用来存放我们自定义xml布局的,在AppCompatActivity中,mContentParent会先存放一个subDecor,然后在subDecor才是真正存放我们的自定义xml布局。

##PhoneWindow
@Override
public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

一张层次结构图带你理解:

image.png

三、FragmentActivity的xml加载

关于FragmentActivity,我们可以发现它自己是没有实现setContentView的,它是直接调用了父类Activity的setContentView方法,所以我们可以看上面的Activity的xml加载过程就可以了。
但是为什么会有FragmentActivity以及它的作用呢,这就要从Fragment说起来,在Android3.0以前是没有Fragment的,为了能让3.0以前能使用Fragment我们需要引入了support包,然后去继承FragmentActivity就可以使用Fragment,在3.0以后我们可以选择直接继承Activity,就可以正常使用Fragment了。而且3.0以前获取FragmentManager的方法是getSupportFragmentManager,3.0以后直接用getFragmentManager就可以了。
关于Fragment和FragmentManager我们会在另外的文字里进行分析,这里就不多讲。

ps:关于FragmentActivity的api原文

FragmentActivity is a special activity provided in the Support Library to handle fragments on system versions older than API level 11. If the lowest system version you support is API level 11 or higher, then you can use a regular Activity.

沿着别人走过的路,跟上去发现不一样的风景

参考链接:

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