Android 源碼分析 - LayoutInflater創(chuàng)建View的流程分析

??在日常開(kāi)發(fā)中单绑,我經(jīng)常使用LayoutInflater將一個(gè)xml布局初始化為一個(gè)View對(duì)象,但是對(duì)它內(nèi)部原理的了解卻是少之又少测萎。今天星澳,我們就來(lái)看看LayoutInflater

??本文主要內(nèi)容:

  1. LayoutInflater創(chuàng)建流程髓迎。我們通過(guò)Activity或者LayoutInflater的from方法來(lái)創(chuàng)建一個(gè)對(duì)象峦朗,我們?nèi)タ纯催@倆方法有啥區(qū)別。
  2. View 創(chuàng)建流程排龄。主要介紹LayoutInflater將一個(gè)xml解析成為一個(gè)View經(jīng)歷的過(guò)程波势。
  3. setContentView方法解析。分析了View的創(chuàng)建流程橄维,我們?cè)賮?lái)看看是怎么初始化ActivityContentView尺铣。

??本文參考文章:

  1. 反思|Android LayoutInflater機(jī)制的設(shè)計(jì)與實(shí)現(xiàn)

1. LayoutInflater的創(chuàng)建流程

??熟悉LayoutInflater的同學(xué)應(yīng)該都知道,創(chuàng)建LayoutInflater對(duì)象有兩種方式:

  1. 通過(guò)ActivitygetLayoutInflater方法争舞。
  2. 通過(guò)LayoutInflater的from方法迄埃。

??可是這倆方法有啥區(qū)別呢?這是本節(jié)需要解答的地方兑障。

(1).Context結(jié)構(gòu)圖

??不過(guò)在此之前侄非,我們先來(lái)了解Context的繼承類(lèi)圖。


??可能有人會(huì)問(wèn)流译,我們分析LayoutInflater逞怨,為什么還要去了解Context的結(jié)構(gòu)呢?這是因?yàn)?code>LayoutInflater本身就是系統(tǒng)的一個(gè)服務(wù)福澡,是通過(guò)ContextgetSystemService方法來(lái)獲取的叠赦。

??根據(jù)源碼我們知道,所有的系統(tǒng)服務(wù)都是在SystemServiceRegistry類(lèi)里面進(jìn)行注冊(cè),然后統(tǒng)一在ContextImpl進(jìn)行獲取除秀,當(dāng)然也包括LayoutInflater糯累。

(2). 兩種方法的區(qū)別

??我們通過(guò)ActivitygetLayoutInflater方法獲取的實(shí)際上是Window里面的LayoutInflater對(duì)象,而Window的LayoutInflater對(duì)象是在構(gòu)造方法里面初始初始化的:

    public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }

??此時(shí)這個(gè)Context就是Activity的對(duì)象册踩。所以從本質(zhì)上來(lái)看,ActivitygetLayoutInflater方法和LayoutInflater的from方法沒(méi)有很大的區(qū)別泳姐,唯一區(qū)別的在于這個(gè)Context對(duì)象的不同。我們來(lái)看一看from方法:

    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

??在from方法里面調(diào)用的是ContextgetSystemService方法暂吉,現(xiàn)在我們必須得了解整個(gè)Context的繼承體系胖秒。
??假設(shè)這里的ContextActivity,那么這里調(diào)用的就是ContextgetSystemService方法

    @Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }

??那這里的mBase又是什么呢?從上面的類(lèi)圖我們知道是ContextImpl的對(duì)象慕的,怎么來(lái)證明呢阎肝?

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // ······
        // 1. 創(chuàng)建ContextImpl的對(duì)象
        ContextImpl appContext = createBaseContextForActivity(r);
        // ······
        // 2. 調(diào)用Activity的attach方法
                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, r.configCallback);
        // ······
    }
    //---------------Activity--------------------------
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
         // 將ContextImpl傳遞給父類(lèi)
        attachBaseContext(context);
        // ·······
    }
    //---------------ContextWapper---------------------
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

??整個(gè)調(diào)用鏈非常的清晰,分別是:ActivityThread#performLaunchActivity -> Activity#attach -> ContextWapper#attachBaseContext肮街。

??然后风题,我們?cè)偃タ纯?code>ContextImpl的getSystemService方法:

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

??最終的對(duì)象是從SystemServiceRegistry里面獲取的。

2. View的創(chuàng)建流程

??LayoutInflater是通過(guò)inflate方法將一個(gè)xml布局解析成為一個(gè)View嫉父。我們都知道inflate方法通常有三個(gè)參數(shù)俯邓,分別是:resourceroot熔号、attachToRoot稽鞭,表示的含義如下:

  1. resource:xml布局的id。
  2. root:解析成之后的View的父View引镊,此參數(shù)只在attachToRoot為true才生效朦蕴。
  3. attachToRoot:決定解析出來(lái)的View是否添加到root上。

??有人可能會(huì)好奇弟头,為什么需要第三個(gè)參數(shù)吩抓,這是因?yàn)槲覀儗ml解析成View不一定立即需要添加到一個(gè)ViewGroup中去,這是為什么呢赴恨?想一想RecyclerView疹娶,RecyclerView在初始化ItemView時(shí),不是立即將ItemView添加進(jìn)去伦连,而是當(dāng)ItemView 進(jìn)入屏幕可見(jiàn)區(qū)域時(shí)才會(huì)添加雨饺,因?yàn)?code>RecyclerView有預(yù)加載機(jī)制,會(huì)加載一部分屏幕外的ItemView惑淳。

??我們先看一下inflate方法:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            try {
                // ······
                // 1. 如果是merge標(biāo)簽额港,直接繞過(guò)merge標(biāo)簽,
                // 解析merge標(biāo)簽下面的View歧焦。
                if (TAG_MERGE.equals(name)) {
                    // ······
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 2.創(chuàng)建View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    // ······
                    // 3.遞歸解析children
                    rInflateChildren(parser, temp, attrs, true);
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    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;
            }
            return result;
        }
    }

??inflate方法里面一種做了2件事:

  1. 如果根View是merge移斩,直接遞歸解析它的子View。
  2. 如果根View不是merge,先解析根View向瓷,然后在遞歸解析它所有的child肠套。

??我們分為兩步來(lái)看,先看一下解析根標(biāo)簽猖任。

(1). 根View的解析

??根View的解析與child的解析不一樣你稚,是通過(guò)createViewFromTag方法來(lái)完成的:

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
            // ······
            View view;
            // 1. 如果mFactory2不為空,優(yōu)先讓mFactory2處理
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
            // 2. 如果上面解析為空超升,再使用mPrivateFactory常識(shí)這解析
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    // 如果是系統(tǒng)widget包下的控件
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else { // 如果是第三方包或者自定義的控件
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
             // ······
    }

??inflate方法里面主要做了三件事:

  1. 首先使用mFactory2mFactory來(lái)嘗試著創(chuàng)建View對(duì)象入宦。mFactory2mFactory二者有且只能有一個(gè)有值哺徊,所以只需要調(diào)用其中一個(gè)就行了室琢。
  2. 如果第一步中的兩個(gè)工廠都無(wú)法創(chuàng)建View對(duì)象,再?lài)L試著使用mPrivateFactory對(duì)象創(chuàng)建落追。不過(guò)通常來(lái)說(shuō)盈滴,這個(gè)對(duì)象都是為空的。
  3. 最后一步就是走兜底邏輯轿钠。這里的兜底有一點(diǎn)的特殊:如果View是widget的控件巢钓,會(huì)先在前面加一個(gè)android.wiget.的前綴,再行創(chuàng)建View疗垛;其次症汹,如果是其他包下的控件,比如說(shuō)贷腕,androidX和自定義的控件背镇,就直接創(chuàng)建View對(duì)象。

??關(guān)于第一點(diǎn)泽裳,我還想介紹一下瞒斩,Google爸爸之所以要設(shè)計(jì)兩個(gè)工廠類(lèi),主要有3個(gè)方面的考慮:

  1. 兼容性涮总,后面發(fā)布的版本可以兼容之前的版本胸囱,比如說(shuō),AppCompatActivity是新推出來(lái)的組件瀑梗,所以在新版本上使用的mFactory2,舊版本就走原來(lái)的原來(lái)邏輯,也就是默認(rèn)的onCreateView方法烹笔。
  2. 擴(kuò)展性,如果開(kāi)發(fā)者需要自定義一種全局的樣式或者手動(dòng)創(chuàng)建解析View抛丽,可以直接給LayoutInflayer設(shè)置Factory箕宙,用來(lái)達(dá)到自己的目的。
  3. 提升性能铺纽,這一點(diǎn)可以從可以從AppCompatActivity里面看出柬帕。AppCompatActivity的內(nèi)部給LayoutInflayer設(shè)置了一個(gè)Factory2,也就是AppCompatDelegateImpl對(duì)象。AppCompatDelegateImpl在解析xml時(shí),會(huì)先判斷當(dāng)前View是否基礎(chǔ)控件陷寝,比如說(shuō)锅很,ButtonTextView或者ImageView等凤跑,如果是的話爆安,可以通過(guò)new 的方式創(chuàng)建對(duì)應(yīng)的AppCompatXXX對(duì)象。之所以說(shuō)它提升性能仔引,是因?yàn)樗诮馕龌A(chǔ)控件時(shí)扔仓,不再通過(guò)反射,而是通過(guò)new的方式創(chuàng)建的咖耘。

??上面的第三點(diǎn)可能有點(diǎn)復(fù)雜翘簇,我們可以直接看一下AppCompatDelegateImplcreateView方法。由于createView內(nèi)部調(diào)用了AppCompatViewInflatercreateView方法儿倒,所以這里我們直接看AppCompatViewInflatercreateView方法:

    final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;
        // ······
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                view = createView(context, name, attrs);
        }
        // ······
        return view;
    }

??在默認(rèn)的情況下版保,創(chuàng)建View對(duì)象的真正操作在createView方法里面,我們可以來(lái)看看:

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
            // ······
            // 如果緩存中沒(méi)有View的構(gòu)造方法對(duì)象夫否,
            // 那么就創(chuàng)建一個(gè)彻犁,并且放入緩存中去。
            if (constructor == null) {
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                if (mFilter != null) {
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;
            // ······
    }

??從這里凰慈,我們就可以知道汞幢,LayoutInflater創(chuàng)建View的本質(zhì)就是Java反射,所以在我們?nèi)粘i_(kāi)發(fā)過(guò)程中微谓,盡量不要套太深的布局森篷,畢竟反射的性能是有目共睹的。

(2). children的解析

??children的解析實(shí)際上是在rInflate方法里面進(jìn)行的堰酿,我們直接來(lái)看源碼:

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        // ······
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            // requestFocus標(biāo)簽
            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) { // tag標(biāo)簽
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) { // include標(biāo)簽
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) { // merge標(biāo)簽
                throw new InflateException("<merge /> must be the root element");
            } else { // 正常View或者ViewGroup
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }
        // ······
    }

??children的遍歷就像是一個(gè)樹(shù)的遍歷疾宏,就是一種廣搜的思想,這里就不過(guò)多的討論了触创。

3. AppCompatActivity的setContentView方法解析

??說(shuō)完了上面的原理坎藐,最后我們?cè)趤?lái)看看AppCompatActivitysetContentView方法。在LayoutInflater方面哼绑,AppCompatActivity相比于Activity,給LayoutInflater設(shè)置了一個(gè)Factory2,也就是上面討論的東西岩馍。

??這里我們不再討論之前談?wù)撨^(guò)的東西,而是看一個(gè)有趣的東西抖韩,我也不知道Google爸爸是怎么想的蛀恩。
??AppCompatActivityonCreate方法內(nèi)部會(huì)給LayoutInflater設(shè)置一個(gè)Factory2對(duì)象,整個(gè)調(diào)用鏈?zhǔn)牵?code>AppCompatActivity#onCreate -> AppCompatDelegateImpl#installViewFactory -> LayoutInflaterCompat#setFactory2 -> LayoutInflaterCompat#forceSetFactory2茂浮。我們直接來(lái)看setFactory2方法:

    private static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
        if (!sCheckedField) {
            try {
                sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
                sLayoutInflaterFactory2Field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
                        + LayoutInflater.class.getName()
                        + "; inflation may have unexpected results.", e);
            }
            sCheckedField = true;
        }
        if (sLayoutInflaterFactory2Field != null) {
            try {
                sLayoutInflaterFactory2Field.set(inflater, factory);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
                        + inflater + "; inflation may have unexpected results.", e);
            }
        }
    }

??看到這個(gè)神奇操作沒(méi)双谆?萬(wàn)萬(wàn)沒(méi)想到Google是通過(guò)反射的方式來(lái)給mFactory2方法壳咕。爸爸為啥要這樣做呢?我猜測(cè)是setFactory2方法的坑顽馋,我們來(lái)看看:

    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

??只要Factory被設(shè)置過(guò)谓厘,不論是Factory還是Factory2,都不允許被再次設(shè)置寸谜。所以竟稳,我猜測(cè)是,爸爸為了成功給mFactory2設(shè)置上值熊痴,通過(guò)反射來(lái)繞開(kāi)這種限制他爸,這也是在是無(wú)奈。

??設(shè)置了Factory2工廠類(lèi)之后果善,就是調(diào)用setContentView方法來(lái)給Activity設(shè)置ContentView诊笤。我們這里直接來(lái)看一下AppCompatDelegateImplsetContentView方法:

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

??從這里我們可以看出來(lái),contentView是通過(guò)LayoutInflater加載出來(lái)的岭埠。具體的細(xì)節(jié)就不再討論了盏混,上面已經(jīng)詳細(xì)的分析過(guò)了蔚鸥。

4. 總結(jié)

??到此為止惜论,本文算是為止≈古纾總的來(lái)說(shuō)馆类,本文還是簡(jiǎn)單的(隱約的感覺(jué)到,本文有點(diǎn)水)弹谁,在這里乾巧,我們對(duì)本文的內(nèi)容做一個(gè)簡(jiǎn)單的總結(jié)。

  1. ActivitygetLayoutInflater方法和LayoutInflater在本質(zhì)沒(méi)有任何的區(qū)別预愤,最終都會(huì)調(diào)用到ContextImplgetSystemService方法里面去沟于。
  2. LayoutInflater初始化View分為三步:1.調(diào)用mFactory2或者mFactory方法來(lái)解析xml;2. 通過(guò)mPrivateFactory來(lái)解析xml植康;3. 通過(guò)onCreateView或者createView方法來(lái)解析xml旷太。除了在AppCompatDelegateImpl在解析基礎(chǔ)控件時(shí)使用的是new方式,其余幾乎都是反射方式來(lái)創(chuàng)建View销睁,所以在布局中盡可能的少寫(xiě)View或者盡量不要書(shū)寫(xiě)層次很深的布局供璧。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市冻记,隨后出現(xiàn)的幾起案子睡毒,更是在濱河造成了極大的恐慌,老刑警劉巖冗栗,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件演顾,死亡現(xiàn)場(chǎng)離奇詭異供搀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)钠至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)趁曼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人棕洋,你說(shuō)我怎么就攤上這事挡闰。” “怎么了掰盘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵摄悯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我愧捕,道長(zhǎng)奢驯,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任次绘,我火速辦了婚禮瘪阁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邮偎。我一直安慰自己管跺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布禾进。 她就那樣靜靜地躺著豁跑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泻云。 梳的紋絲不亂的頭發(fā)上艇拍,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音宠纯,去河邊找鬼卸夕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婆瓜,可吹牛的內(nèi)容都是我干的快集。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼勃救,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碍讨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蒙秒,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤勃黍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后晕讲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體覆获,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡马澈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弄息。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痊班。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖摹量,靈堂內(nèi)的尸體忽然破棺而出涤伐,到底是詐尸還是另有隱情,我是刑警寧澤缨称,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布凝果,位于F島的核電站,受9級(jí)特大地震影響睦尽,放射性物質(zhì)發(fā)生泄漏器净。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一当凡、第九天 我趴在偏房一處隱蔽的房頂上張望山害。 院中可真熱鬧,春花似錦沿量、人聲如沸浪慌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眷射。三九已至匙赞,卻和暖如春佛掖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涌庭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工芥被, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坐榆。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓拴魄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親席镀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匹中,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345