surfaceview和view绘制的区别

一、SurfaceView和View的绘制流程

1.View的绘制流程

View的绘制流程可以分为三个阶段:measure(测量)、layout(布局)和draw(绘制)。这三个阶段是从ViewRootImpl的performTraversals()方法开始,自上而下地遍历整个View树,对每个View进行相应的操作。

measure阶段的目的是确定每个View的宽度和高度。

这个阶段会调用每个View的measure()方法,该方法会根据View的LayoutParams和父View的MeasureSpecs来计算出View的MeasureSpecs,然后传递给View的onMeasure()方法,该方法会根据View的MeasureSpecs和自身的内容来设置View的MeasuredWidth和MeasuredHeight。View的MeasureSpecs是一个32位的整数,其中高2位表示测量模式(MeasureSpec.EXACTLY、MeasureSpec.AT_MOST或MeasureSpec.UNSPECIFIED),低30位表示测量大小。View的MeasuredWidth和MeasuredHeight是一个16位的整数,表示View在测量阶段的宽度和高度。

layout阶段的目的是确定每个View的位置。

这个阶段会调用每个View的layout()方法,该方法会根据View的MeasuredWidth和MeasuredHeight以及父View的位置和边距来设置View的Left、Top、Right和Bottom。这四个属性表示View在父View坐标系中的位置和大小。View的layout()方法会调用View的onLayout()方法,该方法会根据View的子View的MeasuredWidth和MeasuredHeight以及View自身的布局规则来设置子View的位置。View的onLayout()方法是一个抽象方法,需要由具体的子类来实现,例如LinearLayout、RelativeLayout等。

draw阶段的目的是将每个View的内容绘制到屏幕上。

这个阶段会调用每个View的draw()方法,该方法会创建一个Canvas对象,该对象封装了一个Bitmap对象,该Bitmap对象表示View的绘制缓冲区。View的draw()方法会调用View的onDraw()方法,该方法会使用Canvas对象的绘图方法来绘制View的内容,例如drawText()、drawBitmap()等。View的draw()方法还会调用View的dispatchDraw()方法,该方法会遍历View的子View,并调用子View的draw()方法,从而实现View树的递归绘制。View的绘制缓冲区最终会被合成到屏幕上,这个过程由硬件加速或软件渲染来完成。

下面是View的绘制流程的源码分析,以ViewRootImpl的performTraversals()方法为入口:

void performTraversals() {
    // ..........
    boolean viewScrolled = false;
    // 第一次绘制或者窗口大小发生变化时,执行measure阶段
    if (mFirst || mReportNextDraw) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;
    }
    boolean windowSizeMayChange = false;
    // 获取窗口的宽度和高度
    int desiredWindowWidth = mWinFrame.width();
    int desiredWindowHeight = mWinFrame.height();
    // ..........
    // 如果需要执行measure阶段
    if (mLayoutRequested && !mStopped) {
        // ..........
        // 调用View的measure()方法,传入窗口的宽度和高度作为MeasureSpecs
        performMeasure(desiredWindowWidth, desiredWindowHeight);
        // 获取View的MeasuredWidth和MeasuredHeight
        int width = host.getMeasuredWidth();
        int height = host.getMeasuredHeight();
        // ..........
    }
    // ..........
    // 如果需要执行layout阶段
    if ((mLayoutRequested || windowSizeMayChange) && !mStopped) {
        // ..........
        // 调用View的layout()方法,传入View的Left、Top、Right和Bottom
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        // ..........
    }
    // ..........
    // 如果需要执行draw阶段
    if (!mStopped) {
        // ..........
        // 调用View的draw()方法,传入一个Canvas对象
        performDraw(canvas);
        // ..........
    }
    // ..........
}

View绘制流程示意图

2.SurfaceView的绘制流程

SurfaceView的绘制流程可以分为两个阶段:create(创建)和draw(绘制)。这两个阶段是通过SurfaceHolder的回调方法来触发的,create阶段的目的是创建一个Surface,draw阶段的目的是在Surface上绘制内容。

create阶段

调用SurfaceHolder.Callback的surfaceCreated()和surfaceChanged()方法,这两个方法会在Surface被创建或者改变时被调用。在这个阶段,可以获取SurfaceHolder对象,该对象封装了一个Surface对象,该Surface对象表示SurfaceView的绘制缓冲区。可以在这个阶段创建一个绘制线程,并将SurfaceHolder对象传递给该线程,以便在后台进行绘制操作。

draw阶段

调用SurfaceHolder.Callback的surfaceDestroyed()方法,该方法会在Surface被销毁时被调用。在这个阶段,可以停止绘制线程,并释放SurfaceHolder对象。绘制线程可以在运行时获取SurfaceHolder对象的锁,然后获取一个Canvas对象,该对象封装了Surface对象,然后使用Canvas对象的绘图方法来绘制内容,例如drawText()、drawBitmap()等。绘制完成后,需要释放Canvas
下面是SurfaceView的绘制流程的源码分析,以SurfaceView的init()方法为入口:

private void init() {
    // ..........
    // 创建一个SurfaceHolder对象,该对象封装了一个Surface对象
    mSurfaceHolder = new SurfaceHolder() {
        // ..........
        // 获取Surface对象的锁
        @Override
        public Canvas lockCanvas() {
            return internalLockCanvas(null, false);
        }
        // ..........
        // 释放Surface对象的锁,并将绘制内容合成到屏幕上
        @Override
        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            // ..........
        }
        // ..........
        // 添加SurfaceHolder.Callback对象,用于监听Surface的状态变化
        @Override
        public void addCallback(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);
                }
            }
        }
        // ..........
    };
    // ..........
}
SurfaceView绘制流程示意图

二、SurfaceView和View的绘制原理

1.View的绘制原理

View的绘制是基于Window的Surface来实现的,Window的Surface是一个原生的缓冲区,用来保存当前窗口的像素数据,它是由屏幕显示内容合成器(Screen Compositor)所管理的。屏幕显示内容合成器是一个系统级的服务,它负责将所有Window的Surface合成到屏幕上,形成最终的显示效果。屏幕显示内容合成器的具体实现可能有所不同,例如SurfaceFlinger、HWComposer等,但它们的基本原理都是类似的。


View使用的Canvas绘制合成思维导图

View的绘制过程中,会通过Window的lockCanvas方法,锁定Window的Surface中的Canvas对象,用来在Surface上绘制View的内容,绘制完成后,会通过unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。Surface会将缓冲区中的内容交给屏幕显示内容合成器,由它来将Surface的内容合成到屏幕上,完成绘制流程。

下面我们重点分析draw阶段的源码,draw阶段的源码如下:

    private void performDraw(Canvas canvas) {
        // .............
        // 如果没有传入Canvas对象,就从Window的Surface中
        // 获取一个Canvas对象,用于在Surface上绘制View的内容
        if (canvas == null) {
            // If the view hierarchy contains a SurfaceView that is not
            // updated (asynchronous mode), we will use the previous frame
            // to render the view hierarchy. This means we won't see the
            // updated state of the SurfaceView but there is no way around
            // this, unless we were to wait for the new frame to be ready.
            // But then, we would end up with a blank frame and drop below
            // 60 fps.
            if (mAttachInfo.mThreadedRenderer != null) {
                canvas = mAttachInfo.mThreadedRenderer.getCanvas();
            } else {
                canvas = mSurface.lockCanvas(mDirty);
            }
        }
        // .............
        // 调用View的draw()方法,传入Canvas对象,用于在Surface上绘制View的内容,
        // 该方法会先绘制View的背景,然后根据是否需要边缘渐变效果,
        // 调用onDraw方法或者drawWithFadingEdges方法,用于在Surface上绘制View的内容,
        // 然后调用dispatchDraw方法,用于在Surface上绘制View的子View的内容,
        // 然后调用onDrawForeground方法,用于在Surface上绘制View的前景,
        // 最后调用debugDrawFocus方法,用于在Surface上绘制View的焦点状态
        mAttachInfo.mView.draw(canvas);
        // .............
        // 如果从Window的Surface中获取了Canvas对象,
        // 就释放Surface中的Canvas对象,
        // 并将绘制结果提交到Surface的缓冲区中
        if (mAttachInfo.mThreadedRenderer == null) {
            mSurface.unlockCanvasAndPost(canvas);
        }
        // .............
    }

如果没有传入Canvas对象,就从Window的Surface中获取一个Canvas对象,用于在Surface上绘制View的内容,该过程是通过Surface的lockCanvas方法实现的,该方法会返回一个Canvas对象,用于在Surface上绘制内容。该方法的源码如下:

public Canvas lockCanvas(Rect inOutDirty) throws Surface.OutOfResourcesException, 
IllegalArgumentException {
    synchronized (mLock) {
        checkNotReleasedLocked();
        if (mLockedObject != 0) {
            throw new IllegalArgumentException("Surface was already locked");
        }
        mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
        return mCanvas;
    }
}

lockCanvas方法主要做了以下几件事:

  1. 同步锁定mLock对象,用于保证线程安全。

  2. 检查Surface是否已经被释放,如果是,抛出异常。

  3. 检查Surface是否已经被锁定,如果是,抛出异常。

  4. 调用nativeLockCanvas方法,传入mNativeObject、mCanvas和inOutDirty参数,该方法会返回一个mLockedObject对象,用于标识Surface的锁定状态。

  5. 返回mCanvas对象,用于在Surface上绘制内容。

调用View的draw()方法,传入Canvas对象,用于在Surface上绘制View的内容,draw()方法会先绘制View的背景,然后根据是否需要边缘渐变效果,调用onDraw方法或者drawWithFadingEdges方法,用于在Surface上绘制View的内容,然后调用dispatchDraw方法,用于在Surface上绘制View的子View的内容,然后调用onDrawForeground方法,用于在Surface上绘制View的前景,最后调用debugDrawFocus方法,用于在Surface上绘制View的焦点状态。该方法的源码如下:

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // common case onDraw(canvas); 
        } else {
            // …
        }

        dispatchDraw(canvas);

        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        onDrawForeground(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    }

draw()方法主要做了以下几件事:

  1. 清除View的脏标志,设置View的绘制标志,表示View已经被绘制过。
  2. 如果View不是完全不透明的,绘制View的背景,该方法会调用Drawable的draw方法,传入Canvas对象,用于在Surface上绘制Drawable的内容。
  3. 根据View的边缘渐变标志,判断是否需要绘制边缘渐变效果,如果不需要,直接调用onDraw方法,传入Canvas对象,用于在Surface上绘制View的内容,该方法是一个空方法,一般由子类重写,实现具体的绘制逻辑,例如TextView会绘制文本,ImageView会绘制图片等。如果需要,调用drawWithFadingEdges方法,传入Canvas对象,用于在Surface上绘制带有边缘渐变效果的View的内容,该方法会先保存Canvas的状态,然后根据边缘渐变的方向,裁剪Canvas的区域,然后调用onDraw方法,传入Canvas对象,用于在Surface上绘制View的内容,最后恢复Canvas的状态。
  4. 调用dispatchDraw方法,传入Canvas对象,用于在Surface上绘制View的子View的内容,该方法会遍历View的子View列表,根据它们的可见性、透明度、动画等属性,决定是否需要绘制,如果需要,就调用子View的draw方法,传入Canvas对象,用于在Surface上绘制子View的内容,该方法是一个递归的过程,直到所有的子View都被绘制完毕。
  5. 如果View有Overlay,且Overlay不为空,调用Overlay的getOverlayView方法,获取Overlay的View对象,然后调用Overlay的View对象的dispatchDraw方法,传入Canvas对象,用于在Surface上绘制Overlay的内容,该方法与View的dispatchDraw方法类似,也是一个递归的过程,直到所有的Overlay的子View都被绘制完毕。
  6. 调用onDrawForeground方法,传入Canvas对象,用于在Surface上绘制View的前景,该方法会绘制View的滚动条、前景Drawable等内容。
  7. 如果开启了调试模式,调用debugDrawFocus方法,传入Canvas对象,用于在Surface上绘制View的焦点状态,该方法会绘制View的边框、焦点框等内容。

如果从Window的Surface中获取了Canvas对象,就释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中,该过程是通过Surface的unlockCanvasAndPost方法实现的,该方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。unlockCanvasAndPost方法的源码如下:

public void unlockCanvasAndPost(Canvas canvas) {
    synchronized (mLock) {
        checkNotReleasedLocked();
        if (mLockedObject == 0) {
            throw new IllegalStateException("Surface was not locked");
        }
        nativeUnlockCanvasAndPost(mNativeObject, canvas);
        nativeRelease(mLockedObject);
        mLockedObject = 0;
    }
}

unlockCanvasAndPost方法主要做了以下几件事:

  1. 同步锁定mLock对象,用于保证线程安全。

  2. 检查Surface是否已经被释放,如果是,抛出异常。

  3. 检查Surface是否已经被锁定,如果否,抛出异常。

  4. 调用nativeUnlockCanvasAndPost方法,传入mNativeObject和canvas参数,该方法会将绘制结果提交到Surface的缓冲区中,并通知SurfaceFlinger进行合成。

  5. 调用nativeRelease方法,传入mLockedObject参数,该方法会释放Surface的锁定状态,并减少Surface的强引用计数。

Surface会将缓冲区中的内容交给屏幕显示内容合成器,由它来将Surface的内容合成到屏幕上,完成绘制流程。屏幕显示内容合成器是一个系统级的服务,它负责将所有Window的Surface和其他层合成到屏幕上,形成最终的显示效果。屏幕显示内容合成器的源码位于frameworks/native/services/surfaceflinger目录下,它是一个C++的程序,使用OpenGL ES来进行图形渲染。屏幕显示内容合成器的主要类和方法如下:

  • SurfaceFlinger类,它是屏幕显示内容合成器的核心类,它管理着所有的Surface和Layer,以及与客户端的通信和与硬件的交互。

  • SurfaceFlinger::onMessageReceived方法,它是SurfaceFlinger的消息处理方法,它会根据不同的消息类型,执行不同的操作,例如创建Surface、销毁Surface、更新Surface、合成Surface等。

  • SurfaceFlinger::doComposition方法,它是SurfaceFlinger的合成方法,它会遍历所有的Layer,根据它们的Z-order(层级顺序),将它们的内容绘制到一个FrameBuffer对象上,然后将FrameBuffer对象的内容显示到屏幕上。

View绘制原理示意图

2.SurfaceView的绘制原理

SurfaceView的绘制原理是基于SurfaceView自己的Surface来实现的,SurfaceView的Surface是一个独立于Window的Surface,用来展示Surface中的数据。

SurfaceView使用的Canvas绘制合成思维导图

以SurfaceView的init()方法为入口,init()方法是SurfaceView的初始化方法,它会创建一个SurfaceHolder对象,用于管理SurfaceView的Surface,以及一个SurfaceViewUpdateThread对象,用于在非UI线程中更新SurfaceView的内容。

init()方法的源码如下:

private void init() {
    // .............
    // 创建一个SurfaceHolder对象,用于管理SurfaceView的Surface
    mSurfaceHolder = new SurfaceHolder() {
        // .............
    };
    // 创建一个SurfaceViewUpdateThread对象,用于在非UI线程中更新SurfaceView的内容
    mSurfaceViewUpdateThread = new SurfaceViewUpdateThread();
    // .............
}

init()方法主要做了以下几件事:

  • 创建一个SurfaceHolder对象,用于管理SurfaceView的Surface,该对象提供了一些方法,用于获取、锁定、解锁和销毁Surface,以及设置Surface的格式、尺寸、回调等属性。
  • 创建一个SurfaceViewUpdateThread对象,用于在非UI线程中更新SurfaceView的内容,该对象是一个继承自Thread的子类,它重写了run()方法,用于在循环中不断地绘制SurfaceView的内容。

下面重点分析SurfaceView的绘制过程,通过SurfaceHolder的lockCanvas方法,锁定SurfaceView的Surface中的Canvas对象,用来在Surface上绘制内容,绘制完成后,会通过unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。SurfaceFlinger会将SurfaceView的Surface和其他层合成到屏幕上,完成绘制流程。

绘制过程的源码如下:

class SurfaceViewUpdateThread extends Thread {
    // .............
    @Override
    public void run() {
        // .............
        // 循环绘制SurfaceView的内容
        while (mRunning) {
            // .............
            // 通过SurfaceHolder的lockCanvas方法,锁定SurfaceView的Surface中的Canvas对象,用来在Surface上绘制内容
            Canvas canvas = mSurfaceHolder.lockCanvas();
            if (canvas != null) {
                // .............
                // 在Canvas上绘制SurfaceView的内容,例如,使用Paint对象绘制颜色、文本、图形等
                canvas.drawColor(Color.BLACK);
                canvas.drawText("Hello, SurfaceView!", 100, 100, mPaint);
                // .............
                // 通过SurfaceHolder的unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中
                mSurfaceHolder.unlockCanvasAndPost(canvas);
            }
            // .............
        }
    }
}


绘制过程主要做了以下两件事:

  • 循环绘制SurfaceView的内容,直到线程停止或者Surface被销毁。

  • 通过SurfaceHolder的lockCanvas方法,锁定SurfaceView的Surface中的Canvas对象,用来在Surface上绘制内容,lockCanvas方法会返回一个Canvas对象,用于在Surface上绘制内容。

lockCanvas方法的源码如下:

public Canvas lockCanvas() {
    return internalLockCanvas(null, false);
}

private final Canvas internalLockCanvas(Rect dirty, boolean hardware) {
    // .............
    // 调用Surface的lockCanvas方法,传入dirty参数,该方法会返回一个Canvas对象,用于在Surface上绘制内容
    Canvas c = mSurface.lockCanvas(dirty);
    // .............
    return c;
}


lockCanvas方法主要做了以下两件事:

  • 调用Surface的lockCanvas方法,传入dirty参数,该方法会返回一个Canvas对象,用于在Surface上绘制内容,该方法的源码与前面分析的Window的Surface的lockCanvas方法相同,不再赘述。

  • 在Canvas上绘制SurfaceView的内容,例如,使用Paint对象绘制颜色、文本、图形等,该过程与普通的View的绘制过程类似,不再赘述。

通过SurfaceHolder的unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中,unlockCanvasAndPost方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。

unlockCanvasAndPost方法的源码如下:


public void unlockCanvasAndPost(Canvas canvas) {
    // .............
    // 调用Surface的unlockCanvasAndPost方法,传入canvas参数,该方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中
    mSurface.unlockCanvasAndPost(canvas);
    // .............
}

unlockCanvasAndPost方法主要做了以下两件事:

  • 调用Surface的unlockCanvasAndPost方法,传入canvas参数,该方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中,该方法的源码与前面分析的Window的Surface的unlockCanvasAndPost方法相同,不再赘述。

  • SurfaceFlinger会将SurfaceView的Surface和其他层合成到屏幕上,完成绘制流程,该过程与前面分析的Window的Surface的合成过程类似,不再赘述。

SurfaceView绘制原理示意图

三、SurfaceView和View内的Canvas的区别

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

推荐阅读更多精彩内容