探究 Android View 繪制流程辣卒,Activity 的 View 如何展示到屏幕

基于 Android API 26 Platform 源碼

寫作背景

在上一篇探究Android View 繪制流程掷贾,Xml 文件到 View 對(duì)象的轉(zhuǎn)換過程我們了解了setContentView(resId) 如何把 xml 文件轉(zhuǎn)換成 Java 中的 View 對(duì)象。本篇文章在此基礎(chǔ)上繼續(xù)探究荣茫,View 是如何展示到 Activity 上的想帅。

很多 Android 開發(fā)者都知道一個(gè)事情

當(dāng) Activity 執(zhí)行 onResume() 方法后,代表 Activity 顯示到前臺(tái)

這句話很短啡莉,但是背后隱藏了多少方法的調(diào)用呢港准?下面我們將一層一層的剝開源碼尋找真相。

onion.jpg

先從 setContentView(resId) 入手

先說(shuō)明一下咧欣,從 Android 的 Launcher 上點(diǎn)擊應(yīng)用的 Icon 的啟動(dòng)過程比較復(fù)雜浅缸,本人仍在學(xué)習(xí)。如果想了解如何啟動(dòng)一個(gè) Activity 的過程可以參考Android Launcher 啟動(dòng) Activity 的工作過程该押,這里我們只從關(guān)注 Activity 中的 View 顯示出來(lái)疗杉。所以直接從 Activity 的一些方法入手。

在 Activity 的 onCreate(savedInstanceState) 中調(diào)用 setContentView(resId),而setContentView(resId)則會(huì)調(diào)用 PhoneWindow.setContentView(layoutResID)

源碼并不是太長(zhǎng)

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

這里忽略轉(zhuǎn)場(chǎng)動(dòng)畫和一些回調(diào)相關(guān)的邏輯代碼后如下

 if (mContentParent == null) {
     installDecor();
 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
     mContentParent.removeAllViews();
 }
 mLayoutInflater.inflate(layoutResID, mContentParent);
 mContentParent.requestApplyInsets();

其中 mContentParent 是一個(gè) ViewGroup 引用

private ViewGroup mContentParent;

這樣開代碼比較簡(jiǎn)單明了

1. 判斷 mContentParent 是否為空烟具,如果為空?qǐng)?zhí)行 installDecor()
2. 如果 mContentParent 不為空梢什,清除 mContentParent 的所有子 View
3. 把傳入的布局文件轉(zhuǎn)換為 View 對(duì)象添加到 mContentParent

分析 installDecor()

然后我們?cè)倏聪?installDecor() ,因?yàn)樵创a比較長(zhǎng)朝聋,我們分成幾個(gè)部分解讀

第一部分
  if (mDecor == null) {
        mDecor = generateDecor();
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }

這幾行代碼最重要的是調(diào)用了方法 generateDecor() 其實(shí)就是創(chuàng)建一個(gè) DecorView嗡午。這里是不是能想到探究Android View 繪制流程,Canvas 的由來(lái)中最后的那張圖冀痕,我們做個(gè)類似的截圖截個(gè)圖

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new TextView(getApplicationContext()));
    }

    @Override
    protected void onResume() {
        super.onResume();
    }
}
activity_view_01.png

我們看到一個(gè) Activity 頁(yè)面最底層的 View 就是我們剛看到的 DecorView

第二部分
if (mContentParent == null) {
    mContentParent = generateLayout(mDecor);
    ……
}

這里看到了對(duì) mContentParent 的賦值操作荔睹,調(diào)用了 generateLayout(mDecor)

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

    TypedArray a = getWindowStyle();
    //設(shè)置 Windows Style ,title 言蛇、action_bar 僻他、設(shè)置鍵盤彈出方式之類的屬性
    //……
    //……
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        ……
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        ……
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        ……
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        ……
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        ……
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();

    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    //……
    //……
    mDecor.finishChanging();

    return contentParent;
}

這里把 generateLayout(mDecor) 做了很大的簡(jiǎn)化,大部分都是設(shè)置一些窗體屬性腊尚,軟鍵盤彈出方式之類的東西吨拗。我們關(guān)心的 View 相關(guān)的就以下幾行

    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    mDecor.finishChanging();

layoutResource 是什么呢?我們隨便選擇一個(gè) R.layout.screen_simple 在 AndroidSdk 中搜到這個(gè)文件婿斥,內(nèi)容如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

這個(gè)時(shí)候再回到我們剛的那個(gè)截圖劝篷,我們找到了第二層內(nèi)容 LinearLayout 的來(lái)源,這一層LinearLayout包含兩個(gè)部分

1. id 為 action_mode_bar_stub 的 ViewStub ,用來(lái)設(shè)置 actionBar 之類的
2. id 為 android.R.id.content 的 FrameLayout民宿。里面會(huì)存放我們?cè)?Activity.setContentView(resId) 傳入的文件布局

然后再看下最后 mDecor.finishChanging()

public void finishChanging() {
        mChanging = false;
        drawableChanged();
    }
    
private void drawableChanged() {
        if (mChanging) {
            return;
        }

        //……
        //……
        requestLayout();
        invalidate();

        //……
        //……
       
    }
nani.jpg

根據(jù)我們對(duì) View 的了解娇妓,requestLayout()invalidate() 會(huì)引發(fā) View 的重新布局和重新繪制,難道這個(gè)時(shí)候就繪制 View 了活鹰。 這不科學(xué)

而事實(shí)上哈恰,這個(gè)真的不科學(xué)。此時(shí)并不會(huì)執(zhí)行繪制和計(jì)算志群。 原因是此時(shí)的 View 還沒有和 ViewRootImpl 關(guān)聯(lián)上 蕊蝗。留個(gè)懸念,這個(gè)我們?cè)诤竺娴恼鹿?jié)會(huì)講解赖舟。

第三部分

第三部分就是第二部分省略的代碼,代碼特別長(zhǎng),這里也縮減一下夸楣。

 final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
         R.id.decor_content_parent);
 if (decorContentParent != null) {
     //……
 } else {
     mTitleView = (TextView)findViewById(R.id.title);
     //……
 }
 if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
     mDecor.setBackgroundFallback(mBackgroundFallbackResource);
 }
 // Only inflate or create a new TransitionManager if the caller hasn't
 // already set a custom one.
 if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
    //……
 }

這里簡(jiǎn)單的歸納一下代碼做的事情

1. 設(shè)置 title
2. 設(shè)置背景色
3. 處理 FEATURE_ACTIVITY_TRANSITIONS 屬性

requestLayout()invalidate() 源碼追蹤

requestLayout()invalidate() 的源碼都在 View 類里面

先看 requestLayout()

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

我們看到此時(shí)的 View 會(huì)調(diào)用 mParent.requestLayout() 宾抓。mParent 會(huì)是 ViewGroup 嗎?我們看下聲明變量的地方

protected ViewParent mParent;

然后再搜下mParent賦值的地方豫喧,發(fā)現(xiàn)只有一處

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}

那接下來(lái)就看 assignParent(parent) 被誰(shuí)調(diào)用了石洗,發(fā)現(xiàn) View 中只有聲明,沒有調(diào)用紧显。所以我們就去 ViewGroup 看看讲衫。發(fā)現(xiàn)也只有一處調(diào)用

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {

    ……
    // tell our children
    if (preventRequestLayout) {
        child.assignParent(this);
    } else {
        child.mParent = this;
    }

    ……
}

順著這個(gè)方法追溯一下,如下圖

activity_view_02.png

這時(shí)候我們又疑問了:

DecorView 的 mParent 是誰(shuí)呢?涉兽?招驴?

question.png

答案只有一個(gè),是 NULL

我們剛說(shuō)了 mDecor.finishChanging()不會(huì)執(zhí)行繪制和計(jì)算相枷畏。 原因是此時(shí)的 View 還沒有和 ViewRootImpl 關(guān)聯(lián)上 别厘。

先看 invalidate()

public void invalidate() {
    invalidate(true);
}

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 (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }

        ……
    }
}

我們又在跟蹤 invalidate() 方法時(shí)發(fā)現(xiàn)了 p.invalidateChild(this, damage) 這里似乎又是一層一層的向上迭代。為了確保拥诡,我們?nèi)タ聪?ViewGroup 的 invalidateChild()

public final void invalidateChild(View child, final Rect dirty) {
    ……

    ViewParent parent = this;
    if (attachInfo != null) {
        ……

        do {
            ……
            parent = parent.invalidateChildInParent(location, dirty);
            ……
            }
        } while (parent != null);
    }
}

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
         ……
         
        return mParent;
    }

    return null;
}

所以和 requestLayout() 一樣層層追溯触趴,又到了 DecorView 中。我們可以準(zhǔn)確的說(shuō) DecorViewmParent 其實(shí)是 ViewRootImpl渴肉。但是怎么證明呢冗懦??仇祭?

DecorViewViewRootImpl 的關(guān)系

本文開盤就已經(jīng)說(shuō)了 當(dāng) Activity 執(zhí)行 onResume() 方法后披蕉,代表 Activity 顯示到前臺(tái),這是為什么呢前塔?

我們都是 Activity 的由 ActivityManager 管理嚣艇,Activity 頁(yè)面的操作必須在主線程中,而主線程就是 ActivityThread 华弓。在 ActivityThread 的源碼中食零,找到了一個(gè) H 類,該類繼承 Handler 寂屏。在 HhandleMessage(Message msg) 發(fā)現(xiàn)以下代碼

   public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            ……
            case RESUME_ACTIVITY:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
                handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            ……
    }

然后看下 handleResumeActivity

 final void handleResumeActivity(IBinder token,
           ……
           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;
               l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
               l.softInputMode |= forwardBit;
               if (a.mVisibleFromClient) {
                   a.mWindowAdded = true;
                   wm.addView(decor, l);
               }
           ……
   }

這里我們看到了 DecorView 被添加到了 ViewManager 之中贰谣。

ViewManager 只是一個(gè)接口,它的實(shí)現(xiàn)類為 WindowManagerImpl迁霎。在 WindowManagerImpl 我查找 addView() 方法

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

這里的 mGlobal 又是 WindowManagerGlobal 的實(shí)例吱抚。所有我們又要跳轉(zhuǎn)到 WindowManagerGlobal.addView()

keep.jpg

O__O "… 這時(shí)千萬(wàn)別放棄考廉,勝利就在眼前秘豹,同志們要堅(jiān)持往下看啊。

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ……

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

然后再看下 ViewRootImpl.setView()

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     synchronized (this) {
         if (mView == null) {
             mView = view;
             ……
             view.assignParent(this);
         }
     }
 }

親人安痢既绕!終于看到 root.setView(view, wparams, panelParentView),我們上面一直說(shuō)的 View 和 ViewRootImpl 的關(guān)系終于在這關(guān)聯(lián)上了涮坐。為了更清晰一點(diǎn)我們畫一個(gè)時(shí)序圖

activity_view_03.png

ViewRootImpl 繪制 View

現(xiàn)在進(jìn)入了本文的壓軸部分凄贩,View 繪制的核心源碼。

通過以上的講解袱讹,我們也知道要去找 ViewRootImplrequestLayout()invalidateChildInParent() 方法

ViewRootImpl.requestLayout()
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

scheduleTraversals() 又是什么鬼

 void scheduleTraversals() {
     if (!mTraversalScheduled) {
         mTraversalScheduled = true;
         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
         mChoreographer.postCallback(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         if (!mUnbufferedInputDispatch) {
             scheduleConsumeBatchedInput();
         }
         notifyRendererOfFramePending();
         pokeDrawLockIfNeeded();
     }
 }

這里我們看到了一個(gè)任務(wù) mTraversalRunnable

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

mTraversalRunnable 是一個(gè) Runnable 的子類

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

這個(gè)時(shí)候我們又要去看下 doTraversal() 的源碼疲扎。

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

最后我們找到了 performTraversals() 方法, 注意 performTraversals() 里面有重大內(nèi)容該方法很長(zhǎng)(真的是特別長(zhǎng)),我們這里看一下簡(jiǎn)化后的

 private void performTraversals() {
            ……
            if (!mStopped || mReportNextDraw) {
               ……
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
               ……
            }
        ……
        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ……
        }
        ……

                performDraw();
        ……
 }
success.jpg

看到了 performMeasure 椒丧、 performLayout壹甥、 performDraw 這里就不用多說(shuō)了吧。也就解釋了為啥 View 的繪制順序是 measure -> layout -> draw 了吧

ViewRootImpl.invalidateChildInParent()()

這里我們不啰嗦太多瓜挽,直接上源碼

 public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
     checkThread();
     ……

     invalidateRectOnScreen(dirty);

     return null;
 }
 
private void invalidateRectOnScreen(Rect dirty) {
    ……
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

看到這里就不用多說(shuō)了盹廷,下面的執(zhí)行順序 ViewRootImpl.requestLayout() 已經(jīng)分析過了。

這個(gè)時(shí)候大家再看下網(wǎng)上很多分析 requestLayout() 和 invalidate() 方法區(qū)別的久橙,大家可以去先去查一下俄占,等后面有時(shí)間我也會(huì)寫一篇分析這兩個(gè)方法區(qū)別的文章。

View 到底什么時(shí)候繪制到屏幕上淆衷?

通過以上分析我們知道

  1. setContentView() 只是把 View 添加到 DecorView 上
  2. onResume() 中 ViewRootImpl 和 DecorView 做了關(guān)聯(lián)
  3. requestLayout() 和 invalidate() 會(huì)觸發(fā) ViewRootImpl 繪制 View

但是缸榄!setContentView() 中調(diào)用了 requestLayout() 和 invalidate() 不會(huì)觸發(fā)繪制,我們上面只講了 onResume() 中 ViewRootImpl 和 DecorView 做了關(guān)聯(lián) 祝拯。到底什么時(shí)候又調(diào)用了 requestLayout() 或者 invalidate() 甚带??佳头?

往上翻我們發(fā)現(xiàn)在 ViewRootImpl.setView() 中有一個(gè) requestLayout

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
                ……
                requestLayout();
                 ……
                view.assignParent(this);
                ……
            }
        }
    }

但是鹰贵!居然在 view.assignParent(this) 這尼瑪逗我吧!

nani.jpg

我們?cè)诨仡^看下 requestLayout()

 void scheduleTraversals() {
     if (!mTraversalScheduled) {
         mTraversalScheduled = true;
         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
         mChoreographer.postCallback(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         if (!mUnbufferedInputDispatch) {
             scheduleConsumeBatchedInput();
         }
         notifyRendererOfFramePending();
         pokeDrawLockIfNeeded();
     }
 }

這里重點(diǎn)看一下這句

mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

了解 Android Handler Looper 都知道 postSyncBarrier 是創(chuàng)建一個(gè)障礙康嘉,阻止后面的 Message 對(duì)象被執(zhí)行碉输。那這里也就解決了我剛剛的疑問, 雖然request()在 view.assignParent(this) 之前被調(diào)用亭珍,但是會(huì)被阻塞敷钾。 doTraversal() 執(zhí)行的時(shí)候 DecorView 和 ViewRootImpl 已經(jīng)關(guān)聯(lián)了

這里留個(gè)坑

我沒有找到 ViewRootImpl 怎么執(zhí)行到 removeSyncBarrier(mTraversalBarrier) 的代碼组底。

總結(jié)

對(duì)以上內(nèi)容做個(gè)總結(jié)

1. View 在 Activity 的 onCreate() 方法中通過 setContentView() 方法添加到 Activity 的 DecorView 上
2. 此時(shí) ViewRootImpl 和 DecorView 沒有關(guān)聯(lián)上轴咱,不會(huì)繪制 View
3. 在 Activity 的 onResume() 方法執(zhí)行后尔艇,DecorView 會(huì)被添加帶 ViewRootImpl 中惧眠。然后執(zhí)行 requestlayout()

參考資料

Android Launcher 啟動(dòng) Activity 的工作過程

Activity到底是什么時(shí)候顯示到屏幕上的呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末始衅,一起剝皮案震驚了整個(gè)濱河市摇庙,隨后出現(xiàn)的幾起案子勋锤,更是在濱河造成了極大的恐慌粱侣,老刑警劉巖辆毡,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異甜害,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)球昨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門尔店,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事嚣州■晔郏” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵该肴,是天一觀的道長(zhǎng)情竹。 經(jīng)常有香客問我,道長(zhǎng)匀哄,這世上最難降的妖魔是什么秦效? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮涎嚼,結(jié)果婚禮上阱州,老公的妹妹穿的比我還像新娘。我一直安慰自己法梯,他們只是感情好苔货,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著立哑,像睡著了一般夜惭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铛绰,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天诈茧,我揣著相機(jī)與錄音,去河邊找鬼至耻。 笑死若皱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尘颓。 我是一名探鬼主播走触,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼疤苹!你這毒婦竟也來(lái)了互广?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卧土,失蹤者是張志新(化名)和其女友劉穎惫皱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尤莺,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旅敷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颤霎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媳谁。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涂滴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晴音,到底是詐尸還是另有隱情柔纵,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布锤躁,位于F島的核電站搁料,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏系羞。R本人自食惡果不足惜郭计,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望觉啊。 院中可真熱鬧拣宏,春花似錦、人聲如沸杠人。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嗡善。三九已至辑莫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罩引,已是汗流浹背各吨。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袁铐,地道東北人揭蜒。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剔桨,于是被迫代替她去往敵國(guó)和親屉更。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344