WindowManager$BadTokenException(WindowManager源碼分析)

簡介:

本文主要講解WindowManager里的addView(View view, ViewGroup.LayoutParams params),removeView(View view),removeViewImmediate(View view)三個方法的實現(xiàn)原理乡翅,以及通過分析系統(tǒng)源碼,解決我們在平常開發(fā)過程中使用WindowManager遇到的各種異常崩潰問題,本文因修改項目中的WindowManager$BadTokenException類型bug而來闲勺,所以以此命名。

源碼版本

功能:

通過WindowManager實現(xiàn)一個懸浮在屏幕最上方的懸浮View芽狗,提示用戶文件上傳完成把将,以及點擊查看文件內(nèi)容焰枢。

//WindowManager實例獲取:
mContext.getApplicationContext().getSystemService(Context.WINDOW_SERVICE)
//添加view
mWdm.addView(mToastView, mParams);
//移除View
mWdm.removeViewImmediate(mToastView);

場景1:

在使用WindowManager時候徽级,可能會遇到這樣的異常:

View xxxxx@167788  has already been added to the window manager

看到這個異常气破,很多開發(fā)者立馬能夠想到是同一個View,在沒有移除情況下餐抢,又執(zhí)行了addView操作现使,于是,在添加之前判斷該View是否已經(jīng)有父容器了旷痕,有碳锈,者先移除該View:

final ViewParent parent = mToastView.getParent();
if (parent != null) {
     mWdm.removeView(mToastView);
 }

改好,心想如此簡單欺抗,臉上洋溢著開心的笑容售碳,提交,打包绞呈,熱更贸人,完事,但是沒想到佃声,線上還是會報上面的異常艺智。
于是,翻翻WindowManager里的Api圾亏,看到還有removeViewImmediate(View view)這個方法十拣,通過文檔,了解到這個方法與removeView(View view)不同在于前者是同步召嘶,后者是異步的父晶,于是想到可能是removeView(View view)異步導(dǎo)致,執(zhí)行添加之前View還沒有來得及移除弄跌。

\color{red}{疑問:}

  1. removeView實現(xiàn)異步的方式是什么甲喝?
  2. 同步和異步表現(xiàn)的差異在哪里呢?
  3. 為什么在同步和異步移除的情況铛只,得到View的父容器都是null埠胖,為什么用removeView會報異常呢?

帶著疑問繼續(xù)向向看吧淳玩!

于是采用同步移除的方式直撤,測試幾波,發(fā)現(xiàn)ok了蜕着,這下應(yīng)該沒事了吧谋竖,打包红柱,熱更,吃飯蓖乘,哈哈哈哈哈锤悄!

場景2:

突然第二天發(fā)現(xiàn)這個功能偶爾還是會出現(xiàn)崩潰,不同的是異常信息:

Unable to add window -- window android.view.ViewRootImpl$W@c84a531 has already been added

眉頭緊鎖嘉抒,著了零聚,還是報什么View已經(jīng)添加了,難道同步移除也不行些侍,但是報的是ViewRootImpl已經(jīng)添加了啊隶症,這下完了,到底完沒完,繼續(xù)下看岗宣。

補充:

也許發(fā)現(xiàn)蚂会,在不同的Android版本,手機(jī)狈定,targetSdkVersion值颂龙,也會有不一樣的現(xiàn)象,比如在targetSdkVersion指定在Android O及以上纽什,當(dāng)WindowManager.LayoutParams指定的類型為WindowManager.LayoutParams.TYPE_TOAST(官方已建議棄用),會直接崩潰躲叼,報以下錯誤:

Unable to add window -- token  null is not valid; is your activity running?

而在O以下卻不會呢芦缰,這是為什么呢让蕾?帶著疑問一起向下看吧誉裆!

\color{red}{開飯了—下面由我來進(jìn)行逐一解疑:}

removeView,removeViewImmediate異同

  • WindowManager.java實現(xiàn)類WindowManagerImpl.java

1.分解操作:

mContext.getApplicationContext().getSystemService(Context.WINDOW_SERVICE)

//android.app.ContextImpl
@Override
public String getSystemServiceName(Class<?> serviceClass) {
    return SystemServiceRegistry.getSystemServiceName(serviceClass);
}

//android.app.SystemServiceRegistry
static{
    ...
    registerService(Context.WINDOW_SERVICE, WindowManager.class, new       
         CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
    }});
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
 }
  • removeView耀鸦,removeViewImmediate
//android.view.WindowManagerImpl
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

關(guān)鍵就在這個boolean變量了(...省略的都是相同或者不重要的代碼)

//android.view.WindowManagerGlobal
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
         ...
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

關(guān)鍵點:

  1. view.assignParent(null);
    這個方法就是將View的父容器置null,通過該View的getParent()可以獲取該父容
    器的對象。
  2. boolean deferred = root.die(immediate);
 //android.view.ViewRootImpl
    boolean die(boolean immediate) {
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
        ...
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

如果immediate為true立即調(diào)用doDie()奄容,否則通過handler發(fā)送一個MSG_DIE消息,然后立即返回,這時候View并沒有完成真正的刪除操作,原來同步和異步是這么實現(xiàn)的

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
            ...
            }
            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

dispatchDetachedFromWindow:

  1. 垃圾回收相關(guān)操作迫横,比如清除數(shù)據(jù)和消息,移除回調(diào)呛讲。

  2. 通過Session的remove()刪除Window衡瓶,這同樣是一個IPC過程关面,最終會調(diào)用WindowManagerService的removeWindow()。

  3. 調(diào)用View的dispatchDetachedFromWindow(),進(jìn)而調(diào)用onDetachedFromWindow(),onDetachedFromWindowInternal()佃迄。
    onDetachedFromWindow()對于大家來說一定不陌生普碎,我們可以在這個方法內(nèi)部做一些資源回收工作套啤,比如終止動畫、停止線程等随常。
    最后再調(diào)用WindowManagerGlobal的doRemoveView()方法刷新數(shù)據(jù),包括mRoots萄涯、mParams绪氛、mViews和mDyingViews,將當(dāng)前Window所關(guān)聯(lián)的對象從集合中刪除涝影。

  • 重要函數(shù):dispatchDetachedFromWindow
    void dispatchDetachedFromWindow() {
     ...
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;
       ...
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        ...
    }
  • 移除window - mWindowSession.remove(mWindow);
mWindowSession.remove(mWindow);

--------------
   @Override
   public void remove(IWindow window) {
       mService.removeWindow(this, window);
   }
//android.view.WindowManagerGlobal
    public static IWindowSession getWindowSession() {
        ...
        InputMethodManager imm = InputMethodManager.getInstance();
        IWindowManager windowManager = getWindowManagerService();
        sWindowSession = windowManager.openSession(...);
        return sWindowSession;
        }
    }
  • windowManager
IWindowManager windowManager = getWindowManagerService();

--------------
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
              ...
            }
            return sWindowManagerService;
        }
    }
  • ServiceManager.getService("window"))
    Android系統(tǒng)在啟動時枣察,會在SystemServer里面注冊很多系統(tǒng)需要的服務(wù),像AMS燃逻,PMS序目,WMS等

路徑:framewor/base/setvices/java/com.android.server/SystemServer

private void startOtherServices() {

WindowManagerService wm = null;
...
wm = WindowManagerService.main(context, inputManager,mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore, new PhoneWindowManager()); 
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
...
}
  • WindowManagerService.openSession
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }
  • 關(guān)鍵代碼:WindowManagerGlobal.getInstance().doRemoveView(this);
  1. mViews 存儲的是所有Window對應(yīng)的View
  2. mRoots 存儲的是所有Window對應(yīng)的ViewRootImpl
  3. mParams 存儲的是所有Window對應(yīng)的布局參數(shù)
  4. mDyingViews 待刪除的View列表\color{red}{請留意這個集合,一會說的異常就和這個相關(guān):}
    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            //如果找到對應(yīng)view的索引
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }

addView(...)過程分析

  1. 第一個異常:
throw new IllegalStateException("View " + view
         + " has already been added to the window manager.");
  1. 拋出條件:
    if (index >= 0 && ! mDyingViews.contains(view));
//android.view.WindowManagerGlobal
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        ...
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }
          ...
          //創(chuàng)建 new ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  • 關(guān)鍵函數(shù):ViewRootImpl - setView
//android.view.ViewRootImpl
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
           ...
                mAttachInfo.mRootView = view;
              ...
                mAdded = true;
                try {
                 ...
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
                   ...
                } finally {
                  ...
                }
            ...
            // 一大波異常來襲
                if (res < WindowManagerGlobal.ADD_OKAY) {
                   ...
                    switch (res) {
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window " + mWindow
                                    + " has already been added");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- another window of type "
                                    + mWindowAttributes.type + " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- permission denied for window type "
                                    + mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified window type "
                                    + mWindowAttributes.type + " is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }
                ...
                //設(shè)置view的父容器
                view.assignParent(this);
              ...
            }
        }
    }
  • 關(guān)鍵代碼:
  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

//framewor/base/setvices/java/com.android.server/wm/Session

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

//framewor/base/setvices/java/com.android.server/wm/WindowManagerService

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        int[] appOp = new int[1];
//  權(quán)限檢測伯襟,有興趣的可以自己點進(jìn)去看看
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        ...
        synchronized(mWindowMap) { 
        /** final WindowState win = new WindowState(this, session, client,    token, parentWindow,
             mWindowMap.put(client.asBinder(), win);
              win = mWindow = new W(this);
             public ViewRootImpl(Context context, Display display) {
                ...
                mWindow = new W(this);
                ...
             }
            
        */
          appOp[0], seq, attrs, viewVisibility, session.mUid,
          session.mCanAddInternalSystemWindow);
          
            //異常1
            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            /**
              window類型type:
              type表示W(wǎng)indow的類型猿涨,Window有三種類型,分別是應(yīng)用Window姆怪,子
              Window和系統(tǒng)Window叛赚。

              應(yīng)用類Window對應(yīng)著一個Activity澡绩。子Window不能單獨存在,它需要附屬在特定的父Window中俺附,比如Dialog就是一個子Window肥卡。系統(tǒng)Window
              是需要聲明權(quán)限才能創(chuàng)建的Window,比如Toast和系統(tǒng)狀態(tài)欄這些都是系統(tǒng)Window事镣。

              Window是分層的步鉴,每個Window都有對應(yīng)的z-ordered,層級大的會覆蓋    在層級小的Window上璃哟。在三類Window中氛琢,應(yīng)用Window的層級范圍是1~99,子
              Window的層級范圍是1000~1999沮稚,系統(tǒng)Window的層級范圍是2000~2999艺沼。很顯然系統(tǒng)Window的層級是最大的,而且系統(tǒng)層級有很多值蕴掏,一
              般我們可以選用TYPE_SYSTEM_ERROR或者TYPE_SYSTEM_OVERLAY障般,另外重要的是要記得在清單文件中聲明權(quán)限。

            */
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                //子Window
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

           ...

            if (token == null) {
               //應(yīng)用Window
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                
               
                if (rootType == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
          
               ....

                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }

                if (type == TYPE_TOAST) {
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
             ...
            if (type == TYPE_TOAST) {
                if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
                    Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
                ...
            }

            // From now on, no exceptions or errors allowed!

            res = WindowManagerGlobal.ADD_OKAY;
            if (mCurrentFocus == null) {
                mWinAddedSinceNullFocus.add(win);
            }
            ...

            win.attach();
            mWindowMap.put(client.asBinder(), win);
           ...

            boolean imMayMove = true;

            win.mToken.addWindow(win);
            ...

        return res;
    }
  • 異常分析:
                if (type == TYPE_TOAST) {
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }

從注釋// Apps targeting SDK above N MR1 cannot arbitrary add toast windows.可以看出盛杰,當(dāng)targetSdkVersion大于N MR1挽荡,type == TYPE_TOAST 情況下是會直接崩潰的,報以下異常:

      throw new WindowManager.BadTokenException(
            "Unable to add window -- token " + attrs.token
                 + " is not valid; is your activity running?");
  • 異常分析:
            if (type == TYPE_TOAST) {
                if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
                    Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
                ...
            }

如果mParams的類型是TYPE_TOAST即供,對于同一個uid定拟,在同一個時刻只能添加一個toast類型的Window,如果在上一個Window還沒有移除時逗嫡,又去添加新Window的操作青自,直接崩潰報以下異常:

Unable to add window -- window android.view.ViewRootImpl$W@c84a531 has already been added

但是不同Android系統(tǒng)版本铁追,表現(xiàn)不一樣仑荐,在我的Android5.1上測試,是可以同時添加多個Window,通過查看5.1系統(tǒng)源碼腺办,發(fā)現(xiàn)在5.1上沒有上面的判斷邏輯抹锄,具體從那個Android系統(tǒng)開始的逆瑞,沒有去查看。

里面還有很多異常伙单,大家可以自己去查看获高。如果寫得有不對的地方,歡迎留言吻育。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末念秧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扫沼,更是在濱河造成了極大的恐慌出爹,老刑警劉巖庄吼,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異严就,居然都是意外死亡总寻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門梢为,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渐行,“玉大人,你說我怎么就攤上這事铸董∷钣。” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵粟害,是天一觀的道長蕴忆。 經(jīng)常有香客問我,道長悲幅,這世上最難降的妖魔是什么套鹅? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮汰具,結(jié)果婚禮上卓鹿,老公的妹妹穿的比我還像新娘。我一直安慰自己留荔,他們只是感情好吟孙,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聚蝶,像睡著了一般杰妓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碘勉,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天稚失,我揣著相機(jī)與錄音,去河邊找鬼恰聘。 笑死,一個胖子當(dāng)著我的面吹牛吸占,可吹牛的內(nèi)容都是我干的晴叨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼矾屯,長吁一口氣:“原來是場噩夢啊……” “哼兼蕊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起件蚕,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤孙技,失蹤者是張志新(化名)和其女友劉穎产禾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牵啦,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡亚情,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哈雏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楞件。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖裳瘪,靈堂內(nèi)的尸體忽然破棺而出土浸,到底是詐尸還是另有隱情,我是刑警寧澤彭羹,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布黄伊,位于F島的核電站,受9級特大地震影響派殷,放射性物質(zhì)發(fā)生泄漏还最。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一愈腾、第九天 我趴在偏房一處隱蔽的房頂上張望憋活。 院中可真熱鬧,春花似錦虱黄、人聲如沸悦即。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辜梳。三九已至,卻和暖如春泳叠,著一層夾襖步出監(jiān)牢的瞬間作瞄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工危纫, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留宗挥,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓种蝶,卻偏偏與公主長得像契耿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子螃征,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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