Android 復(fù)盤——你真的了解 setContentView 嗎舒岸?

# 1. AppCompatDelegate 的 setContentView()

分析 Android 中的 View蛾派,我們先從進(jìn)入應(yīng)用的看到的的一個 View 入手个少,第一個 View 就是 通過 setContentView() 這個方法進(jìn)行加載的夜焦。我們來看 setContentView() 的源碼:

public void setContentView(@LayoutRes int layoutResID) {
    this.getDelegate().setContentView(layoutResID);
}

AppCompatActivity 中的 setContentView() 又調(diào)用了 AppCompatDelegate 中的 setContentView() 方法,那 AppCompatDelegate 是做什么的呢巷波?

AppCompat 出現(xiàn)在 v7 包,它的作用是讓 API 等級在 7 之上的設(shè)備也能使用 ActionBar锉屈,在 v7:21 之后垮耳,AppCompat 可以讓 API 在 7 之上的設(shè)備使用 MD、ToolBar 等效果俊嗽。之前的 ActionBarActivity 也被取代為 AppCompatActivity绍豁。但 AppCompatActivity 的內(nèi)部回調(diào)是由 AppCompatDelegate 來實(shí)現(xiàn)的豌研。

AppCompatDelegate 是一個抽象類,它的實(shí)現(xiàn)類是 AppCompatDelegateImpl鬼佣,現(xiàn)在看 AppCompatDelegateImpl 中的 setContentView() 方法:

public void setContentView(int resId) {
    // 創(chuàng)建 DecorView晶衷,DecorView 是視圖中的頂級 View
    this.ensureSubDecor();
    // 獲取 DecorView 中的 content 部分
    ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
    contentParent.removeAllViews();
    // 將我們編寫的界面填充到 content 中
    LayoutInflater.from(this.mContext).inflate(resId, contentParent);
    this.mOriginalWindowCallback.onContentChanged();
}

# 2. DecorView

在 AppCompatDelegateImpl 的 setContentView() 中阴孟,通過 ensureSubDecor() 方法為視圖創(chuàng)建 DecorView永丝,

private void ensureSubDecor() {
    if (!this.mSubDecorInstalled) {
        // DecorView 不存在,調(diào)用 createSubDecor() 創(chuàng)建 DecorView
        this.mSubDecor = this.createSubDecor();
        CharSequence title = this.getTitle();
        if (!TextUtils.isEmpty(title)) {
            if (this.mDecorContentParent != null) {
                this.mDecorContentParent.setWindowTitle(title);
            } else if (this.peekSupportActionBar() != null) {
                this.peekSupportActionBar().setWindowTitle(title);
            } else if (this.mTitleView != null) {
                this.mTitleView.setText(title);
            }
        }

        this.applyFixedSizeWindow();
        this.onSubDecorInstalled(this.mSubDecor);
        this.mSubDecorInstalled = true;
        AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
        if (!this.mIsDestroyed && (st == null || st.menu == null)) {
            this.invalidatePanelMenu(108);
        }
    }
}

private ViewGroup createSubDecor() {
    // 獲取設(shè)置的主題屬性
    TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
    // 如果使用的主題不是 Theme.AppCompat哥牍,或者沒又繼承自 Theme.AppCompat嗅辣,拋出異常澡谭。
    if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
        throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
    } else {
        // 根據(jù)主題的屬性進(jìn)行設(shè)置
        if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
            // 在 requestWindowFeature() 方法中
            // 設(shè)置 this.mWindowNoTitle = true
            this.requestWindowFeature(1);
        } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) {
            this.requestWindowFeature(108);
        }

        if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            this.requestWindowFeature(109);
        }

        if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            this.requestWindowFeature(10);
        }
    
        // 記錄是否為浮動的主題
        this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();
        this.mWindow.getDecorView();
        LayoutInflater inflater = LayoutInflater.from(this.mContext);
        ViewGroup subDecor = null;
        
        // 根據(jù)不同的設(shè)置蛙奖,給 subDecor 填充內(nèi)容
        if (!this.mWindowNoTitle) {
            if (this.mIsFloating) {
                // Dialog 的主題
                subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
                this.mHasActionBar = this.mOverlayActionBar = false;
            } else if (this.mHasActionBar) {
                // 添加 ActionBar
                TypedValue outValue = new TypedValue();
                this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true);
                Object themedContext;
                if (outValue.resourceId != 0) {
                    themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId);
                } else {
                    themedContext = this.mContext;
                }

                subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
                this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);
                this.mDecorContentParent.setWindowCallback(this.getWindowCallback());
                if (this.mOverlayActionBar) {
                    this.mDecorContentParent.initFeature(109);
                }

                if (this.mFeatureProgress) {
                    this.mDecorContentParent.initFeature(2);
                }

                if (this.mFeatureIndeterminateProgress) {
                    this.mDecorContentParent.initFeature(5);
                }
            }
        } else {
            if (this.mOverlayActionMode) {
                subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
            } else {
                subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
            }

            if (VERSION.SDK_INT >= 21) {
                ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() {
                    public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                        int top = insets.getSystemWindowInsetTop();
                        int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top);
                        if (top != newTop) {
                            insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
                        }

                        return ViewCompat.onApplyWindowInsets(v, insets);
                    }
                });
            } else {
                ((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() {
                    public void onFitSystemWindows(Rect insets) {
                        insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top);
                    }
                });
            }
        }

        // 把 DecorView 添加到 Window 上 并且返回 DecorView
        if (subDecor == null) {
            throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
        } else {
            if (this.mDecorContentParent == null) {
                this.mTitleView = (TextView)subDecor.findViewById(id.title);
            }

            ViewUtils.makeOptionalFitsSystemWindows(subDecor);
            ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
            ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
            if (windowContentView != null) {
                while(windowContentView.getChildCount() > 0) {
                    View child = windowContentView.getChildAt(0);
                    windowContentView.removeViewAt(0);
                    contentView.addView(child);
                }

                windowContentView.setId(-1);
                contentView.setId(16908290);
                if (windowContentView instanceof FrameLayout) {
                    ((FrameLayout)windowContentView).setForeground((Drawable)null);
                }
            }
            
            // 把 DecorView 添加到 Window 上 
            this.mWindow.setContentView(subDecor);
            contentView.setAttachListener(new OnAttachListener() {
                public void onAttachedFromWindow() {
                }

                public void onDetachedFromWindow() {
                    AppCompatDelegateImpl.this.dismissPopups();
                }
            });
            return subDecor;
        }
    }
}

創(chuàng)建好 DecorView 之后崎脉,DecorView 會被添加到 Windows(實(shí)現(xiàn)類是 PhoneWindow) 中,然后返回 DecorView祭衩。
并且 DecorView 是視圖的頂級容器阅签,我們可以通過 Android Studio 的 Layout Inspector 來查看一個界面的 View Tree。


View tree

一個 DecorView 包含這一個縱向的 LinearLayout路克,LinearLayout 內(nèi)部是一個 FrameLayout精算,F(xiàn)rameLayout 是一個包含了內(nèi)容部分和標(biāo)題部分的容器碎连。

在 AppCompatDelegateImpl 中的 setContentView() 方法中還有一句:

ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);

這句代碼得到的 contentParent 就是剛剛創(chuàng)建的 DecorView 中的 內(nèi)容根部局(id/content (ContentFrameLayout))。

ContentFrameLayout 是 FrameLayout 的子類廉嚼,我們編寫的 xml 布局就是被添加到它的內(nèi)部倒戏。

然后查看為內(nèi)容根布局添加視圖的過程。

# 3. LayoutInflater 的 inflate()

inflate() 的代碼如下:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    
    // 獲取解析當(dāng)前布局 xml 文件的 parser 對象
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        // 調(diào)用 inflate() 方法傍念,開始解析 xml 文件葱椭,并返回得到界面
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

在上述方法中孵运,會先獲取解析 xml 文件的 parser 對象,然后調(diào)用另一個 infalte() 方法進(jìn)行解析驳概。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // 通過 while 循環(huán)遍歷 xml 中的節(jié)點(diǎn),直到找到 root
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            final String name = parser.getName();

            // 如果是 merge 節(jié)點(diǎn)更卒,執(zhí)行 rInflate() 方法稚照,按照層次遞歸的去實(shí)例化 xml 文件的子項(xiàng)
            if (TAG_MERGE.equals(name)) {
                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
                // 不是 merge 節(jié)點(diǎn)果录,就通過 tag 標(biāo)簽創(chuàng)建一個 view
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                // 將創(chuàng)建的 View 添加到 root 視圖中
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(parser.getPositionDescription()
                    + ": " + e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        return result;
    }
}

在這個 inflate() 方法中辨萍,會先尋找 xml 文件中的起始節(jié)點(diǎn)返弹,如果起始節(jié)點(diǎn)是 merge,就執(zhí)行 rInflate() 方法拉背,如果不是 merge并扇,就執(zhí)行 createViewFromTag() 方法去創(chuàng)建一個新的 View,最后把 View 添加到內(nèi)容根部局中土陪。

# 4. createViewFromTag()

現(xiàn)在看 createViewFromTag() 的源碼:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    // 如果 name 的值為 blink鬼雀,返回一個 BlinkLayout
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        // 依次尋找合適的 Factory 對象去創(chuàng)建 View
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    }
}

我們看一看代碼中的 mFactory2源哩、mFactory鸦做、mPrivateFactory 是什么。

在 LayoutInflater.java 的屬性中坛掠,有如下幾個變量:

private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;

Factory 是一個接口,F(xiàn)actory2 是繼承了 Factory 的接口舷蒲,它們都有個一個 onCreateView() 的方法友多。

# 5. onCreateView()

我們?nèi)タ此鼈冊?AppCompatDelegateImpl 中的實(shí)現(xiàn):

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
    if (this.mAppCompatViewInflater == null) {
        TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
        String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
        if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
            try {
                Class viewInflaterClass = Class.forName(viewInflaterClassName);
                this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
            } catch (Throwable var8) {
                Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
                this.mAppCompatViewInflater = new AppCompatViewInflater();
            }
        } else {
                this.mAppCompatViewInflater = new AppCompatViewInflater();
        }
    }

    boolean inheritContext = false;
    if (IS_PRE_LOLLIPOP) {
        inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
    }

    return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}

方法的最后可以看出創(chuàng)建視圖的工作交給了 AppCompatViewInflater 的 createView() 去完成

# 6. createView()

final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    Context originalContext = context;
    if (inheritContext && parent != null) {
        context = parent.getContext();
    }

    if (readAndroidTheme || readAppTheme) {
        context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
    }

    if (wrapContext) {
        context = TintContextWrapper.wrap(context);
    }

    View view = null;
    byte var12 = -1;
    switch(name.hashCode()) {
    case -1946472170:
        if (name.equals("RatingBar")) {
            var12 = 11;
        }
        break;
    case -1455429095:
        if (name.equals("CheckedTextView")) {
            var12 = 8;
        }
        break;
    case -1346021293:
        if (name.equals("MultiAutoCompleteTextView")) {
            var12 = 10;
        }
        break;
    case -938935918:
        if (name.equals("TextView")) {
            var12 = 0;
        }
        break;
    case -937446323:
        if (name.equals("ImageButton")) {
            var12 = 5;
        }
        break;
    case -658531749:
        if (name.equals("SeekBar")) {
            var12 = 12;
        }
        break;
    case -339785223:
        if (name.equals("Spinner")) {
            var12 = 4;
        }
        break;
    case 776382189:
        if (name.equals("RadioButton")) {
            var12 = 7;
        }
        break;
    case 1125864064:
        if (name.equals("ImageView")) {
            var12 = 1;
        }
        break;
    case 1413872058:
        if (name.equals("AutoCompleteTextView")) {
            var12 = 9;
        }
        break;
    case 1601505219:
        if (name.equals("CheckBox")) {
            var12 = 6;
        }
        break;
    case 1666676343:
        if (name.equals("EditText")) {
            var12 = 3;
        }
        break;
    case 2001146706:
        if (name.equals("Button")) {
            var12 = 2;
        }
    }

    switch(var12) {
    case 0:
        view = this.createTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 1:
        view = this.createImageView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 2:
        view = this.createButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 3:
        view = this.createEditText(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 4:
        view = this.createSpinner(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 5:
        view = this.createImageButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 6:
        view = this.createCheckBox(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 7:
        view = this.createRadioButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 8:
        view = this.createCheckedTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 9:
        view = this.createAutoCompleteTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 10:
        view = this.createMultiAutoCompleteTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 11:
        view = this.createRatingBar(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 12:
        view = this.createSeekBar(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    default:
        view = this.createView(context, name, attrs);
    }

    if (view == null && originalContext != context) {
        view = this.createViewFromTag(context, name, attrs);
    }

    if (view != null) {
        this.checkOnClickListener((View)view, attrs);
    }

    return (View)view;
}

通過 createTextView() 等源碼可以發(fā)現(xiàn),creatView() 方法把常用的組件都變成了 AppCompat 的類型藐窄,從而達(dá)到了兼容的目的酬土。

至此撤缴,setContentView() 的流程就走完了叽唱。但是添加好布局文件之后,視圖并不會顯示到界面上棺亭,還需要通過 WindowsManagerService 去渲染界面才能使界面顯示。這部分到內(nèi)容在后面會講到嗽桩。

零碎的東西很多凄敢,為了方便大家記憶,我把上面的內(nèi)容做成了思維導(dǎo)圖扑庞,需要的朋友可以保存下來拒逮,偶爾看一下,幫助自己記憶栅隐。

未命名.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末约啊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子记盒,更是在濱河造成了極大的恐慌外傅,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碾盟,死亡現(xiàn)場離奇詭異技竟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)熙尉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門检痰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锨推,“玉大人,你說我怎么就攤上這事椎椰〗醯#” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵套媚,是天一觀的道長堤瘤。 經(jīng)常有香客問我,道長本辐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任老虫,我火速辦了婚禮祈匙,結(jié)果婚禮上天揖,老公的妹妹穿的比我還像新娘。我一直安慰自己些阅,他們只是感情好斑唬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恕刘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衡便,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天镣陕,我揣著相機(jī)與錄音,去河邊找鬼岂嗓。 笑死鹊碍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的公罕。 我是一名探鬼主播耀销,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼罐柳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起齿梁,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤士飒,失蹤者是張志新(化名)和其女友劉穎蔗崎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芳撒,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡未桥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年冬耿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片日月。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡爱咬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出精拟,到底是詐尸還是另有隱情虱歪,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布荡碾,位于F島的核電站局装,受9級特大地震影響劳殖,放射性物質(zhì)發(fā)生泄漏拨脉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一矛缨、第九天 我趴在偏房一處隱蔽的房頂上張望帖旨。 院中可真熱鬧,春花似錦落竹、人聲如沸货抄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怪与。三九已至,卻和暖如春分别,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工煌往, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轧邪,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓曲管,卻偏偏與公主長得像,于是被迫代替她去往敵國和親院水。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容