Android源碼解析系列1——Activity啟動和界面加載

源碼版本:Android 27

一、應(yīng)用的啟動

首先我們需要知道:

ActivityThreadmain方法梳侨,是Android應(yīng)用程序啟動時的入口點。

public final class ActivityThread {
    // 省略部分代碼

    public static void main(String[] args) {
        // 省略部分代碼

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

在main方法里執(zhí)行了以下操作:

  • 1、首先調(diào)用了Looper.prepareMainLooper()方法铅协,它會創(chuàng)建一個與當前線程(主線程)相關(guān)聯(lián)的Looper對象匠楚。
  • 2巍膘、然后創(chuàng)建一個ActivityThread對象,并調(diào)用其attach()方法芋簿。
  • 3峡懈、最后調(diào)用Looper.loop()方法,讓剛創(chuàng)建的Looper對象与斤,從它的MessageQueue中循環(huán)讀取數(shù)據(jù)并執(zhí)行肪康。

現(xiàn)在,我們開始分析ActivityThreadattach()方法做了什么撩穿?

public final class ActivityThread {
    final ApplicationThread mAppThread = new ApplicationThread();
    // 省略部分代碼

    private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            // 省略部分代碼

            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            // 省略后續(xù)代碼
        }
    }
}

attach方法中磷支,調(diào)用ActivityManager.getService()方法,獲取到遠程的IActivityManager對象食寡,并將一個ApplicationThread實例傳入雾狈。

ApplicationThread繼承至IApplicationThread.Stub,即Binder進程間通信的本地實現(xiàn)類冻河。它有很多與Activity生命周期相關(guān)的方法箍邮,大都以schedule開頭:

image.png

至此茉帅,我們可以總結(jié)一下:

1、ActivityThreadmain方法锭弊,是應(yīng)用程序啟動的入口堪澎。
2、Activity生命周期是由系統(tǒng)底層控制味滞,通過Binder機制樱蛤,回調(diào)到ApplicationThreadscheduleXXX方法中。
3剑鞍、ActivityThreadApplicationThread針對每個應(yīng)用昨凡,都只有一個實例。

這里有個問題:
為什么沒看到scheduleStartActivity方法蚁署?難道Activity的onStart()生命周期回調(diào)便脊,不是由ActivityManager發(fā)送消息控制的?

帶著這個疑問光戈,我們開始閱讀這些scheduleXXX方法的源碼哪痰。

二、Activity生命周期

我們從ApplicationThread中的scheduleLaunchActivity方法開始分析久妆,因為由名字可以猜測它應(yīng)該會執(zhí)行Activity創(chuàng)建和啟動工作晌杰。

public final class ActivityThread {
    // 省略部分代碼

    private class ApplicationThread extends IApplicationThread.Stub {
        // 省略部分代碼

        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            // 給ActivityClientRecord賦值

            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }
        // 省略部分代碼
    }
}

scheduleLaunchActivity方法中,首先創(chuàng)建了一個ActivityClientRecord對象筷弦,并對其進行賦值肋演。然后通過Handler,將線程由Binder線程切換到主線程烂琴。最終調(diào)用到ActivityThreadhandleLaunchActivity方法爹殊。

注意:ActivityClientRecord主要用于記錄與Activity相關(guān)的數(shù)據(jù)。

public final class ActivityThread {
    // 省略部分代碼

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // 省略部分代碼

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            // 省略部分代碼
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

           if (!r.activity.mFinished && r.startsNotResumed) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn't in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);
                // 省略部分代碼
            }
        }
    }
}

這里监右,我們暫時不管performLaunchActivity方法中做了什么便斥,僅分析后續(xù)代碼捧请。后續(xù)代碼中,調(diào)用了handleResumeActivity,猜測它應(yīng)該會調(diào)用Activity的onResume方法殖熟。

根據(jù)Activity生命周期推測到:

performLaunchActivity方法里胀莹,一定會依次調(diào)用Activity的onCreate荒澡、onStart方法橡淆。

帶著這個思路,開始分析performLaunchActivity方法父虑。

public final class ActivityThread {
    // 省略部分代碼

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 省略部分代碼

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            // 省略部分代碼
        } catch (Exception e) { /* 省略部分代碼 */ }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            // 省略部分代碼

            if (activity != null) {
                // 省略部分代碼

                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                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);
                }
                // 省略部分代碼

                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    if (r.isPersistable()) {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state,
                                r.persistentState);
                    } else {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state);
                    }
                    // 省略部分代碼
                }
            }
            r.paused = true;

            // 保存ActivityClientRecord
            mActivities.put(r.token, r);

        } catch  { /* 省略catch相關(guān)代碼 */ }

        return activity;
    }

上述代碼主要執(zhí)行了以下操作:

  • 1该酗、創(chuàng)建Activity對象

調(diào)用InstrumentationnewActivity方法,通過反射創(chuàng)建Activity對象。

  • 2呜魄、初始化Activity

調(diào)用Activity對象的attach方法悔叽,用于初始化Activity的一些數(shù)據(jù),同時會為Activity設(shè)置Window對象爵嗅。注意:Activity的Window對象娇澎,與傳入的Window對象不是同一個對象。這也意味著:每個Activity都有各自的Window對象睹晒。

public class Activity extends .... {
    // 省略部分代碼
    private Window mWindow;
    private WindowManager mWindowManager;

    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.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        // 省略部分代碼

        mWindowManager = mWindow.getWindowManager();
        // 省略部分代碼
    }
    // 省略部分代碼
}
  • 3趟庄、調(diào)用3個生命周期方法

1、Instrumentation.callActivityOnCreate方法伪很,該方法中會調(diào)用activity.performCreate()方法戚啥。
2、activity.performStart()方法锉试。
3猫十、Instrumentation.callActivityOnPostCreate方法,該方法中會調(diào)用activity.onPostCreate()方法键痛。

查看里面的源碼炫彩,確實依次調(diào)用了onCreate、onStart絮短、onPostCreate方法,驗證了我們之前對performLaunchActivity的猜想昨忆。

總結(jié)一下
handleLaunchActivity方法里丁频,會回調(diào)以下生命周期:

onCreate() -> onStart() -> onPostCreate() -> onResume()

注意:如果ActivityClientRecord.startsNotResumed = true時,生命周期流程將會變?yōu)椋?br> onCreate() -> onStart() -> onPostCreate() -> onResume()-> onPause()


二邑贴、界面加載

通過上節(jié)內(nèi)容的介紹席里,我們知道在handleLaunchActivity方法里,會回調(diào)Activity的onCreate()生命周期方法拢驾。

而一般我們在創(chuàng)建Activity時奖磁,會在onCreate()中調(diào)用setContentView來設(shè)置布局文件。

下面繁疤,我們開始分析咖为,我們自定義的布局文件是如何被加載出來的。

2.1 設(shè)置布局

首先分析Activity中的setContentView方法的源碼稠腊。

public class Activity extends .... {
    // 省略部分代碼

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    // 省略部分代碼
}

根據(jù)前面的分析可知躁染,這里的getWindow()方法返回的是該Activity所特有的Window對象,它在attach方法中被賦值架忌。

Window是一個抽象類吞彤,通過查看類上的注解可知,它只有一個名為PhoneWindow的子類,所以我們直接看PhoneWindowsetContentView方法饰恕。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // 省略部分代碼
    ViewGroup mContentParent;

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        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;
    }
    // 省略部分代碼
}

代碼還是比較清楚的挠羔,如果mContentParent == null,調(diào)用installDecor()方法埋嵌,后續(xù)將傳入的布局資源破加,加載到mContentParent中。

所以這里可以肯定:installDecor()方法莉恼,主要作用就是為了創(chuàng)建mContentParent對象拌喉。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // 省略部分代碼
    private DecorView mDecor;
    ViewGroup mContentParent;

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            // 省略部分代碼
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            // 省略后續(xù)設(shè)置icon、title等代碼
        }
    }
}

嗯俐银,這里確實通過generateLayout方法創(chuàng)建了mContentParent對象尿背,但在創(chuàng)建之前,先創(chuàng)建了一個DecorView對象捶惜,并將其作為參數(shù)傳入generateLayout方法里田藐。

DecorView,我們只需要知道它繼承至FrameLayout即可吱七,因為此時分析它的細節(jié)汽久,對于我們并沒太大幫助。

那我們分析generateLayout做了什么:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // 省略部分代碼
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        // 省略部分代碼:從主題文件中讀取內(nèi)容踊餐,并設(shè)置對應(yīng)的Flag

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        //省略部分代碼:通過features景醇,為layoutResource設(shè)置不同布局資源id

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        //省略部分代碼:為mDecor設(shè)置background、elevation吝岭、title三痰、titleColor等數(shù)據(jù)

        mDecor.finishChanging();

        return contentParent;
    }
}

主要做了以下內(nèi)容:

  • 1、從主題中獲取數(shù)據(jù)窜管,并應(yīng)用到當前Window中散劫。
  • 2、根據(jù)features幕帆,讓mDecor加載不同的布局文件获搏。
  • 3、獲得mContentParent對象(id為com.android.internal.R.id.content)失乾。

這里我們可以得出以下信息:

  • 1常熙、不同的主題會讓Window加載不同的布局到DecorView中。
  • 2仗扬、setContentView方法症概,實際是將自定義的布局文件,加載到mContentParent中早芭。

至此彼城,我們可以簡單總結(jié)一下setContentView的流程:

1、首先,Activity中的Window對象會創(chuàng)建一個DecorView募壕。
2调炬、然后根據(jù)不同的主題,讓DecorView加載不同的布局資源舱馅。
3缰泡、獲取這些布局資源中的mContentParent,它的id為com.android.internal.R.id.content代嗤。
4棘钞、最后將自定義的布局加載到mContentParent中。

2.2 渲染布局

在上述setContentView的流程中干毅,所有的布局資源都已加載完畢宜猜,而布局的加載又會涉及到addView方法。

一般來說硝逢,調(diào)用addView方法姨拥,都會間接調(diào)用到requestLayout()invalidate(true)方法,造成界面重新布局和刷新渠鸽。

而我們也知道:

Activity在onCreate時叫乌,界面并不會被加載出來。

這里仿佛出現(xiàn)了矛盾徽缚,那我們再分析分析憨奸,看看為什么調(diào)用了addView方法后,界面卻沒有被加載出來凿试。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    // 省略部分代碼

    public void addView(View child, int index, LayoutParams params) {
        // 省略部分代碼
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
    // 省略部分代碼
}
public class View implements ... {
    // 省略部分代碼

    public void requestLayout() {
        // 省略部分代碼
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        // 省略部分代碼
    }

    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        // 省略部分代碼
        if (skipInvalidate()) {
            return;
        }
        // 省略后續(xù)代碼
    }

    private boolean skipInvalidate() {
        return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
                (!(mParent instanceof ViewGroup) ||
                        !((ViewGroup) mParent).isViewTransitioning(this));
    }
    // 省略部分代碼
}

哦膀藐,原來此時DecorView沒有父容器,導致這里只會執(zhí)行添加操作红省,而不會去重新布局和刷新。


那什么時候国觉,界面才會被加載出來呢吧恃?

只要學過Android生命周期的人,都知道:

當Activity處于onCreate時麻诀,是不可見的痕寓。
當Activity處于onStart時,可見蝇闭,但不可交互呻率,
當Activity處于onResume時,才是可見可交互的呻引。

那我們看看ActivityperformStart方法實現(xiàn):

public class Activity extends .... {
    // 省略部分代碼

    final void performStart() {
        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
        mFragments.noteStateNotSaved();
        mCalled = false;
        mFragments.execPendingActions();
        mInstrumentation.callActivityOnStart(this);
        if (!mCalled) {
            throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onStart()");
        }
        mFragments.dispatchStart();
        mFragments.reportLoaderStart();

       // 省略部分代碼

        mActivityTransitionState.enterReady(this);
    }

這里只要調(diào)用了InstrumentationcallActivityOnStart方法礼仗,而callActivityOnStart方法內(nèi)部的實現(xiàn),只是簡單調(diào)用了傳入的Activity對象的onStart()方法。

emmmmm......好像有點不對啊元践,performStart方法里好像也沒做什么操作啊韭脊,難不成界面渲染是在onResume里?

帶著疑惑单旁,我們一起來看看handleResumeActivity方法:

public final class ActivityThread {
    // 省略部分代碼

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        // 省略部分代碼

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;
            // 省略部分代碼

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                // 省略部分代碼

                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }
                // 省略后續(xù)代碼
            }
        }
    }
}

其中performResumeActivity也沒做過多操作沪羔,只是調(diào)用了Activity的performResume()方法,間接調(diào)用到onResume象浑,我們就不過多分析了蔫饰。
這里比較核心的是,將DecorView添加到ViewManager中愉豺,這里間接調(diào)用到WindowManagerGlobaladdView方法篓吁,它是一個單例對象,注意這里的判定條件粒氧,可以看出越除,一個ActivityClientRecord對象,之后執(zhí)行一次外盯。

public final class WindowManagerGlobal {
    // 省略部分代碼
    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>();

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 省略部分代碼

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

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // 省略部分代碼

            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;
            }
        }
    }
    // 省略部分代碼
}

這里先創(chuàng)建ViewRootImpl對象摘盆,然后進行緩存到數(shù)組中。接下來把DecorView設(shè)置到ViewRootImpl中饱苟,進而執(zhí)行到ViewRootImplrequestLayout() -> scheduleTraversals() -> doTraversal()方法孩擂,最終執(zhí)行到performTraversals()方法中,流程圖如下:

image.png

View的繪制流程箱熬,會涉及到測量类垦、擺放繪制三部分,這個我們后面單獨在View渲染章節(jié)來講城须。我們只需明白蚤认,此時界面就已經(jīng)被渲染出來了。

2.3糕伐、延伸

現(xiàn)在砰琢,我們已經(jīng)知道了,Activity渲染到屏幕良瞧,是在onResume()之后才完成的陪汽,那為啥說,onStart()可見但不可交互的呢褥蚯?
這里就不賣關(guān)子了挚冤,其實官方所說的"可見",其實有一定的歧義赞庶,我認為:

"可見"只是針對于已被渲染過的Activity训挡,而不是正在創(chuàng)建的Activity澳骤。

參照下面這張圖來解釋一下:

image.png

1、對于創(chuàng)建的Activity舍哄,只是簡單的走了生命周期宴凉,渲染是在Resumed時完成的。
2表悬、對于已被渲染過的Activity:

1弥锄、當它由Resumed狀態(tài)切換到Started狀態(tài),界面被部分覆蓋蟆沫,失去焦點籽暇,即無法進行交互。
2饭庞、當它由Started狀態(tài)切換到Created狀態(tài)戒悠,界面被完全覆蓋,即不可見舟山。
3绸狐、當它由Created狀態(tài)切換到Started狀態(tài),界面再次被部分覆蓋累盗,依然獲取不到焦點寒矿,無法交互。
4若债、當它由Started狀態(tài)切換到Resumed狀態(tài)符相,界面完全顯示。

正是因為這樣蠢琳,才會造成以下這個的問題:

Activity創(chuàng)建過程中啊终,在onCreate()onStart()傲须、onResume()方法里蓝牲,都無法獲取控件的寬高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泰讽,一起剝皮案震驚了整個濱河市搞旭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菇绵,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镇眷,死亡現(xiàn)場離奇詭異咬最,居然都是意外死亡,警方通過查閱死者的電腦和手機欠动,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門永乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惑申,“玉大人,你說我怎么就攤上這事翅雏∪ν眨” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵望几,是天一觀的道長绩脆。 經(jīng)常有香客問我,道長橄抹,這世上最難降的妖魔是什么靴迫? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮楼誓,結(jié)果婚禮上玉锌,老公的妹妹穿的比我還像新娘。我一直安慰自己疟羹,他們只是感情好主守,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著榄融,像睡著了一般参淫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剃袍,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天黄刚,我揣著相機與錄音,去河邊找鬼民效。 笑死憔维,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的畏邢。 我是一名探鬼主播业扒,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舒萎!你這毒婦竟也來了程储?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤臂寝,失蹤者是張志新(化名)和其女友劉穎章鲤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咆贬,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡败徊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掏缎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皱蹦。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡煤杀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沪哺,到底是詐尸還是另有隱情沈自,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布辜妓,位于F島的核電站枯途,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嫌拣。R本人自食惡果不足惜柔袁,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望异逐。 院中可真熱鬧捶索,春花似錦、人聲如沸灰瞻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酝润。三九已至燎竖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間要销,已是汗流浹背构回。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疏咐,地道東北人纤掸。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像浑塞,于是被迫代替她去往敵國和親借跪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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