Activity的Window創(chuàng)建和添加過程-源碼分析

之前介紹過Activity的啟動過程墨闲,一直講到了Actiivty的oCreate方法執(zhí)行题禀,Activity的啟動過程完成缀蹄。本文就順著啟動過程的完成,介紹下Activity的Window創(chuàng)建和添加的過程。為什么講這個(gè)呢令花,因?yàn)锳ctivity啟動過程完成到onCreate方法的執(zhí)行锦担,Activity的頁面還沒有顯示出來俭识,而Activity的頁面布局的顯示,就是由Window吆豹、WIndowManager和WindowManagerService這一套機(jī)制來管理的鱼的,也就是通常說的WMS機(jī)制。
在Android系統(tǒng)中痘煤,一個(gè)Activity通常就表示一個(gè)頁面凑阶,這個(gè)頁面實(shí)際是由Window來管理的。每個(gè)Activity都對應(yīng)著一個(gè)Window衷快。Window是一個(gè)抽象類宙橱,具體實(shí)現(xiàn)類是PhoneWindow,它對View進(jìn)行管理蘸拔。確切的來講是师郑,PhoneWindow包含一個(gè)DecorView類型的成員,它代表這個(gè)Window的頂層View调窍,是一個(gè)FrameLayout宝冕。DecorView的布局結(jié)構(gòu)包含兩部分:標(biāo)題欄(title)和內(nèi)容欄(content)。根據(jù)設(shè)置的主題不同邓萨,這兩部分也會有不同的呈現(xiàn)地梨。但內(nèi)容欄是一定存在的菊卷,并且它的id是固定的,完整id都是android.R.id.content宝剖。

需要說明一下:本文的源碼分析是基于Android 8.0版本洁闰,也就是API Level26的源碼。

Window的創(chuàng)建過程

熟悉Activity啟動過程的同學(xué)應(yīng)該都記得万细,在ActivityThread的performLaunchActivity方法扑眉,會調(diào)用Activity的attach方法。與Activity相關(guān)聯(lián)的Window對象就是在attach方法中創(chuàng)建的赖钞。

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, IBinder assistToken) {
    attachBaseContext(context);
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
     ...

    mWindow.setColorMode(info.colorMode);
}

可以看到侮叮,在attach方法中創(chuàng)建了一個(gè)PhoneWindow對象般码,并為它設(shè)置回調(diào)接口Callback和WindowManager拥娄。由于Activity類實(shí)現(xiàn)了Window.Callback接口迄靠,因此當(dāng)Window接收到相關(guān)的事件觸發(fā)時(shí)就會調(diào)用Activity的相應(yīng)方法。Callback接口中的方法很多卓缰,有幾個(gè)是我們比較常見的计呈,比如dispatchTouchEvent、onAttachedToWindow征唬、onDetachedFromWindow等捌显。

Window的添加過程

上面已經(jīng)介紹了Activity關(guān)聯(lián)的Window對象是在什么時(shí)候創(chuàng)建的,接下來总寒,我們看看Activity的Window的添加過程扶歪。
在日常開發(fā)中,我們都會在Activity的onCreate方法中摄闸,通過setContentView來給Activity設(shè)置視圖布局善镰,來指定頁面顯示什么內(nèi)容。
我們需要先看看ActivityThread的performLaunchActivity方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
        ...
        if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
}

在Activity的attach方法返回后年枕,程序會調(diào)用mInstrumentation.callActivityOnCreate方法炫欺,而這個(gè)方法最終會觸發(fā)Activity的onCreate回調(diào)。而在onCreate中熏兄,會調(diào)用setContentView方法品洛,開始Activity的Window添加過程。

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

Activity的setContentView方法內(nèi)部調(diào)用的mWindow的setContentView方法摩桶,這個(gè)mWindow對象就是在attach方法中創(chuàng)建的PhoneWindow對象桥状。所以,接下來要看到PhoneWindow的setContentView方法

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        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);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

PhoneWindow的setContentView中硝清,首先判斷mContentParent是否存在辅斟,否則調(diào)用installDecor方法。這個(gè)mContentParent指的就是DecorView的內(nèi)容欄芦拿。它的賦值就只有一個(gè)地方士飒,就是在installDecor方法中挽霉。

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    ...
}

如果mDecor為null,就先調(diào)用generateDecor方法創(chuàng)建DecorView

protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

DecorView對象創(chuàng)建之后变汪,再判斷mContentParent對象是否存在,否則調(diào)用generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
    ...
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    return contentParent;
}

generateLayout方法的代碼很多蚁趁,我們先只關(guān)注相關(guān)的重點(diǎn)裙盾,generateLayout最后會返回一個(gè)ViewGroup對象contentParent,而contentParent的id是ID_ANDROID_CONTENT他嫡,它的值就是com.android.internal.R.id.content番官。這就是DecorView的內(nèi)容欄id為android.R.id.content的由來,也是Activity的SetContentView方法名的由來钢属。
現(xiàn)在回頭看到PhoneWindow的setContentView方法徘熔,調(diào)用installDecor方法,創(chuàng)建好DecorView和mContentParent之后淆党,會調(diào)用 mLayoutInflater.inflate(layoutResID, mContentParent)酷师,將Activity的布局視圖添加到mContentParent中。然后回調(diào)Activity的onContentChanged方法通知Activity染乌,視圖已經(jīng)發(fā)生改變山孔。
以上就是,Activity的視圖布局已經(jīng)添加到DecorView的過程荷憋。這個(gè)過程是在Activity的onCreate方法中完成的台颠。但這個(gè)時(shí)候,Activity的視圖布局還沒有顯示出來勒庄。這是因?yàn)榇埃珼ecorView還沒有被WindowManager正式添加到窗口中。這里說的窗口实蔽,并不是指WIndow和它的子類PhoneWindow荡碾,而是一個(gè)抽象的概念。窗口更多表示的是一種抽象的功能集合局装。雖然在Activity的attach方法中玩荠,WIndow對象就已經(jīng)被創(chuàng)建了,但這個(gè)時(shí)候由于DecorView并沒有被WindowManager識別贼邓,所以此時(shí)的WIndow無法提供具體功能阶冈,因?yàn)樗€無法接受外界的輸入信息。我們知道塑径,在Activity執(zhí)行onResume方法之后女坑,視圖才能完全顯示,并和用戶正常交互统舀,那onResume方法是在什么地方回調(diào)了呢匆骗,熟悉Activity啟動過程的同學(xué)劳景,應(yīng)該會有印象,在ActivityThread的handleLaunchActivity方法中碉就,

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
    r.createdConfig = new Configuration(mConfiguration);
    reportSizeConfigurations(r);
    Bundle oldState = r.state;
    handleResumeActivity(r.token, false, r.isForward,
            !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    ...
}

執(zhí)行完performLaunchActivity方法返回一個(gè)Activity的實(shí)例盟广,接下來判斷如果創(chuàng)建的Activity實(shí)例不為null,就會執(zhí)行handleResumeActivity方法

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    r = performResumeActivity(token, clearHide, reason);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    ...
}

在handleResumeActivity方法中瓮钥,會調(diào)用performResumeActivity方法筋量,這個(gè)方法中又經(jīng)過層層調(diào)用,最終會回調(diào)Activity的onResume方法碉熄。這就是Activity的onResume方法的回調(diào)時(shí)機(jī)桨武。這里不是本文的重點(diǎn),就沒有列出相關(guān)的源碼锈津。
handleResumeActivity中呀酸,在performResumeActivity方法執(zhí)行之后,會調(diào)用Activity的makeVisible方法

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

在makeVisible方法中琼梆,會調(diào)用WindowManager的addView方法性誉,將DecorView正式添加到窗口中。同時(shí)DecorView設(shè)置為可見茎杂。
這里先介紹下WindowManager艾栋,它是一個(gè)接口,繼承自ViewManager蛉顽。從ViewManager接口繼承了三個(gè)方法addView蝗砾、updateViewLayout和removeView。這三個(gè)方法都與窗口管理直接相關(guān)携冤。而WindowManager的實(shí)現(xiàn)類是WindowManagerImpl悼粮。所以Activity的makeVisible方法中,調(diào)用的實(shí)際上是WindowManagerImpl的addView方法

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

在WindowManagerImpl的addView方法內(nèi)部曾棕,調(diào)用的是WindowManagerGlobal的addView方法扣猫。WindowManagerImpl通過橋接模式,將功能實(shí)現(xiàn)委托給了WindowManagerGlobal翘地。WindowManagerGlobal是一個(gè)單例申尤,說明一個(gè)進(jìn)程中只有一個(gè)WindowManagerGlobal實(shí)例。而每一個(gè)Window都會有一個(gè)相關(guān)聯(lián)的WindowManagerImpl實(shí)例衙耕。WindowManagerGlobal的addView方法如下:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        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;
    ...
        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.
        }
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

WindowManagerGlobal的addView方法主要完成三個(gè)步驟:

  1. 檢查參數(shù)是否合法昧穿,如果是子Window還需要調(diào)整一些布局參數(shù)
  2. 創(chuàng)建ViewRootImpl,并將傳進(jìn)來的View添加到mViews列表里
  3. 通過ViewRootImpl來更新界面并完成WIndow的添加過程橙喘。

上來的代碼中时鸵,涉及到WindowManagerGlobal類幾個(gè)重要的列表

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

簡單說明一下,mViews存放的是所有窗口所對應(yīng)的View,比如Activity的窗口所對應(yīng)的DecorView饰潜。mRoots存放的是所有窗口所對應(yīng)的ViewRootImpl對象初坠。mParams存放的是所有窗口所對應(yīng)的布局參數(shù)。而在這三個(gè)列表中彭雾,索引值相同的一組View碟刺、ViewRootImpl和LayoutParam對象,可以看做是一個(gè)窗口的概念薯酝。mDyingViews則存儲了正在被刪除的View半沽,或者說是那些已經(jīng)調(diào)用了removeView方法但刪除操作還沒有完成的Window對象。
View和LayoutParams應(yīng)該都不會陌生蜜托,關(guān)于ViewRootImpl,熟悉View繪制機(jī)制的同學(xué)霉赡,也會對它有印象橄务。通俗的說法是,一個(gè)頁面的繪制流程穴亏,就是從ViewRootImpl開始蜂挪。而實(shí)際上ViewRootImpl承擔(dān)的職責(zé)包括以下幾點(diǎn):

  • 作為View層級(View hierarchy)的頂層(top-level)管理View層級
  • 觸發(fā)View的測量、布局和繪制
  • 輸入事件的中轉(zhuǎn)站
  • 負(fù)責(zé)與WMS進(jìn)程間通信
    介紹完WindowManagerGlobal中這幾個(gè)重要的列表之后嗓化,我們接著看addView方法棠涮。將View、ViewRootImpl和LayoutParams對象分別添加到列表之后刺覆,會調(diào)用ViewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    requestLayout();
    ...
    try {
    mOrigWindowType = mWindowAttributes.type;
    mAttachInfo.mRecomputeGlobalAttributes = true;
    collectViewAttributes();
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(),
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mOutsets, mInputChannel);
    ...
}

setView方法的代碼很多严肪,本文中暫時(shí)只關(guān)注兩點(diǎn)。
調(diào)用requestLayout方法會觸發(fā)View層級的繪制遍歷谦屑。這個(gè)對于了解View的繪制過程的同學(xué)肯定不陌生驳糯。簡單介紹下,requestLayout方法內(nèi)部會調(diào)用scheduleTraversals方法氢橙。scheduleTraversals方法實(shí)際上就是View繪制過程的入口酝枢。
然后會調(diào)用mWindowSession對象的addToDisplay方法,mWindowSession的類型是IWindowSession悍手,它是一個(gè)Binder對象帘睦,用于進(jìn)程間通信,IWindowSession是Client端的代理坦康。它的Server端實(shí)現(xiàn)是Session竣付。此前的代碼都是運(yùn)行在Actiivty所在的app進(jìn)程,而Session的addToDisplay方法則是運(yùn)行在WMS所在的SystemServer進(jìn)程中滞欠。


image.png

從上圖中可以看出卑笨,app進(jìn)程中的ViewRootImpl對象要想與WMS進(jìn)行通信需要經(jīng)過Session,確切的說仑撞,通過調(diào)用Session的addToDisplay方法

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

addToDisplay方法內(nèi)部赤兴,直接調(diào)用了mService的addWindow方法妖滔,并將Session對象本身作為第一個(gè)參數(shù)傳進(jìn)去。這個(gè)mService就是WMS的實(shí)例桶良。每一個(gè)app進(jìn)程都會對應(yīng)一個(gè)Session對象座舍,它用來表示app進(jìn)程與WMS的通信渠道。WMS會用ArrayList來存放這些Session對象陨帆。接下來的工作就交給WMS來處理曲秉。WMS會為這個(gè)要添加的窗口分配Surface,并確定窗口的顯示次序疲牵,而真正負(fù)責(zé)顯示界面視圖的是畫布Surface承二,而不是窗口本身。WMS會將它所管理的Surface交由SurfaceFlinger處理纲爸,SurfaceFlinger會將這些Surface混合并繪制到屏幕上亥鸠。
WindowManagerService的addWindow方法代碼非常多,有興趣的同學(xué)可以自行查看源碼识啦。這里只是概括性的介紹addWindow方法中主要做了四件事:

  • 對要添加的窗口進(jìn)行檢查负蚊,確保窗口和一些參數(shù)滿足條件,否則就直接返回相應(yīng)的提示信息颓哮,不再往下執(zhí)行家妆。
  • WindowToken的相關(guān)處理,比如有的窗口類型需要提供WindowToken冕茅,沒有提供的話也會直接返回相應(yīng)的提示信息伤极,不再往下執(zhí)行。有的窗口類型需要由WMS隱式創(chuàng)建WindowToken姨伤。
  • WindowState的創(chuàng)建和相關(guān)處理塑荒,將WIndowToken和WindowState相關(guān)聯(lián)。
  • 創(chuàng)建和配置DisplayContent姜挺,完成窗口添加到系統(tǒng)前的準(zhǔn)備工作齿税。

關(guān)于WindowToken
要了解WindowToken,首先要了解ActivityRecord的內(nèi)部類Token炊豪,它繼承自IApplicationToken.Stub凌箕。很明顯,它是基于Binder機(jī)制词渤。Binder除了用于跨進(jìn)程通信之外牵舱,另一個(gè)用途就是在多個(gè)進(jìn)程中標(biāo)識同一個(gè)對象,這里的Token主要是用于后者缺虐。一個(gè)Token對象(ActivityRecord的成員變量appToken)標(biāo)識了一個(gè)ActivityRecord對象芜壁。而WindowToken中,IBinder類型token對象標(biāo)識的也是ActivityRecord中的這個(gè)Token對象appToken。有興趣的朋友可以由此去了解慧妄。

關(guān)于Surface顷牌、Window和View概念的理解
以下是由安卓framework開發(fā)者Dianne Hackborn在stackoverflow給出的關(guān)于Window和View的定義,由此可以更好的理解WIndow和View的關(guān)系
A Surface is an object holding pixels that are being composited to the screen. Every window you see on the screen (a dialog, your full-screen activity, the status bar) has its own surface that it draws in to, and Surface Flinger renders these to the final display in their correct Z-order. A surface typically has more than one buffer (usually two) to do double-buffered rendering: the application can be drawing its next UI state while the surface flinger is compositing the screen using the last buffer, without needing to wait for the application to finish drawing.
A window is basically like you think of a window on the desktop. It has a single Surface in which the contents of the window is rendered. An application interacts with the Window Manager to create windows; the Window Manager creates a Surface for each window and gives it to the application for drawing. The application can draw whatever it wants in the Surface; to the Window Manager it is just an opaque rectangle.
A View is an interactive UI element inside of a window. A window has a single view hierarchy attached to it, which provides all of the behavior of the window. Whenever the window needs to be redrawn (such as because a view has invalidated itself), this is done into the window's Surface. The Surface is locked, which returns a Canvas that can be used to draw into it. A draw traversal is done down the hierarchy, handing the Canvas down for each view to draw its part of the UI. Once done, the Surface is unlocked and posted so that the just drawn buffer is swapped to the foreground to then be composited to the screen by Surface Flinger.

到此塞淹,本文對Activity的Window創(chuàng)建和添加過程分析就講到這里窟蓝。

本文參考
《Android開發(fā)藝術(shù)探索》
《Android進(jìn)階解密》
深入理解Activity——Token之旅
https://stackoverflow.com/questions/4576909/understanding-canvas-and-surface-concepts#answers

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市饱普,隨后出現(xiàn)的幾起案子运挫,更是在濱河造成了極大的恐慌,老刑警劉巖套耕,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谁帕,死亡現(xiàn)場離奇詭異,居然都是意外死亡冯袍,警方通過查閱死者的電腦和手機(jī)匈挖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颠猴,“玉大人关划,你說我怎么就攤上這事小染∏涛停” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵裤翩,是天一觀的道長资盅。 經(jīng)常有香客問我,道長踊赠,這世上最難降的妖魔是什么呵扛? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮筐带,結(jié)果婚禮上今穿,老公的妹妹穿的比我還像新娘。我一直安慰自己伦籍,他們只是感情好蓝晒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帖鸦,像睡著了一般芝薇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上作儿,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天洛二,我揣著相機(jī)與錄音,去河邊找鬼。 笑死晾嘶,一個(gè)胖子當(dāng)著我的面吹牛妓雾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播变擒,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼君珠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娇斑?” 一聲冷哼從身側(cè)響起策添,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毫缆,沒想到半個(gè)月后唯竹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苦丁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年浸颓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旺拉。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡产上,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛾狗,到底是詐尸還是另有隱情晋涣,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布沉桌,位于F島的核電站谢鹊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏留凭。R本人自食惡果不足惜佃扼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔼夜。 院中可真熱鬧兼耀,春花似錦、人聲如沸求冷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遵倦。三九已至尽超,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梧躺,已是汗流浹背似谁。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工傲绣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巩踏。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓秃诵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親塞琼。 傳聞我的和親對象是個(gè)殘疾皇子菠净,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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