View.getContext()一定返回Activity嗎合陵?

1潭兽、View.getContext()

@ViewDebug.CapturedViewProperty
    public final Context getContext() {
        return mContext;
    }

代碼很簡單直接返回成員變量mContext朋其,那么mContext是在哪里賦值的呢王浴?搜索發(fā)現(xiàn)mContext只有一個賦值的地方:即View的構(gòu)造函數(shù)中

public View(Context context) {
        mContext = context;
        //.......
}

所以getContext()返回的對象,取決于創(chuàng)建View時傳什么參數(shù)梅猿。一般我們創(chuàng)建View的方式有兩種:

  • 1氓辣、直接通過構(gòu)造new出對象。
  • 2袱蚓、通過xml解析創(chuàng)建View對象
    第一種方式?jīng)]啥好講的钞啸,創(chuàng)建View時傳入什么對象,getContext()就返回什么喇潘。下面主要看下第二種方式体斩。

2、通過xml解析創(chuàng)建View對象

開發(fā)中我們經(jīng)常在xml中寫布局颖低,然后在Activity的onCreate()方法中通過setContentView(int layoutId)進行布局的加載硕勿。xml中的控件是如何創(chuàng)建的呢?setContentView()在Activity和AppCompatActivity中是不同的枫甲。

2.1源武、Activity.setContentView()

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

getWindow()返回PhoneWindow的對象。所以Activity的setContentView()不過是在調(diào)用PhoneWindow()的setContentView()方法想幻。

###PhoneWindow.java
@Override
    public void setContentView(int layoutResID) {
       if (mContentParent == null) {
            //installDecor()中創(chuàng)建DecorView粱栖,并將id為android.R.id.content的mContentParent添加到DecorView中。
            installDecor();
        } 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);
        }
      //省略部分代碼.....
    }

代碼很簡單脏毯,如果沒有FEATURE_CONTENT_TRANSITIONS標記的話闹究,則直接調(diào)用mLayoutInflater.inflate(layoutResID, mContentParent);加載出來。mLayoutInflater是在PhoneWindow的構(gòu)造函數(shù)中創(chuàng)建的食店,PhoneWindow是在Activity中的attach()方法中創(chuàng)建的渣淤。

###Activity.java
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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        //省略部分代碼....
    }

所以PhoneWindow中的context就是Activity本身。

  • 問: LayoutInflater中的mContext和PhoneWindow中的context有什么關(guān)系吉嫩?
  • 答: LayoutInflater.from(context)最終會通過調(diào)用LayoutInflater的構(gòu)造函數(shù)
protected LayoutInflater(Context context) {
        mContext = context;
    }

對mContext進行賦值价认。所以LayoutInflater中的mContext也是Activity本身。

  • 問:LayoutInflater中的mContext和View中的mContext是什么關(guān)系自娩?
  • 答:這就需要看下inflate()方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        //省略部分代碼...
        // Temp is the root view that was found in the xml
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
              //省略部分代碼
    }

主要看下createViewFromTag()方法

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
      //省略部分代碼
        try {
            View 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不會空,則調(diào)用其onCreateView()進行View的創(chuàng)建脐彩,繼承Activity的Activity mFactory2和mFactory均為空碎乃,而mPrivateFactory的實現(xiàn)類就是Activity本身,但是Activity中的onCreateView()方法直接返回null惠奸,所以會調(diào)用LayoutInflate的createView()去創(chuàng)建View梅誓,其中原理很簡單就是通過反射進行View的創(chuàng)建。此時LayoutInflater中的mContext就傳遞到View中了佛南。

2.1.1梗掰、總結(jié)

說了這么多有點亂,總結(jié)一下Activity傳遞到View中的過程:

  • 1共虑、在Activity的attach方法中創(chuàng)建了PhoneWindow將Activity傳遞到PhoneWindow中愧怜。
  • 2呀页、在PhoneWindow的構(gòu)造函數(shù)中創(chuàng)建了LayoutInflater妈拌,將Activity傳遞到LayoutInflater中。
  • 3蓬蝶、通過反射創(chuàng)建View尘分,傳遞到View中。
    所以丸氛,在繼承Activity的頁面中通過xml加載的View的getContext()返回的對象一定是Activity培愁。

2.2、AppCompatActivity.setContentView()

直接上源碼

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

@NonNull
public AppCompatDelegate getDelegate() {
    if (this.mDelegate == null) {
        this.mDelegate = AppCompatDelegate.create(this, this);
    }
    return this.mDelegate;
}

###AppCompatDelegate .java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }

可以看到最終調(diào)用的是AppCompatDelegateImpl中的setContentView()

public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

主要做了3件事:

  • 1缓窜、確保 mSubDecor 的初始化
  • 2定续、從mSubDecor 中找id為android.R.id.content的contentParent
  • 3、通過inflate將id為resId的View添加到contentParent中禾锤。

2.2.1私股、mSubDecor的初始化

###AppCompatDelegateImpl.java
 private void ensureSubDecor() {
        if(!this.mSubDecorInstalled) {
            this.mSubDecor = this.createSubDecor();
            //省略部分代碼。恩掷。倡鲸。
            }
          //省略部分代碼。黄娘。峭状。
    }

 private ViewGroup createSubDecor() {
     //...省略... 這部分主要針對 AppCompat 樣式檢查和適配

    // Now let's make sure that the Window has installed its decor by retrieving it
    //這句代碼很重要哦
    mWindow.getDecorView();

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
 
    //...省略... 這部分主要針對不同的樣式設(shè)置來初始化不同的 subDecor(inflater 不同的布局 xml )
 subDecor =inflater.inflate(XXX);

    //...省略...
    
    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) {
        // There might be Views already added to the Window's content view so we need to
        // migrate them to our content view
        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);
        }
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);

    //...省略...

    return subDecor;
}

  • 1、通過mWindow.getDecorView();創(chuàng)建DecorView逼争,并將id為android.R.id.content的ContentParent添加到DecorView中优床。
  • 2、創(chuàng)建mSubDecor
    -3 誓焦、將DecorView中的ContentParent的所有子View都添加到mSubDecor的子View contentView中羔巢,并清空ContentParent中所有子View,然后將ContentParent的id置成-1,將contentView的id置成android.R.id.content,依次來達到偷梁換柱的目的竿秆。
  • 4启摄、最后通過mWindow.setContentView(subDecor);將subDecor添加到DecorView中。
    還記得我們要干什么嗎幽钢?當然是看繼承AppCompatActivity的Activity中的View中的Context和AppCompatActivity的關(guān)系了歉备,我們回到AppCompatDelegateImpl的setContentView()
public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

上面分析了ensureSubDecor()做了些什么事,真正添加resId的仍然是通過
LayoutInflater.from(this.mContext).inflate(resId, contentParent);來完成的匪燕。

2.2.2蕾羊、LayoutInflater.inflate()

在分析Activity的setContentView()時我們已經(jīng)對inflate做了分析,最終創(chuàng)建View的方法是LayoutInflater的createViewFromTag()帽驯。

###LayoutInflater.java
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
      //省略部分代碼
        try {
            View 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;
        }
    }

和Activity不同的是mFactory2不為空龟再,mFactory2是在AppCompatActivity中的onCreate()中賦值的。

###AppCompatActivity.java
 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
       //省略部分代碼...
    }

### AppcompatDelegateImpl.java
 public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
        if (layoutInflater.getFactory() == null) {
//最終調(diào)用LayoutInflater的setFactory2()
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
        }

    }

###LayoutInflater.java
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);
        }
    }

此時LayoutInflater中的mFactory和mFactory2已被賦值了尼变,mFactory和mFactory2的實例就是AppCompatDelegateImpl利凑,那么mPrivateFactory是何時賦值的呢?

/**
     * @hide for use by framework
     */
    public void setPrivateFactory(Factory2 factory) {
        if (mPrivateFactory == null) {
            mPrivateFactory = factory;
        } else {
            mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
        }
    }

沒找到哪里調(diào)用的嫌术,從注釋可以看出是framework調(diào)用的哀澈。通過斷點可知mPrivateFactory 的實例就是AppCompatActivity本身。下面我們看下Factory2的實現(xiàn)類AppCompatDelegateImpl和AppCompatActivity中的onCreateView(View parent, String name, Context context, AttributeSet attrs)方法到底做了什么度气?

2.2.3割按、AppCompatDelegateImpl的onCreateView()

public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return this.createView(parent, name, context, attrs);
    }

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
       //省略...這部分創(chuàng)建mAppCompatViewInflater
        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());
    }

可以看到最終會調(diào)用AppCompatViewInflater的createView()方法

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;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        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:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

這段代碼主要為了兼容低版本,將name為TextView磷籍、ImageView等控件替換成AppCompatXXX,以TextView轉(zhuǎn)換成AppCompatTextView為例看下适荣。

###AppCompatViewInflate.java
 @NonNull
    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }
###AppCompatTextView.java
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
        this.mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
        this.mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
        this.mTextHelper = new AppCompatTextHelper(this);
        this.mTextHelper.loadFromAttributes(attrs, defStyleAttr);
        this.mTextHelper.applyCompoundDrawablesTints();
    }

可以看到AppCompatTextView的構(gòu)造函數(shù)對context進行了包裝:TintContextWrapper.wrap(context)

 public static Context wrap(@NonNull Context context) {
        if (shouldWrap(context)) {
           //省略代碼...
                TintContextWrapper wrapper = new TintContextWrapper(context);
                sCache.add(new WeakReference(wrapper));
                return wrapper;
            }
        } else {
            return context;
        }
    }

如果shouldWrap(context)返回true,則將context包裝成TintContextWrapper 院领,否則直接返回context弛矛。

private static boolean shouldWrap(@NonNull Context context) {
        if (!(context instanceof TintContextWrapper) && !(context.getResources() instanceof TintResources) && !(context.getResources() instanceof VectorEnabledTintResources)) {
            return VERSION.SDK_INT < 21 || VectorEnabledTintResources.shouldBeUsed();
        } else {
            return false;
        }
    }

可以看到在VERSION.SDK_INT >= 21時肯定返回false,在VERSION.SDK_INT<21時會將context包裝成TintContextWrapper栅盲。所以我們可以得出結(jié)論:

  • 1汪诉、在Android5.0之前,會使用TintContextWrapper創(chuàng)建name為TextView谈秫、ImageView等控件扒寄。
  • 2、在Android5.0及以上直接使用context即AppCompatActivity創(chuàng)建View拟烫。
    分析完了AppCompatDelegateImpl的onCreateView()该编,下面看下AppComatActivity的onCreateView()

2.2.4硕淑、AppComatActivity的onCreateView()

AppComatActivity中并沒有重寫onCreateView课竣,而在其父類FragmentActivity做了重寫

public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View v = this.dispatchFragmentsOnCreateView(parent, name, context, attrs);
        return v == null ? super.onCreateView(parent, name, context, attrs) : v;
    }

可以看到對于非Fragment的控件會直接調(diào)用super.onCreateView(parent, name, context, attrs)即Activity的onCreateView()嘉赎。

public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }

Activity的處理很粗暴,直接return null于樟;所以name不為TextView公条、ImageView等的控件,如LinearLayout迂曲,會通過反射進行創(chuàng)建靶橱。

2.2.5、AppCompatActivity傳遞到View的過程總結(jié)

  • 1路捧、創(chuàng)建AppCompatDelegateImpl关霸,將AppCompatActivity賦值給mContext。
  • 2杰扫、然后通過LayoutInflater.from(this.mContext).inflate(resId, contentParent);將AppCompatActivity傳遞給LayoutInflater队寇。
  • 3、在LayoutInflater中的createViewFromTag()方法中調(diào)用Factory2的實現(xiàn)類 AppCompatDelegateImpl中的createView()將name為TextView章姓、ImageView等的控件替換成AppCompat開頭的控件佳遣,在Android5.0以下的創(chuàng)建AppCompatXX時傳遞的是TintContextWrapper。
  • 4啤覆、對于name不為TextView苍日、ImageView的控件依然調(diào)用LayoutInflater中的onCreateView惭聂,通過反射創(chuàng)建窗声。

3、總結(jié)

  • 1辜纲、繼承Activity的頁面中的View笨觅,getContext()直接返回Activity。
  • 2耕腾、繼承AppCompatActivity的頁面中的View见剩,
    • 在Android5.0以下,如果name為TextView扫俺、ImageView等的控件的getContext()返回TintContextWrapper苍苞,否則則返回AppCompatActivity。
    • 在Android5.0以上狼纬,直接返回AppCompatActivity羹呵。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疗琉,隨后出現(xiàn)的幾起案子冈欢,更是在濱河造成了極大的恐慌,老刑警劉巖盈简,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凑耻,死亡現(xiàn)場離奇詭異太示,居然都是意外死亡,警方通過查閱死者的電腦和手機香浩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門类缤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人邻吭,你說我怎么就攤上這事呀非。” “怎么了镜盯?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵岸裙,是天一觀的道長。 經(jīng)常有香客問我速缆,道長降允,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任艺糜,我火速辦了婚禮剧董,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘破停。我一直安慰自己翅楼,他們只是感情好,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布真慢。 她就那樣靜靜地躺著毅臊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黑界。 梳的紋絲不亂的頭發(fā)上管嬉,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音朗鸠,去河邊找鬼蚯撩。 笑死,一個胖子當著我的面吹牛烛占,可吹牛的內(nèi)容都是我干的胎挎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼忆家,長吁一口氣:“原來是場噩夢啊……” “哼犹菇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弦赖,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤项栏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蹬竖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沼沈,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡流酬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了列另。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芽腾。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖页衙,靈堂內(nèi)的尸體忽然破棺而出摊滔,到底是詐尸還是另有隱情,我是刑警寧澤店乐,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布艰躺,位于F島的核電站,受9級特大地震影響眨八,放射性物質(zhì)發(fā)生泄漏腺兴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一廉侧、第九天 我趴在偏房一處隱蔽的房頂上張望页响。 院中可真熱鬧,春花似錦段誊、人聲如沸闰蚕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽没陡。三九已至,卻和暖如春烟瞧,著一層夾襖步出監(jiān)牢的瞬間诗鸭,已是汗流浹背染簇。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工参滴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锻弓。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓砾赔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親青灼。 傳聞我的和親對象是個殘疾皇子暴心,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354