Android View工作流程

本文來自網(wǎng)絡(luò)店乐,也可能略有改動魏割,如有任何不妥可以聯(lián)系刪除来惧,原文地址 http://www.reibang.com/p/af266ff378c6

本文的目的有兩個:

  1. 給對自定義 View 感興趣的人一些入門的指引
  2. 給正在使用自定義 View 的人一些更深入的解析
    自定義 View 一直都被認為是 Android 開發(fā)高手的必備技能暇赤,而穩(wěn)中帶皮的學(xué)習(xí) View 的基礎(chǔ)體系心例,這是自定義 View 的必經(jīng)之路宵凌,如果自定義 View 如果設(shè)計的不好或者不考慮性能的話會造成很大的問題鞋囊。所以我們進入 View 工作流程的分析。

一瞎惫、Android 的 UI 層級繪制體系

Android 中的 Activity 是作為應(yīng)用程序的載體存在的溜腐,它代表一個完整的用戶界面并提供了窗口進行視圖繪制。

  • 在這里瓜喇,我們這里所說的視圖繪制挺益,實質(zhì)上就是在對 View 及其子類進行操作。而 View 作為視圖控件的頂層父類乘寒,在本文中會對其進行詳細分析望众。我們以 Android 的 UI 層級繪制體系為切入點對 View 進行探究。

    圖 1 View 的層級結(jié)構(gòu)

Android 的 UI 層級繪制體系如圖 1 所示伞辛。

繪制體系中做了這些事情
①當調(diào)用 Activity 的 setContentView 方法后會調(diào)用 PhoneWindow 類的 setContentView 方法(PhoneWindow 是抽象類 Windiw 的實現(xiàn)類烂翰,Window 用來描述 Activity 視圖最頂端的窗口的顯示內(nèi)容和行為動作)。
②PhoneWindow 類的 setContentView 方法中最終會生成一個 DecorView 對象(DectorView 是是 PhoneWindow 的內(nèi)部類蚤氏,繼承自 FrameLayout)甘耿。
③DecorView 容器中包含根布局,根布局中包含一個 id 為 content 的 FrameLayout 布局竿滨,Activity 加載布局的 xml 最后通過 LayoutInflater 將 xml 文件中的內(nèi)容解析成 View 層級體系佳恬,最后填加到 id 為 content 的 FrameLayout 布局中。

至此于游,View 最終就會顯示到手機屏幕上毁葱。

二、View 的視圖繪制流程剖析

1贰剥、DecorView 被加載到 Window 中

DecorView 被加載到 Window 的過程中倾剿,WindowManager 起到了關(guān)鍵性的作用,最后交給 ViewRootImpl 做詳細處理鸠澈,通過如下的局部 ActivityThread 的源碼分析這一點可以得到印證 (在這里我只展示核心源碼柱告,詳細源碼可以在代碼中查看)截驮。

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
       ...
       
        r = performResumeActivity(token, clearHide, reason);
       ...
       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();
          ...
          if (r.mPreserveWindow) {
          ...
          
          ViewRootImpl impl = decor.getViewRootImpl();
           ...
          }
          if (a.mVisibleFromClient) {
              if (!a.mWindowAdded) {
                   a.mWindowAdded = true;
                   
                   wm.addView(decor, l);
                   } 
                   ...
          }
          ...
    }


WindowManager 將 DecorView 添加到 PhoneWindow 中,即 addView() 方法執(zhí)行時將視圖添加的動作交給了 ViewRoot际度,ViewRoot 作為接口葵袭,其實現(xiàn)類 ViewRootImpl 具體實現(xiàn)了 addView() 方法,最后乖菱,視圖的具體繪制在 performTraversals() 中展開坡锡,如下圖 2.1 所示:

圖 2.1 View 繪制的代碼層級分析

2、ViewRootImpl 的 performTraversals() 方法完成具體的視圖繪制流程

在源碼中 ViewRootImpl 中視圖具體繪制的流程如下:

private void performTraversals() {
        
        
        final View host = mView;
        
        if (host == null || !mAdded)
            return;
        
        mIsInTraversal = true;
        
        mWillDrawSoon = true;
         ...
        
        int desiredWindowWidth;
        int desiredWindowHeight;

         ...
        
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            
            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                DisplayMetrics packageMetrics =
                    mView.getContext().getResources().getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
            }
    }
  ...

 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

  
  
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
  
 performLayout(lp, desiredWindowWidth, desiredWindowHeight);
  ...
  
 performDraw();
}


該方法主要流程就體現(xiàn)了 View 繪制渲染的三個主要步驟窒所,分別是測量鹉勒,擺放,繪制三個階段吵取。流程圖如下圖 2.2 所示:

圖 2.2 View 的繪制流程

接下來禽额,我們對于 performMeasure()、performLayout()皮官、 performDraw() 完成具體拆解分析脯倒。實質(zhì)上最后就需要定位到 View 的 onMeasure()、onLayout()捺氢、onDraw() 方法中藻丢。

三、MeasureSpec 在 View 體系中的作用

1摄乒、MeasureSpec 的作用

首先我們從 performMeasure() 入手分析悠反,在上面的內(nèi)容中,我們通過源碼可以看到 performMeasure() 方法中傳入了 childWidthMeasureSpec馍佑、childHeightMeasureSpec 兩個 int 類型的值斋否,performMeasure 方法的源碼如下所示:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}


這兩個值又傳遞到 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec) 方法中,其中 measure 方法的核心源碼如下:

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
        ...
        if (forceLayout || needsLayout) {
            
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                
                
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                ...
            } 
         ...
    }


到這里我們應(yīng)該明確挤茄,childWidthMeasureSpec, childHeightMeasureSpec 是 MeasureSpec 根據(jù)原有寬高計算獲取不同模式下的具體寬高值如叼。

2、MeasureSpec 剖析

MeasureSpec 是 View 的內(nèi)部類穷劈,內(nèi)部封裝了 View 的規(guī)格尺寸笼恰,以及 View 的寬高信息。在 Measure 的流程中歇终,系統(tǒng)會將 View 的 LayoutParams 根據(jù)父容器是施加的規(guī)則轉(zhuǎn)換為 MeasureSpec社证,然后在 onMeasure() 方法中具體確定控件的寬高信息。源碼及分析如下所示:

public static class MeasureSpec {
        
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}
        

        
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        
        
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            
            return (measureSpec & MODE_MASK);
        }

        
        
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }



MeasureSpec 的常量中指定了兩種內(nèi)容评凝,一種為尺寸模式追葡,一種為具體的寬高信息。其中高 2 位表示尺寸測量模式,低 30 位表示具體的寬高信息宜肉。
尺寸測量模式有如下三種:

尺寸測量模式的 3 種類型
①UNSPECIFIED:未指定模式匀钧,父容器不限制 View 的大小,一般用于系統(tǒng)內(nèi)部的測量
②AT_MOST:最大模式谬返,對應(yīng)于在 xml 文件中指定控件大小為 wrap_content 屬性之斯,子 View 的最終大小是父 View 指定的大小值,并且子 View 的大小不能大于這個值
③EXACTLY :精確模式遣铝,對應(yīng)于在 xml 文件中指定控件為 match_parent 屬性或者是具體的數(shù)值佑刷,父容器測量出 View 所需的具體大小

我?guī)湍憧偨Y(jié)一下

對于每一個 View,都持有一個 MeasureSpec酿炸,MeasureSpec 保存了該 View 的尺寸測量模式以及具體的寬高信息瘫絮,MeasureSpec 受自身的 LayoutParams 和父容器的 MeasureSpec 共同影響。

四填硕、View 的 Measure 流程分析

1麦萤、View 樹的 Measure 測量流程邏輯圖

2、View 的 Measure 流程分析

那么在上文 3.1 的分析中廷支,我們能夠明確在 measure 方法中最后調(diào)用 onMeasure() 方法完成子 View 的具體測量频鉴,onMeasure() 方法的源碼如下所示:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }


setMeasuredDimension() 方法在 onMeasure() 中被調(diào)用,被用于存儲測繪的寬度恋拍、高度,而不這樣做的話會觸發(fā)測繪時的異常藕甩。

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }


在 setMeasuredDimension() 方法中傳入的是 getDefaultSize()施敢,接著分析 getDefaultSize() 中做了哪些操作:

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }


通過上文對 MeasureSpec 的分析,在這里我們就能明確狭莱,getDefaultSize 實質(zhì)上就是根據(jù)測繪模式確定子 View 的具體大小僵娃,而對于自定義 View 而言,子 View 的寬高信息不僅由自身決定腋妙,如果它被包裹在 ViewGroup 中就需要具體測量得到其精確值默怨。

3、View 的 Measure 過程中遇到的問題以及解決方案

View 的 measure 過程和 Activity 的生命周期方法不是同步執(zhí)行的骤素,因此無法保證 Activity 執(zhí)行了 onCreate匙睹、onStart、onResume 時某個 View 已經(jīng)測量完畢了济竹。如果 View 還沒有測量完畢痕檬,那么獲得的寬和高都是 0。下面是 3 種解決該問題的方法:

①Activity/View 的 onWindowsChanged() 方法

onWindowFocusChanged() 方法表示 View 已經(jīng)初始化完畢了送浊,寬高已經(jīng)準備好了梦谜,這個時候去獲取是沒問題的。這個方法會被調(diào)用多次,當 Activity 繼續(xù)執(zhí)行或者暫停執(zhí)行的時候唁桩,這個方法都會被調(diào)用闭树,代碼如下:

  public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
       if(hasWindowFocus){
       int width=view.getMeasuredWidth();
       int height=view.getMeasuredHeight();
      }      
  }


②View.post(runnable) 方法

通過 post 將一個 Runnable 投遞到消息隊列的尾部,然后等待 Looper 調(diào)用此 runnable 的時候 View 也已經(jīng)初始化好了

@Override  
protected void onStart() {  
    super.onStart();  
    view.post(new Runnable() {  
        @Override  
        public void run() {  
            int width=view.getMeasuredWidth();  
            int height=view.getMeasuredHeight();  
        }  
    });  
}  


③ViewTreeObsever

使用 ViewTreeObserver 的眾多回調(diào)方法可以完成這個功能荒澡,比如使用 onGlobalLayoutListener 接口蔼啦,當 View 樹的狀態(tài)發(fā)生改變或者 View 樹內(nèi)部的 View 的可見性發(fā)生改變時,onGlobalLayout 方法將被回調(diào)仰猖。伴隨著 View 樹的變化捏肢,這個方法也會被多次調(diào)用。

  @Override  
  protected void onStart() {  
    super.onStart();  
    ViewTreeObserver viewTreeObserver=view.getViewTreeObserver();  
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {  
        @Override  
        public void onGlobalLayout() {  
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);  
            int width=view.getMeasuredWidth();  
            int height=view.getMeasuredHeight();  
        }  
    });  
} 


當然饥侵,在這里你可以通過 setMeasuredDimension() 方法對子 View 的具體寬高以及測量模式進行指定鸵赫。

五、View 的 layout 流程分析

1躏升、View 樹的 layout 擺放流程邏輯圖

2辩棒、View 的 layout 流程分析

layout 的作用是 ViewGroup 來確定子元素的位置,當 ViewGroup 的位置被確定后膨疏,在 layout 中會調(diào)用 onLayout 一睁,在 onLayout 中會遍歷所有的子元素并調(diào)用子元素的 layout 方法。
在代碼中設(shè)置 View 的成員變量 mLeft佃却,mTop者吁,mRight,mBottom 的值饲帅,這幾個值是在屏幕上構(gòu)成矩形區(qū)域的四個坐標點复凳,就是該 View 顯示的位置,不過這里的具體位置都是相對與父視圖的位置而言灶泵,而 onLayout 方法則會確定所有子元素位置育八,ViewGroup 在 onLayout 函數(shù)中通過調(diào)用其 children 的 layout 函數(shù)來設(shè)置子視圖相對與父視圖中的位置,具體位置由函數(shù) layout 的參數(shù)決定赦邻。下面我們先看 View 的 layout 方法 (只展示關(guān)鍵性代碼) 如下:

  
    public void layout(int l, int t, int r, int b) {
        ...
        
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        
        
        
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        
            onLayout(changed, l, t, r, b);
            ...
        }
        ...
    }


六髓棋、View 的 draw 流程分析

1、View 樹的 draw 繪制流程邏輯圖

2惶洲、View 的 draw 流程分析

在 View 的 draw() 方法的注釋中按声,說明了繪制流程中具體每一步的作用,源碼中對于 draw() 方法的注釋如下湃鹊,我們在這里重點分析注釋中除第 2儒喊、第 5 步外的其他步驟。

①View 中的 drawBackground() 繪制背景

核心源碼如下:

  private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        ...
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }


如果背景有偏移币呵,實質(zhì)上對畫布首先做偏移處理怀愧,然后在其上進行繪制侨颈。

②View 內(nèi)容的繪制

View 內(nèi)容的繪制源碼如下所示:

    protected void onDraw(Canvas canvas) {
    }


該方法是空實現(xiàn),就根據(jù)不同的內(nèi)容進行不同的設(shè)置芯义,自定義 View 中就需要重寫該方法加入我們自己的業(yè)務(wù)邏輯哈垢。

③子 View 的繪制

子 View 的繪制源碼如下所示:

    protected void dispatchDraw(Canvas canvas) {

    }


該方法同樣為空實現(xiàn),而對于 ViewGroup 而言對子 View 進行遍歷扛拨,并最終調(diào)用子 View 的 onDraw 方法進行繪制耘分。

④裝飾繪制

裝飾繪制的源碼如下所示 (只展示核心源碼):

    public void onDrawForeground(Canvas canvas) {
        
        onDrawScrollIndicators(canvas);
        onDrawScrollBars(canvas);
       ...
            foreground.draw(canvas);
    }


很明顯,在這里 onDrawForeground() 方法用于繪制例如 ScrollBar 等其他裝飾绑警,并將它們顯示在視圖的最上層求泰。

七、視圖重繪

1计盒、requestLayout 重新繪制視圖

子 View 調(diào)用 requestLayout 方法渴频,會標記當前 View 及父容器,同時逐層向上提交北启,直到 ViewRootImpl 處理該事件卜朗,ViewRootImpl 會調(diào)用三大流程,從 measure 開始咕村,對于每一個含有標記位的 view 及其子 View 都會進行測量场钉、布局、繪制懈涛。

2逛万、invalidate 在 UI 線程中重新繪制視圖

當子 View 調(diào)用了 invalidate 方法后,會為該 View 添加一個標記位肩钠,同時不斷向父容器請求刷新泣港,父容器通過計算得出自身需要重繪的區(qū)域,直到傳遞到 ViewRootImpl 中价匠,最終觸發(fā) performTraversals 方法,進行開始 View 樹重繪流程 (只繪制需要重繪的視圖)呛每。

3踩窖、postInvalidate 在非 UI 線程中重新繪制視圖

這個方法與 invalidate 方法的作用是一樣的,都是使 View 樹重繪晨横,但兩者的使用條件不同洋腮,postInvalidate 是在非 UI 線程中調(diào)用,invalidate 則是在 UI 線程中調(diào)用手形。

我要總結(jié)了

  • 總結(jié)一下
    一般來說啥供,如果 View 確定自身不再適合當前區(qū)域,比如說它的 LayoutParams 發(fā)生了改變库糠,需要父布局對其進行重新測量伙狐、擺放涮毫、繪制這三個流程,往往使用 requestLayout贷屎。而 invalidate 則是刷新當前 View罢防,使當前 View 進行重繪,不會進行測量唉侄、布局流程咒吐,因此如果 View 只需要重繪而不需要測量,布局的時候属划,使用 invalidate 方法往往比 requestLayout 方法更高效恬叹。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市同眯,隨后出現(xiàn)的幾起案子绽昼,更是在濱河造成了極大的恐慌,老刑警劉巖嗽测,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绪励,死亡現(xiàn)場離奇詭異,居然都是意外死亡唠粥,警方通過查閱死者的電腦和手機疏魏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晤愧,“玉大人大莫,你說我怎么就攤上這事」俜荩” “怎么了只厘?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舅巷。 經(jīng)常有香客問我羔味,道長,這世上最難降的妖魔是什么钠右? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任赋元,我火速辦了婚禮,結(jié)果婚禮上飒房,老公的妹妹穿的比我還像新娘搁凸。我一直安慰自己,他們只是感情好狠毯,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布护糖。 她就那樣靜靜地躺著,像睡著了一般嚼松。 火紅的嫁衣襯著肌膚如雪嫡良。 梳的紋絲不亂的頭發(fā)上锰扶,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音皆刺,去河邊找鬼少辣。 笑死,一個胖子當著我的面吹牛羡蛾,可吹牛的內(nèi)容都是我干的漓帅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼痴怨,長吁一口氣:“原來是場噩夢啊……” “哼忙干!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浪藻,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤捐迫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后爱葵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體施戴,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年萌丈,在試婚紗的時候發(fā)現(xiàn)自己被綠了赞哗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡辆雾,死狀恐怖肪笋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情度迂,我是刑警寧澤藤乙,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站惭墓,受9級特大地震影響坛梁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腊凶,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一罚勾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吭狡,春花似錦、人聲如沸丈莺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缔俄。三九已至弛秋,卻和暖如春器躏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蟹略。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工登失, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挖炬。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓揽浙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親意敛。 傳聞我的和親對象是個殘疾皇子馅巷,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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