Dialog中的Window添加过程解析

Dialog中的Window添加过程解析

Dialog一直作为一种依附在Activity上下文才能存在的窗口视图,那是否可以逃脱Activity的上下文,采用其他Context存在呢?答案是肯定的,Dialog完全可以不依赖Activity上下文存在,这里我们说的只是非Activity的Context,不是不依赖Context

  • Dialog的组成:

    Dialog是一种承载Window的容器,而Window的唯一实现便是PhoneWindow,Dialog的setContentView就是将布局文件的id传给PhoneWindow, PhoneWindow通过该布局id解析然后创建一个DecorView,这是一个继承FrameLayout的ViewGroup,每个Window都有一个WindowManagerImpl,这里所说的是每个非子window类型的window,因为子window是依附于父window,父子共用一个WindowManagerImpl,普通的Dialog的WindowManagerImpl与Activity是共用的

  • Dialog的创建代码:


    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

        if (createContextThemeWrapper) {

            if (themeResId == ResourceId.ID_NULL) {

                final TypedValue outValue = new TypedValue();

                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);

                themeResId = outValue.resourceId;

            }

            mContext = new ContextThemeWrapper(context, themeResId);

        } else {

            mContext = context;

        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // 获取windowmanager

        final Window w = new PhoneWindow(mContext);

        mWindow = w;

        w.setCallback(this);

        w.setOnWindowDismissedCallback(this);

        w.setOnWindowSwipeDismissedCallback(() -> {

            if (mCancelable) {

                cancel();

            }

        });

        w.setWindowManager(mWindowManager, null, null);  // phonewindow 与 Windowmanager绑定

        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);

    }

再来看一下setContentView代码:


  Dialog.java

    public void setContentView(@LayoutRes int layoutResID) {

        mWindow.setContentView(layoutResID);

    }

PhoneWindow.java

public void setContentView(int layoutResID) {

        if (mContentParent == null) {

            installDecor();  // 创建DecorView与PhoneWindow绑定

        } 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);

        }

        mContentParent.requestApplyInsets();

        final Callback cb = getCallback();

        if (cb != null && !isDestroyed()) {

            cb.onContentChanged();

        }

        mContentParentExplicitlySet = true;

    }

以上也说明Dialog中视图的构建过程,其实就是Dialog持有一个Context、phonewindow、windowmanager,其中Context如果是Activity的话,因为Activity是集成ContextWrapTheme类,所以由Activity上下文构建的Dialog是和Activity的主题一样的。phonewindow持有视图解析后的View结构树DecorView,而WindowManager,如果是子窗口类型,便使用父窗口的windowmanager,如果是系统窗口,将独立创建一个windowmanager,其实每个WindowManager也是一个傀儡,真正执行View操作的的事WindowManagerGlobal,这个每个Application只有一个该单例对象,管理当前app的所有window以及view的跟新操作,可以看一下为什么WindowManager是个傀儡,这个类代码就几十行:


public final class WindowManagerImpl implements WindowManager {

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    private final Context mContext;

    private final Window mParentWindow;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Context context) {

        this(context, null);

    }

    private WindowManagerImpl(Context context, Window parentWindow) {

        mContext = context;

        mParentWindow = parentWindow;

    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {

        return new WindowManagerImpl(mContext, parentWindow);

    }

    public WindowManagerImpl createPresentationWindowManager(Context displayContext) {

        return new WindowManagerImpl(displayContext, mParentWindow);

    }

    /**

    * Sets the window token to assign when none is specified by the client or

    * available from the parent window.

    *

    * @param token The default token to assign.

    */

    public void setDefaultToken(IBinder token) {

        mDefaultToken = token;

    }

    @Override

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

        applyDefaultToken(params);

        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

    }

    @Override

    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

        applyDefaultToken(params);

        mGlobal.updateViewLayout(view, params);

    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {

        // Only use the default token if we don't have a parent window.

        if (mDefaultToken != null && mParentWindow == null) {

            if (!(params instanceof WindowManager.LayoutParams)) {

                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");

            }

            // Only use the default token if we don't already have a token.

            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

            if (wparams.token == null) {

                wparams.token = mDefaultToken;

            }

        }

    }

    @Override

    public void removeView(View view) {

        mGlobal.removeView(view, false);

    }

    @Override

    public void removeViewImmediate(View view) {

        mGlobal.removeView(view, true);

    }

    @Override

    public void requestAppKeyboardShortcuts(

            final KeyboardShortcutsReceiver receiver, int deviceId) {

        IResultReceiver resultReceiver = new IResultReceiver.Stub() {

            @Override

            public void send(int resultCode, Bundle resultData) throws RemoteException {

                List<KeyboardShortcutGroup> result =

                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);

                receiver.onKeyboardShortcutsReceived(result);

            }

        };

        try {

            WindowManagerGlobal.getWindowManagerService()

                .requestAppKeyboardShortcuts(resultReceiver, deviceId);

        } catch (RemoteException e) {

        }

    }

    @Override

    public Display getDefaultDisplay() {

        return mContext.getDisplay();

    }

可以看到WindowMangaerGlobal这个类才是真正执行window视图添加跟新操作的,这个类有四个数据结构:


    private final ArrayList<View> mViews = new ArrayList<View>();  // 存储所有的DecorView

    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); // 每个窗口对应的视图树管理类

    private final ArrayList<WindowManager.LayoutParams> mParams =

            new ArrayList<WindowManager.LayoutParams>();  // 存储所有window的布局参数 包括窗口类型

    private final ArraySet<View> mDyingViews = new ArraySet<View>(); // 存储刚才remove的窗口中的view

为了方便梳理这个window窗口的添加过程,看一下dialog.show():


public void show() {

        if (mShowing) { // 是否正在展示状态

            if (mDecor != null) {  // 是否设置了view

                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {  // 判断Context带有的主题是否包含ActionBar 或者用户是否手动设置了

                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);

                }

                mDecor.setVisibility(View.VISIBLE); // 设置DecorView可见 当View属于Visible 才会触发绘制流程

            }

            return;

        }

        mCanceled = false;

        if (!mCreated) {

            dispatchOnCreate(null);

        } else {

            // Fill the DecorView in on any configuration changes that

            // may have occured while it was removed from the WindowManager.

            final Configuration config = mContext.getResources().getConfiguration();

            mWindow.getDecorView().dispatchConfigurationChanged(config);  // 当app配置改变时回调OnConfigChange

        }

        onStart();

        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { // 设置样式

            final ApplicationInfo info = mContext.getApplicationInfo();

            mWindow.setDefaultIcon(info.icon);

            mWindow.setDefaultLogo(info.logo);

            mActionBar = new WindowDecorActionBar(this);

        }

        WindowManager.LayoutParams l = mWindow.getAttributes();

        if ((l.softInputMode

                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {

            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();

            nl.copyFrom(l);

            nl.softInputMode |=

                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;

            l = nl;

        }

        mWindowManager.addView(mDecor, l);  // 重点  将DecorView添加给windowmanager

        mShowing = true;

        sendShowMessage();

    }

在Dialog创建的时候PhoneWindow与WindowManager绑定,在show的时候讲DecorView交给WindowManager,继续看 mWindowManager.addView(mDecor, l); 上面知道真正执行addView的事WindowManagerGlobal:


public void addView(View view, ViewGroup.LayoutParams params,

            Display display, Window parentWindow) {

            ...

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

        if (parentWindow != null) {  // 判断是否有父窗口 如果context 会执行下面一句 因为父窗就是Activity所持有的phonewindow

            parentWindow.adjustLayoutParamsForSubWindow(wparams);

        } else {

            // If there's no parent, then hardware acceleration for this view is

            // set from the application's hardware acceleration setting.

            final Context context = view.getContext();

            if (context != null

                    && (context.getApplicationInfo().flags

                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {

                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; //开启硬件加速

            }

        }

        ViewRootImpl root;

        View panelParentView = null;

          // 创建View视图管理

            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);  // 将DecorView交由VIewRootImp管理与Wms交互

            } catch (RuntimeException e) {

                // BadTokenException or InvalidDisplayException, clean up.

                if (index >= 0) {

                    removeViewLocked(index, true);

                }

                throw e;

            }

        }

    }

上面最关键的两行代码:


void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {  // 如果窗口类型是子窗口类型 将该DecorView的WindowToken赋值给该窗口管理器的布局参数

        CharSequence curTitle = wp.getTitle();

        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

            if (wp.token == null) {

                View decor = peekDecorView();

                if (decor != null) {

                    wp.token = decor.getWindowToken();

                }

            }

            }

  1. root.setView(view, wparams, panelParentView); // 将DecorView交由VIewRootImp管理与Wms交互

    最后其实addView交给了ViewRootImpl,截取添加有本文有关的代码:


                int res;                        /* = WindowManagerImpl.ADD_OKAY; */

                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(),

                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mInputChannel);

                if (res < WindowManagerGlobal.ADD_OKAY) {

                    mAttachInfo.mRootView = null;

                    mAdded = false;

                    mFallbackEventHandler.setView(null);

                    unscheduleTraversals();

                    setAccessibilityFocus(null, null);

                    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);

                }

以上代码是ViewRootImpl中setView的代码其中最重要的就是res的赋值,res默认是window可以添加的 WindowManagerImpl.ADD_OKAY:


  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(),

                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mInputChannel);

  • mWindowSession是一个用户与Wms交互的接口,源码查看只有十个左右的接口:

interface IWindowSession {

    int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, out Rect outContentInsets,

            out InputChannel outInputChannel);

    int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, in int layerStackId, out Rect outContentInsets,

            out InputChannel outInputChannel);

    int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, out Rect outContentInsets);

    int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, in int layerStackId, out Rect outContentInsets);

    void remove(IWindow window);



    int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            int requestedWidth, int requestedHeight, int viewVisibility,

            int flags, out Rect outFrame,

            out Rect outContentInsets, out Rect outVisibleInsets,

            out Configuration outConfig, out Surface outSurface);

    void performDeferredDestroy(IWindow window);

    boolean outOfMemory(IWindow window);

    void setTransparentRegion(IWindow window, in Region region);



    void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,

            in Rect visibleInsets, in Region touchableRegion);



    void getDisplayFrame(IWindow window, out Rect outDisplayFrame);



    void finishDrawing(IWindow window);



    void setInTouchMode(boolean showFocus);

    boolean getInTouchMode();



    boolean performHapticFeedback(IWindow window, int effectId, boolean always);



    IBinder prepareDrag(IWindow window, int flags,

            int thumbnailWidth, int thumbnailHeight, out Surface outSurface);



    boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,

            float thumbCenterX, float thumbCenterY, in ClipData data);

void reportDropResult(IWindow window, boolean consumed);



    void dragRecipientEntered(IWindow window);

    void dragRecipientExited(IWindow window);

    void setWallpaperPosition(IBinder windowToken, float x, float y, float xstep, float ystep);



    void wallpaperOffsetsComplete(IBinder window);



    Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,

            int z, in Bundle extras, boolean sync)



    void wallpaperCommandComplete(IBinder window, in Bundle result);

    void setUniverseTransform(IBinder window, float alpha, float offx, float offy,

            float dsdx, float dtdx, float dsdy, float dtdy);



    void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);

}

而此处mWindowSession真正是实现是


class Session extends IWindowSession.Stub implements IBinder.DeathRecipient

每个application对应一个IwindowSession的实现类Session对象 session很明显是Binder的服务端对象,而真正实现窗口添加的还是WindowManagerService,下面是Session添加window的代码:


Session.java

    @Override

    public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,

            int viewVisibility, Rect outContentInsets, Rect outStableInsets,

            InputChannel outInputChannel) {

        return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY,

                new Rect() /* outFrame */, outContentInsets, outStableInsets, null /* outOutsets */,

                new DisplayCutout.ParcelableWrapper()  /* cutout */, outInputChannel);

    }

因此可以得出Session每个application进行窗口操作的唯一对应,想要进行窗口的操作必须通过Session来与Wms交互,因此下面一张图很清楚描述这一关系:

Session关系图

重点来了:

窗口的添加从WindowManager - > WIndowManagerGlobal - > ViewRootImpl -> Session - > WindowMangagerService 这一系列检查包装最终交给Wms,wms的添加窗口操作如下,这个添加方法较长 ,只分析跟本来关联较大代码,拆开分析:

  • 方法名 关注一下这个返回值是一个窗口添加结果返回值

public int addWindow(Session session, IWindow client, int seq,

            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,

            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,

            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel)

  • 窗口 type 判断

// 判断该window是否权限允许

if (!displayContent.hasAccess(session.mUid)

                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {

                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "

                        + "does not have access: " + displayId + ".  Aborting.");

                return WindowManagerGlobal.ADD_INVALID_DISPLAY;

            }

// 判断这个client是否已经存在 这个Client 是每个Wms在客户端回调的Binder类

            if (mWindowMap.containsKey(client.asBinder())) {

                Slog.w(TAG_WM, "Window " + client + " is already added");

                return WindowManagerGlobal.ADD_DUPLICATE_ADD;

            }

// 判断是否是子窗口类型的窗口

            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {

          // 如果是Dialog的Context是activity 且未修改dialog的窗口类型 下面这句代码就会获取到Activity的window

                parentWindow = windowForClientLocked(null, attrs.token, false);

                if (parentWindow == null) {  // 如果为null Activity的窗口已经不再显示(可能销毁 可能pause)

                    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 type如果也是子窗口类型window 返回ADD_BAD_SUBWINDOW_TOKEN

                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;

                }

            }

以上就是为啥你在使用Window时进场报错的最终触发点,而这里的错误只是提供给google工程师看的,我们看到的是在ViewRootImpl中addView时根据IwindowSession.addDisplay()返回值抛给我们的:


int  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,

                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

if (res < WindowManagerGlobal.ADD_OKAY) {

                    mAttachInfo.mRootView = null;

                    mAdded = false;

                    mFallbackEventHandler.setView(null);

                    unscheduleTraversals();

                    setAccessibilityFocus(null, null);

                    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);

                }

以上的错误信息就是我们经常在as的logcat看到的 ,那么既然windowmanager添加之后了解到与wms通信的服务端,那么wms是如何与View通信的呢?其实ViewRootImpl中有一个匿名内部类W类,该类继承IWindow.stub,我们知道binder通信是客户端与服务端是相对的,此时WMS想发消息给客户端,也只能通过BInder了,那WMS持有的客户端的Binder对象便是W类的远程代理,这个bInder代理对象就是在Session.addToDisplay时的参数通过binder通信传过去的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容