SurfaceView原理简述

以前有个疑问,SurfaceView为什么可以开一个新的线程进行绘制,而其他的不行。我们知道View的布局绘制是在主线程执行的,通过ViewRootImpl的performTraversals方法开始驱动顶层DecorView和它的子View执行measure, layout, draw。同样是View类,那为什么SurfaceView的绘制操作却可以单独开新的线程去执行呢?

在这里先提前总结一下原理。View的绘制是由ViewRootImpl的Surface对象将绘制后的ui数据交给WindowManagerService,WindowManagerService会将多个Surface数据合并成一整屏的ui数据,交给SurfaceFlinger渲染对应的Layer。而SurfaceView内部维护着一块Surface用于ui数据的的绘制,同时在WindowManagerService端会创建一个新的绘图对象,对应着SurfaceFlinger的一个新的Layer,因此SurfaceView中绘制的数据就由新的Layer,而不是宿主DecorView的Layer;意思就是SurfaceView有和宿主DecorView对应的ViewRootImpl一样的一套绘制渲染模型,两者分别独立渲染。

现在简单讲讲SurfaceView的工作流程。

  • 1.在onAttachedToWindow中进行对SurfaceView的初始化和准备工作。完成透明区域的请求,获取WindowManagerService的本地代理对象mSession,和对绘制的监听addOnPreDrawListener
  • 2.在onWindowVisibilityChanged中将被显示时,调用updateWindow
  • 3.updateWindow,初始化MyWindow对象,用于WindowManagerService通知SurfaceView的状态变化,mSession.relayout以请求WindowManagerService对Surface的UI进行布局,这样SurfaceFlinger就会为其创建一个独立的Layer绘图对象。这里同时会回调SurfaceHolder.Callback的surfaceCreated和surfaceChanged等方法,通知当前的SurfaceView是否准备好了。
  • 4.然后我们就可以通过getHolder获取SurfaceHolder的对象,在新线程中,通过lockCanvas锁定画布,然后用Cavas对象进行绘制,内部是通过SurfaceView的Surface对象来取得Canvas对象,该Canvas通过JNI调用到底层的Surface的Canvas进行操作。
  • 5.在onDetachedFromWindow中进行对SurfaceView的清理工作。移除绘制监听,调用updateWindow通知回调SurfaceHolder.Callback的surfaceDestroyed,mSession移除mWindow以使WindowManagerService解除对SurfaceView的操作。

再次总结,因为普通View走的是ViewRootImpl的绘制流程,在里面有对线程进行检查,非主线程的话会抛异常,目的就是实现单线程绘制模型,同时又要接受输入事情等。而SurfaceView有独立的绘制机制,比如独立的客户端Surface,WindowManagerService中独立的绘图对象,SurfaceFlinger中独立的Layer渲染。因为它里面可以只负责绘制,所以效率要更高。当然,SurfaceView的其他处理比如输入事件还是继承使用了View的那一套。

好了,简述完了之后,觉得还是有必要贴出些源码,让描述显得更加有依据一些。

public SurfaceView(Context context) {
    super(context);
    init();
}

public SurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init();
}

private void init() {
    setWillNotDraw(true);
}

此处初始化,目的是不进行View的那一套绘制,专心做新线程的绘制。

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    mParent.requestTransparentRegion(this);
    mSession = getWindowSession();
    mLayout.token = getWindowToken();
    mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
    mViewVisibility = getVisibility() == VISIBLE;

    if (!mGlobalListenersAdded) {
        ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnScrollChangedListener(mScrollChangedListener);
        observer.addOnPreDrawListener(mDrawListener);
        mGlobalListenersAdded = true;
    }
}

当SurfaceView被添加到Window上即将显示时,调用mParent.requestTransparentRegion(this);请求顶层View给自己留出空白区域,这样我们的SurfaceView才能不会被其他View遮挡住。然后这里获取了WindowManager的代理对象,以后后面对WindowManager的请求操作。然后添加滚动监听和绘制监听

private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
        = new ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {
                    updateWindow(false, false);
                }
        };

private final ViewTreeObserver.OnPreDrawListener mDrawListener =
        new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                // reposition ourselves where the surface is
                mHaveFrame = getWidth() > 0 && getHeight() > 0;
                updateWindow(false, false);
                return true;
            }
        };

这样,即将要绘制的时候,就会执行updateWindow方法了。

@Override
protected void onWindowVisibilityChanged(int visibility) {
    super.onWindowVisibilityChanged(visibility);
    mWindowVisibility = visibility == VISIBLE;
    mRequestedVisible = mWindowVisibility && mViewVisibility;
    updateWindow(false, false);
}

SurfaceView依附的Window要显示或隐藏时,也同样调用updateWindow,那updateWindow是做什么操作的呢?

protected void updateWindow(boolean force, boolean redrawNeeded) {
    .....
    final boolean creating = mWindow == null;
    final boolean formatChanged = mFormat != mRequestedFormat;
    final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
    final boolean visibleChanged = mVisible != mRequestedVisible;
    final boolean layoutSizeChanged = getWidth() != mLayout.width
            || getHeight() != mLayout.height;

    //此处判断SurfaceView是否有发生变化,需要更新
    if (force || creating || formatChanged || sizeChanged || visibleChanged
        || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
            //需要刷新SurfaceView,更新mLayout这个SurfaceView的LayoutParams的信息
       ......

            if (mWindow == null) {
                //MyWindow是一个Binder类,用于WindowMangerService通知回调SurfaceView,这里创建MyWindow对象mWIndow,并通过mSession.addToDisplayWithoutInputChannel将这些参数传给WindowMangerService,通知它为SurfaceView创建一块不接收输入事件的Surface,以便后面绘图所用。
                Display display = getDisplay();
                mWindow = new MyWindow(this);
                mLayout.type = mWindowType;
                mLayout.gravity = Gravity.START|Gravity.TOP;
                mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
                        mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets,
                        mStableInsets);
            }

            boolean realSizeChanged;
            boolean reportDrawNeeded;

            int relayoutResult;
            //此处需要用锁锁住,防止其他线程同时修改
            mSurfaceLock.lock();
            try {
                mUpdateWindowNeeded = false;
                reportDrawNeeded = mReportDrawNeeded;
                mReportDrawNeeded = false;
                mDrawingStopped = !visible;

                //通过mSession这个binder对象请求WindowMangerService为SurfaceView的UI进行布局,然后WindowMangerService就会填充mNewSurface这个Surface对象,以便后面可以通过它获取画布Canvas,进行绘图操作。
                relayoutResult = mSession.relayout(
                    mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight,
                        visible ? VISIBLE : GONE,
                        WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
                        mWinFrame, mOverscanInsets, mContentInsets,
                        mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
                        mConfiguration, mNewSurface);
                if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                    reportDrawNeeded = true;
                }

                if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                        + "New surface: " + mNewSurface
                        + ", vis=" + visible + ", frame=" + mWinFrame);

                mSurfaceFrame.left = 0;
                mSurfaceFrame.top = 0;
                if (mTranslator == null) {
                    mSurfaceFrame.right = mWinFrame.width();
                    mSurfaceFrame.bottom = mWinFrame.height();
                } else {
                    float appInvertedScale = mTranslator.applicationInvertedScale;
                    mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
                    mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
                }

                final int surfaceWidth = mSurfaceFrame.right;
                final int surfaceHeight = mSurfaceFrame.bottom;
                realSizeChanged = mLastSurfaceWidth != surfaceWidth
                        || mLastSurfaceHeight != surfaceHeight;
                mLastSurfaceWidth = surfaceWidth;
                mLastSurfaceHeight = surfaceHeight;
            } finally {
        //此处释放锁
                mSurfaceLock.unlock();
            }

            try {
                redrawNeeded |= creating | reportDrawNeeded;

                SurfaceHolder.Callback callbacks[] = null;

                final boolean surfaceChanged = (relayoutResult
                        & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0;
                if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                    mSurfaceCreated = false;
                    if (mSurface.isValid()) {
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "visibleChanged -- surfaceDestroyed");
                        callbacks = getSurfaceCallbacks();
                         //这里判断了Surface被销毁了,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceDestroyed(mSurfaceHolder);
                        }
                    }
                }

                //这里将最新状态的mNewSuface对象的数据更新到当前的mSurface中
                mSurface.transferFrom(mNewSurface);
                if (visible && mSurface.isValid()) {
                    if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                        mSurfaceCreated = true;
                        mIsCreating = true;
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "visibleChanged -- surfaceCreated");
                        if (callbacks == null) {
                            callbacks = getSurfaceCallbacks();
                        }
                        //这里判断了Surface被创建了,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceCreated(mSurfaceHolder);
                        }
                    }
                    if (creating || formatChanged || sizeChanged
                            || visibleChanged || realSizeChanged) {
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "surfaceChanged -- format=" + mFormat
                                + " w=" + myWidth + " h=" + myHeight);
                        if (callbacks == null) {
                            callbacks = getSurfaceCallbacks();
                        }
                         //这里判断了Surface被更新了,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
                        }
                    }
                    if (redrawNeeded) {
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "surfaceRedrawNeeded");
                        if (callbacks == null) {
                            callbacks = getSurfaceCallbacks();
                        }
                        //这里判断了Surface需要被重绘,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了
                        for (SurfaceHolder.Callback c : callbacks) {
                            if (c instanceof SurfaceHolder.Callback2) {
                                ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                        mSurfaceHolder);
                            }
                        }
                    }
                }
            } finally {
                mIsCreating = false;
                if (redrawNeeded) {
                    if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                            + "finishedDrawing");
                    mSession.finishDrawing(mWindow);
                }
                mSession.performDeferredDestroy(mWindow);
            }
        } catch (RemoteException ex) {
            Log.e(TAG, "Exception from relayout", ex);
        }
       
    } else {
        //除了以上情况,如果是SufaceView的位置或大小发生改变,就进行UI线程的布局更新
        // Calculate the window position in case RT loses the window
        // and we need to fallback to a UI-thread driven position update
        getLocationInWindow(mLocation);
        final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
                || mWindowSpaceTop != mLocation[1];
        if (positionChanged || layoutSizeChanged) { // Only the position has changed
            mWindowSpaceLeft = mLocation[0];
            mWindowSpaceTop = mLocation[1];
            // For our size changed check, we keep mLayout.width and mLayout.height
            // in view local space.
            mLocation[0] = mLayout.width = getWidth();
            mLocation[1] = mLayout.height = getHeight();

            transformFromViewToWindowSpace(mLocation);

            mWinFrame.set(mWindowSpaceLeft, mWindowSpaceTop,
                    mLocation[0], mLocation[1]);

            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(mWinFrame);
            }

            if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
                try {
                    if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition UI, " +
                            "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
                            mWinFrame.left, mWinFrame.top,
                            mWinFrame.right, mWinFrame.bottom));
                    mSession.repositionChild(mWindow, mWinFrame.left, mWinFrame.top,
                            mWinFrame.right, mWinFrame.bottom, -1, mWinFrame);
                } catch (RemoteException ex) {
                    Log.e(TAG, "Exception from relayout", ex);
                }
            }
        }
    }
}

可见,updateWindow的主要作用是SurfaceView更新的处理,包括mWindow,通知WindowMangerService中Surface的创建,还有Surface的创建,更新,销毁的通知回调等。
我们在看看MyWindow这个Binder类

private static class MyWindow extends BaseIWindow {
    private final WeakReference<SurfaceView> mSurfaceView;

    public MyWindow(SurfaceView surfaceView) {
        //弱引用持有当前的SurfaceView,防止内存泄露
        mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
    }

    @Override
    public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
            Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
            Configuration newConfig, Rect backDropRect, boolean forceLayout,
            boolean alwaysConsumeNavBar) {
        //WindowMangerService中SurfaceView对应的Surface大小发生变化,发消息通知窗口布局发生了变化
        SurfaceView surfaceView = mSurfaceView.get();
        if (surfaceView != null) {
            if (DEBUG) Log.v(TAG, surfaceView + " got resized: w=" + frame.width()
                    + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight);
            surfaceView.mSurfaceLock.lock();
            try {
                if (reportDraw) {
                    surfaceView.mUpdateWindowNeeded = true;
                    surfaceView.mReportDrawNeeded = true;
                    surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
                } else if (surfaceView.mWinFrame.width() != frame.width()
                        || surfaceView.mWinFrame.height() != frame.height()
                        || forceLayout) {
                    surfaceView.mUpdateWindowNeeded = true;
                    surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
                }
            } finally {
                surfaceView.mSurfaceLock.unlock();
            }
        }
    }

    @Override
    public void dispatchAppVisibility(boolean visible) {
        // The point of SurfaceView is to let the app control the surface.
    }

    @Override
    public void dispatchGetNewSurface() {
        //通知对应的新的Surface创建了,这里消息处理会调用updateWindow,在其中会获得最新的Surface填充到mNewSurface中
        SurfaceView surfaceView = mSurfaceView.get();
        if (surfaceView != null) {
            Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
            surfaceView.mHandler.sendMessage(msg);
        }
    }

    @Override
    public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
        Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
    }

    @Override
    public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
    }

    int mCurWidth = -1;
    int mCurHeight = -1;
}

可见,MyWindow就是WindowMangerService用来通知SurfaceView的沟通工具,通知SurfaceView对应的Surface状态的变化。
最后就是mSurfaceHolder了,顾名思义就是Surface的持有者,通过它来操作Surface对象。

private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {

    private static final String LOG_TAG = "SurfaceHolder";

    @Override
    public boolean isCreating() {
        return mIsCreating;
    }

    @Override
    public void addCallback(Callback callback) {
        //添加callback回调
        synchronized (mCallbacks) {
            // This is a linear search, but in practice we'll
            // have only a couple callbacks, so it doesn't matter.
            if (mCallbacks.contains(callback) == false) {
                mCallbacks.add(callback);
            }
        }
    }

    @Override
    public void removeCallback(Callback callback) {
        //移除callback回调
        synchronized (mCallbacks) {
            mCallbacks.remove(callback);
        }
    }

    @Override
    public void setFixedSize(int width, int height) {
        if (mRequestedWidth != width || mRequestedHeight != height) {
            mRequestedWidth = width;
            mRequestedHeight = height;
            requestLayout();
        }
    }

    @Override
    public void setSizeFromLayout() {
        if (mRequestedWidth != -1 || mRequestedHeight != -1) {
            mRequestedWidth = mRequestedHeight = -1;
            requestLayout();
        }
    }

    @Override
    public void setFormat(int format) {

        // for backward compatibility reason, OPAQUE always
        // means 565 for SurfaceView
        if (format == PixelFormat.OPAQUE)
            format = PixelFormat.RGB_565;

        mRequestedFormat = format;
        if (mWindow != null) {
            updateWindow(false, false);
        }
    }

    /**
     * @deprecated setType is now ignored.
     */
    @Override
    @Deprecated
    public void setType(int type) { }

    @Override
    public void setKeepScreenOn(boolean screenOn) {
        Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
        msg.arg1 = screenOn ? 1 : 0;
        mHandler.sendMessage(msg);
    }

    /**
     * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
     *
     * After drawing into the provided {@link Canvas}, the caller must
     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
     *
     * The caller must redraw the entire surface.
     * @return A canvas for drawing into the surface.
     */
    @Override
    public Canvas lockCanvas() {
        return internalLockCanvas(null);
    }

    /**
     * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
     *
     * After drawing into the provided {@link Canvas}, the caller must
     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
     *
     * @param inOutDirty A rectangle that represents the dirty region that the caller wants
     * to redraw.  This function may choose to expand the dirty rectangle if for example
     * the surface has been resized or if the previous contents of the surface were
     * not available.  The caller must redraw the entire dirty region as represented
     * by the contents of the inOutDirty rectangle upon return from this function.
     * The caller may also pass <code>null</code> instead, in the case where the
     * entire surface should be redrawn.
     * @return A canvas for drawing into the surface.
     */
    @Override
    public Canvas lockCanvas(Rect inOutDirty) {
        return internalLockCanvas(inOutDirty);
    }

    private final Canvas internalLockCanvas(Rect dirty) {
        //锁定并获取Canvas画布对象,以进行后面的绘画
        mSurfaceLock.lock();

        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
                + mDrawingStopped + ", win=" + mWindow);

        Canvas c = null;
        if (!mDrawingStopped && mWindow != null) {
            try {
                c = mSurface.lockCanvas(dirty);
            } catch (Exception e) {
                Log.e(LOG_TAG, "Exception locking surface", e);
            }
        }

        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
        if (c != null) {
            mLastLockTime = SystemClock.uptimeMillis();
            return c;
        }

        // If the Surface is not ready to be drawn, then return null,
        // but throttle calls to this function so it isn't called more
        // than every 100ms.
        long now = SystemClock.uptimeMillis();
        long nextTime = mLastLockTime + 100;
        if (nextTime > now) {
            try {
                Thread.sleep(nextTime-now);
            } catch (InterruptedException e) {
            }
            now = SystemClock.uptimeMillis();
        }
        mLastLockTime = now;
        mSurfaceLock.unlock();

        return null;
    }

    /**
     * Posts the new contents of the {@link Canvas} to the surface and
     * releases the {@link Canvas}.
     *
     * @param canvas The canvas previously obtained from {@link #lockCanvas}.
     */
    @Override
    public void unlockCanvasAndPost(Canvas canvas) {
        mSurface.unlockCanvasAndPost(canvas);
        mSurfaceLock.unlock();
    }

    @Override
    public Surface getSurface() {
        return mSurface;
    }

    @Override
    public Rect getSurfaceFrame() {
        return mSurfaceFrame;
    }
};

可见,SurfaceHolder就是SurfaceView对外提供访问Surface的接口,这里访问Surface有做同步,为空时重复请求的处理。

到这里的话,SurfaceView的大部分逻辑都在这了,就先到这里了。

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

推荐阅读更多精彩内容