View.Post () 的身世大揭秘

View.post( ),大家肯定都用過拓哺,也就不陌生了伍纫。一般使用View.Post ( ) 的場景最常見的就是
1.子線程更UI,
2.獲取View的寬高

那就讓我們再帶著問題去看看原因咯屯援。

    public boolean post(Runnable action) {
     //判斷 attachInfo 是否為空蕉鸳,而進行不同的操作
     //那么其實就是要知道 mAttachInfo 是在哪里被賦值的珍德?
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
mAttachInfo的賦值

我們會發(fā)現(xiàn)他兩個被賦值的地方泣矛,分別為 dispatchAttachedToWindow旦棉,dispatchDetachedFromWindow

//dispatchAttachedToWindow:
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //這里賦值
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }
       registerPendingFrameMetricsObservers();

        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        //緩存不為空的是時候去執(zhí)行 緩存的 action
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        //當對應的 Activity 被添加到 Window的時候調(diào)用齿风,只調(diào)用一次
        onAttachedToWindow();
   //  .......省略代碼
//dispatchDetachedFromWindow:
  void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
                if (isShown()) {
                    // Invoking onVisibilityAggregated directly here since the subtree
                    // will also receive detached from window
                    onVisibilityAggregated(false);
                }
            }
        }

        onDetachedFromWindow();
        onDetachedFromWindowInternal();

        InputMethodManager imm = InputMethodManager.peekInstance();
        if (imm != null) {
            imm.onViewDetachedFromWindow(this);
        }

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }

        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
        }
        //這里賦空值
        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }

        notifyEnterOrExitForAutoFillIfNeeded(false);
    }

但是會發(fā)現(xiàn),到這里的時候我們無法再追蹤這兩個方法在哪里被調(diào)用了绑洛,于是我們可以通過網(wǎng)上那些Android源碼閱讀的網(wǎng)站救斑,或者自己有下載Android源碼的來找一找看看 究竟在什么地方被調(diào)用的:
推薦一個:http://androidxref.com/
搜索 dispatchAttachedToWindow,可以發(fā)現(xiàn)如下:

7403980-e96a4387af6ee912.png

可以發(fā)現(xiàn)真屯,在 ViewGroupViewRootImpl 均有被調(diào)用脸候,那么我們就去看看。

ViewGroup
 @Override
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
       // super.dispatchAttachedToWindow(info, visibility); 這句話就是說明他執(zhí)行了父類的方法
      //也就是我們一開始看到的 View 的dispatchAttachedToWindow()的方法绑蔫。
        super.dispatchAttachedToWindow(info, visibility);
        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
    //這里又會把 mAttachInfo 作為參數(shù)傳遞進去运沦,分別讓自己的子類去執(zhí)行 dispatchAttachedToWindow () 方法,
    //讓自己的子類 分別給 mAttachInfo  賦值配深。
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View view = mTransientViews.get(i);
            view.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, view.getVisibility()));
        }
    }
/*但是這樣一來我們還是不知道 mAttachInfo  是在那里被賦值的發(fā)携添,只是知道 ViewGroup 會去執(zhí)行 View 類和掉用子 View 的 dispatchAttachedToWindow () 方法。
方法篓叶。*/

繼續(xù)看看 ViewGroup 里面還有什么地方調(diào)用了:
addViewInner()方法是 viewGroup addView( )內(nèi)部都會調(diào)用的一個方法

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }
         //判斷View是否被添加
        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (mTransition != null) {
            mTransition.addChild(this, child);
        }

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

        if (index < 0) {
            index = mChildrenCount;
        }
        //添加到 ViewGroup
        addInArray(child, index);

        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }

        final boolean childHasFocus = child.hasFocus();
        if (childHasFocus) {
            requestChildFocus(child, child.findFocus());
        }
       //這里判斷 mAttachInfo 的對象是否為空烈掠,如果不為空就把 mAttachInfo 作為參數(shù)調(diào)用子類的 dispatchAttachedToWindow ( ),那么
      //還是回到了 View 的 dispatchAttachedToWindow (),我們還是不知道 mAttachInfo 再哪里給賦值的.
        AttachInfo ai = mAttachInfo;
        if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
            boolean lastKeepOn = ai.mKeepScreenOn;
            ai.mKeepScreenOn = false;
            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
            if (ai.mKeepScreenOn) {
                needGlobalAttributesUpdate(true);
            }
            ai.mKeepScreenOn = lastKeepOn;
        }
       .......省略

既然ViewGroup沒有缸托,那么我們就去看看 ViewRootImpl左敌。

ViewRootImpl

我們在 ViewRootImpl 的 performTraversals(),發(fā)現(xiàn)了dispatchAttachedToWindow()被調(diào)用俐镐,而 performTraversals() 作用就是遍歷整個View樹母谎,并且按照要求進行measure,layout和draw流程。

 private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
  //判斷是不是第一次
  if (mFirst) {
       .....
      //這里調(diào)用了 dispatchAttachedToWindow京革,并且把 mAttachInfo 給子view
       host.dispatchAttachedToWindow(mAttachInfo, 0);
       mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
       dispatchApplyInsets(host);
     //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
      .....
} 
   mFirst=false
    ...
  // Execute enqueued actions on every traversal in case a detached view enqueued an action
    getRunQueue().executeActions(mAttachInfo.mHandler);
    ...
    performMeasure();
    ...
    performLayout();
    ...
    performDraw();
    ...

上面的代碼奇唤,等下我們再回來看幸斥,我們先找找在 ViewRootImpl 里面 mAttachInfo 是在哪被賦值的

        ...
       final View.AttachInfo mAttachInfo;
        ...
      // mAttachInfo 就是在這里被賦值了,其中在多個參數(shù)之中咬扇,我們發(fā)現(xiàn)了 mHandler甲葬。
       mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);


    //繼續(xù)看看 mHandler 是在哪被初始化的。
  final ViewRootHandler mHandler = new ViewRootHandler();
/*
通過這句代碼我們就可以知道懈贺。這里 new 的時候是無參構(gòu)造函數(shù)经窖,那默認綁定的就是當前線程的 Looper,而這句 new 代碼是在主線程中執(zhí)行的梭灿,所以這個 Handler 綁定的也就是主線程的 Looper
*/
再結(jié)合:getRunQueue().executeActions(mAttachInfo.mHandler);
   public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }
為什么能更新UI:

總結(jié)回顧一下:
我們知道了 mAttachInfo 是在 ViewRootImpl 初始化的画侣,再結(jié)合剛說等下回去看的 performTraversals 的方法,可以知道ViewRootImpl 會調(diào)用子view的 dispatchAttachedToWindow堡妒。我們還可以知道為什么 View.post(Runnable),可以更新UI了配乱,因為這些 Runnable 操作都通過 ViewRootImpl 的 mHandler 切到主線程來執(zhí)行了。

為什么能獲取寬高

那么我們再次回到 一開始的的地方皮迟,我們知道 View 里面的 mAttachInfo 是在 ViewdispatchAttachedToWindow 被賦值搬泥,那么 dispatchAttachedToWindow()是在什么時候執(zhí)行的呢?我們上面分析的是在哪調(diào)用了他伏尼,和 mAttachInfo的初始化忿檩,細心的朋友,會發(fā)現(xiàn)在ViewdispatchAttachedToWindow()onAttachedToWindow();爆阶,那么我們就可簡單寫個測試燥透。

  class TestView : TextView {
    constructor(context: Context) : super(context) {}

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}

    override fun onAttachedToWindow() {
        Log.e("TAG---AttachedToWindow", "onAttachedToWindow");
        super.onAttachedToWindow();
    }
}
//MainActivity :
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        Log.e("TAG---沒有Post", "mButton width : " + tv_test.getMeasuredWidth() + " - height : " + tv_test.getMeasuredHeight());
        tv_test.post {
            Log.e("TAG---Post", "mButton width : " + tv_test.getMeasuredWidth() + " - height : " + tv_test.getMeasuredHeight());
        }

    }
}

//結(jié)果:
07-29 17:17:24.201 31814-31814/? E/TAG---沒有Post: mButton width : 0 - height : 0
07-29 17:17:24.261 31814-31814/? E/TAG---AttachedToWindow: onAttachedToWindow
07-29 17:17:24.351 31814-31814/? E/TAG---Post: mButton width : 84 - height : 57

那么結(jié)果就出來了,在 onCreate 中獲取寬高辨图,AttachedToWindow ( ) 是還沒執(zhí)行的兽掰,那就說明一開始的時候 mAttachInfo 是為空值的,那么我們再看開頭的第一段代碼:

    public boolean post(Runnable action) {
        // mAttachInfo 為空
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
那么他就會執(zhí)行: getRunQueue().post(action);
 public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;


    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

我們post()傳進來的 Runnable 會先經(jīng)過 HandlerAction 包裝一下徒役,然后再緩存起來。HandlerActionQueue 是通過一個默認大小為4的數(shù)組保存這些 Runnable 操作的窖壕,如果數(shù)組不夠時忧勿,就會通過 GrowingArrayUtils 來擴充數(shù)組。那么既然是被緩存起來的瞻讽,那么他是什么時候執(zhí)行呢鸳吸?我又會發(fā)現(xiàn) 他執(zhí)行的方法還是在 dispatchAttachedToWindow 里面:

dispatchAttachedToWindow :
     //緩存不為空的是時候去執(zhí)行 緩存的 action
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }

executeActions:
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

既然我們知道了 post()傳進來的 Runnable 會在 dispatchAttachedToWindow 執(zhí)行,結(jié)合我們上面的分析速勇,我們就可以知道晌砾,post 的操作是要經(jīng)過 ViewRootImpl 的 performTraversals(),而它的作用就是遍歷整個View樹烦磁,并且按照要求進行measure,layout和draw流程养匈。但是仔細看看代碼哼勇,我們會發(fā)現(xiàn):

 private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
  //判斷是不是第一次
  if (mFirst) {
       .....
      //這里調(diào)用了 dispatchAttachedToWindow,明顯是在 performMeasure 之前呕乎,
     //為什么在測量之前調(diào)用還能得到寬高呢积担?
       host.dispatchAttachedToWindow(mAttachInfo, 0);
       mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
       dispatchApplyInsets(host);
     //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
      .....
} 
   mFirst=false
    ...
  // Execute enqueued actions on every traversal in case a detached view enqueued an action
    getRunQueue().executeActions(mAttachInfo.mHandler);
    ...
    performMeasure();
    ...
    performLayout();
    ...
    performDraw();
    ...

那么 為什么明明測量 performMeasure(); 的是在 dispatchAttachedToWindow 之后執(zhí)行,但是我們卻能得到測量后的寬高猬仁?請看下面的代碼:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
          // doTraversal 這里執(zhí)行
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();


    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
          //向Looper中移除了Barrier(同步屏障)帝璧,同步的消息可以執(zhí)行
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //performTraversals() 在這里被執(zhí)行
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
mTraversalBarrier 是什么東東?

為了讓View能夠有快速的布局和繪制湿刽,android中定義了一個Barrier的概念的烁,當View在繪制和布局時會向Looper中添加了Barrier(同步屏障),這樣后續(xù)的消息隊列中的同步的消息將不會被執(zhí)行诈闺,以免會影響到UI繪制渴庆,但是只有異步消息才能被執(zhí)行。 所謂的異步消息也只是體現(xiàn)在這买雾,添加了Barrier后把曼,消息還可以繼續(xù)被執(zhí)行,不會被推遲運行漓穿。 如何使用異步消息嗤军,只有在創(chuàng)建Handler(構(gòu)造方法的參數(shù)上標識是否異步消息)的時候或者在發(fā)送Message(Mesasge#setAsynchronous(true))時進行設置。而異步消息應用層是無法設置晃危,因為相關設置的方法均是Hide的叙赚。

那就是什么意思呢?
首先僚饭,我們搞清楚 mTraversalScheduled 這個對象是在哪被賦值震叮。

 void scheduleTraversals() {
        if (!mTraversalScheduled) {
           //這里
            mTraversalScheduled = true;
           //向Looper中添加了Barrier(同步屏障),這樣后續(xù)的消息隊列中的同步的消息將不會被執(zhí)行鳍鸵,
           //以免會影響到UI繪制苇瓣,但是只有異步消息才能被執(zhí)行
           //設置同步障礙,確保mTraversalRunnable優(yōu)先被執(zhí)行
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //這里又調(diào)用了  mTraversalRunnable偿乖,執(zhí)行異步
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
           //這里
            mTraversalScheduled = false;
           //向Looper中移除了Barrier(同步屏障)击罪,同步的消息可以執(zhí)行
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        //這里又調(diào)用了  mTraversalRunnable。
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

scheduleTraversals ( ) 方法是在 requestLayout( )被調(diào)用的, requestLayout( )是什么贪薪?額媳禁,這里就不解釋了,不然又要寫一大堆画切,哈哈竣稽。只要知道 第一次調(diào)用requestLayout( ) 就是引起整個 View 的繪制流程

 @Override
 public void requestLayout() {
     if (!mHandlingLayoutInLayoutRequest) {
       // 檢查當前線程
         checkThread();
         mLayoutRequested = true;
        // 調(diào)用繪制
         scheduleTraversals();

那么就是 scheduleTraversals( )——》TraversalRunnable ( )——》doTraversal( )——》performTraversals( ),而且 doTraversal ( )中向Looper中移除了Barrier(同步屏障),同步的消息可以執(zhí)行,
經(jīng)查閱資料發(fā)現(xiàn)毫别,跟Android系統(tǒng)的消息機制有關系(猜測更上面的同步機制有關)娃弓,performTraversals會先執(zhí)行dispatchAttachedToWindow,這個時候會將任務post到主線程的MessageQueue等待執(zhí)行拧烦,然后performTraversals方法會繼續(xù)執(zhí)行忘闻,完全執(zhí)行完后,Looper再去消費下一個Message恋博,這個時候才有可能會拿到post的Runnable齐佳,因此Runnabel操作實際是在performMeasure操作后才執(zhí)行的,寬高自然就取到了债沮。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炼吴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疫衩,更是在濱河造成了極大的恐慌硅蹦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闷煤,死亡現(xiàn)場離奇詭異童芹,居然都是意外死亡,警方通過查閱死者的電腦和手機鲤拿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進店門假褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人近顷,你說我怎么就攤上這事生音。” “怎么了窒升?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵缀遍,是天一觀的道長。 經(jīng)常有香客問我饱须,道長域醇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任蓉媳,我火速辦了婚禮譬挚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘督怜。我一直安慰自己,他們只是感情好狠角,可當我...
    茶點故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布号杠。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姨蟋。 梳的紋絲不亂的頭發(fā)上屉凯,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天,我揣著相機與錄音眼溶,去河邊找鬼悠砚。 笑死,一個胖子當著我的面吹牛堂飞,可吹牛的內(nèi)容都是我干的灌旧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼绰筛,長吁一口氣:“原來是場噩夢啊……” “哼枢泰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起铝噩,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤衡蚂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骏庸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毛甲,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年具被,在試婚紗的時候發(fā)現(xiàn)自己被綠了玻募。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡硬猫,死狀恐怖补箍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啸蜜,我是刑警寧澤坑雅,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站衬横,受9級特大地震影響裹粤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜂林,卻給世界環(huán)境...
    茶點故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一遥诉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧噪叙,春花似錦矮锈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽债朵。三九已至,卻和暖如春瀑凝,著一層夾襖步出監(jiān)牢的瞬間序芦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工粤咪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谚中,地道東北人。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓寥枝,卻偏偏與公主長得像宪塔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脉顿,可洞房花燭夜當晚...
    茶點故事閱讀 43,499評論 2 348

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