Android VirtualDisplay解析

一.概述

       我们知道,Android设备都有录屏功能,其实主要是通过VirtualDisplay来实现的,VirtualDisplay对应虚拟Display,主要用来进行屏幕录制等相关功能;
       在DMS的registerDefaultDisplayAdapters()内部,除了创建LocalDisplayAdapter,还创建了VirtualDisplayAdapter,可以参考文章Android Display管理服务DMS,接下来一起看一下VirtualDisplayAdapter类实现:

二.VirtualDisplayAdapter

public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
            Context context, Handler handler, Listener listener) {
    this(syncRoot, context, handler, listener,
                (String name, boolean secure) -> SurfaceControl.createDisplay(name, secure));
}

@VisibleForTesting
VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener,
            SurfaceControlDisplayFactory surfaceControlDisplayFactory) {
    super(syncRoot, context, handler, listener, TAG);
    mHandler = handler;
    mSurfaceControlDisplayFactory = surfaceControlDisplayFactory;
}

       在构造方法内部会通过SurfaceControl的createDisplay()获取的对象赋值给mSurfaceControlDisplayFactory变量,后续在创建VirtualDisplayDevice时需要;

三.VirtualDisplayDevice

       通过VirtualDisplayAdapter内部的createVirtualDisplayLocked()来创建VirtualDisplayDevice;

public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
            IMediaProjection projection, int ownerUid, String ownerPackageName, String name,
            int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) {
    boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
    IBinder appToken = callback.asBinder();
    IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);
    final String baseUniqueId =
                UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
    final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
    if (uniqueId == null) {
        uniqueId = baseUniqueId + uniqueIndex;
    } else {
        uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId;
    }
    VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
                ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags,
                new Callback(callback, mHandler), uniqueId, uniqueIndex);

    mVirtualDisplayDevices.put(appToken, device);
    try {
        if (projection != null) {
            projection.registerCallback(new MediaProjectionCallback(appToken));
        }
        appToken.linkToDeath(device, 0);
    }
    return device;
}

       1.通过mSurfaceControlDisplayFactory的createDisplay()来获取对应的displayToken,由于是虚拟设备,跟BUILT_IN设备获取方式是不同的;
       2.VirtualDisplay的mUniqueId格式为:virtual:com.hly.test,1000(ownerUid),-display(name),0;
       3.将创建的VirtualDisplayDevice加入mVirtualDisplayDevices进行管理;
       4.projection.registerCallback()执行录屏相关注册;

四.VirtualDisplay创建

       当要执行屏幕录制前,会先通过MediaProjection执行createVirtualDisplay()来创建VirtualDisplay;

1.createVirtualDisplay()

public VirtualDisplay createVirtualDisplay(@NonNull String name,int width, int height, int dpi, int flags, @Nullable Surface surface,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
     DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
     return dm.createVirtualDisplay(this, name, width, height, dpi, surface, flags, callback,handler, null /* uniqueId */);
}

       需要传入VirtualDisplay的name、width、height、Surface等,Surface用来获取或显示屏幕数据,最后会调用到DisplayMangerGlobal内部的createVirtualDisplay()方法:

2.createVirtualDisplay()

public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
            String name, int width, int height, int densityDpi, Surface surface, int flags,
            VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
    ..........................
    VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
    IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
    int displayId;
    try {
        displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken,
                    context.getPackageName(), name, width, height, densityDpi, surface, flags,
                    uniqueId);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
    Display display = getRealDisplay(displayId);
    ...............................
    return new VirtualDisplay(this, display, callbackWrapper, surface);
}

       接下来会调用执行到DMS的createVirtualDisplay()来先创建VirtualDisplayDevice和LogicalDisplay,根据LogicalDisplay的displayId创建Display,然后将Display作为参数创建VirtualDisplay返回;

3.总结

       用一张流程图总结一下创建流程:


image.png

五.镜像实现

       在录屏时,其实就是一个镜像,VirtualDisplayDevice是没有内容显示的,所以需要将其镜像到要录制屏对应的DisplayDevice就可以了,具体实现逻辑是在DMS的configureDisplayInTransactionLocked()方法,那该方法是什么时候会触发调用呢?
       根据前面的分析,在创建VirtualDisplay时,会执行到handleDisplayDeviceAddedLocked(),一起看一下该方法:

1.handleDisplayDeviceAddedLocked()

private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
    DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
    .....................
    mDisplayDevices.add(device);
    LogicalDisplay display = addLogicalDisplayLocked(device);
    Runnable work = updateDisplayStateLocked(device);
    if(work != null) {
        work.run();
    }
    scheduleTraversalLocked(false);
}

       在该方法内部会创建VirtualDisplayDevice对应的LogicalDisplay,继而分配displayId及layerStack,那创建完后是如何跟目标屏建立对应关系的呢?关键点就在scheduleTraversalLocked(false),一起看一下:

2.scheduleTraversalLocked()

private void scheduleTraversalLocked(boolean inTraversal) {
    if (!mPendingTraversal && mWindowManagerInternal != null) {
        mPendingTraversal = true;
        if (!inTraversal) {
            mHandler.sendEmptyMessage(MSG_REQUEST_TRAVERSAL);
        }
    }
}

case MSG_REQUEST_TRAVERSAL:
     mWindowManagerInternal.requestTraversalFromDisplayManager();
     break;

private final class LocalService extends WindowManagerInternal {
    @Override
     public void requestTraversalFromDisplayManager() {
        requestTraversal();
     }
}

void requestTraversal() {
    synchronized (mWindowMap) {
        mWindowPlacerLocked.requestTraversal();
    }
}

       在scheduleTraversalLocked()内部发送 MSG_REQUEST_TRAVERSAL消息,执行该消息的时候就是调用WMS的LocalService执行 requestTraversal()方法,具体执行过程简单罗列一下:

WindowSurfacePlacer -> requestTraversal()
 WindowSurfacePlacer -> performSurfacePlacement()
  WindowSurfacePlacer -> performSurfacePlacementLoop()
   RootWindowContainer -> performSurfacePlacement()
    void performSurfacePlacement(boolean recoveringMemory) {
       ...................
       mService.openSurfaceTransaction();
        try {
            applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);
        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
            mService.closeSurfaceTransaction();
        }
      ......
    }

       可以看到,在RootWindowContainer内部的performSurfacePlacement()会顺序执行以下方法:
       1.mService.openSurfaceTransaction(),通过SurfaceControl来通知native开始一个Transaction;
       2.applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh)来处理Transaction;
       3.mService.closeSurfaceTransaction(),通过SurfaceControl来通知native(SurfaceFlinger)关闭一个Transaction最终来执行合成显示等工作;

3.applySurfaceChangesTransaction()

private void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw,
            int defaultDh) {
    ......................................
    // Give the display manager a chance to adjust properties like display rotation if it needs
    // to.
    mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
}

       刷新后经过一番调用,最终回到DisplayManagerService的performTraversalInTransactionFromWindowManager()方法,该方法会调用到performTraversalInTransactionLocked()方法:

4.performTraversalInTransactionLocked()

private void performTraversalInTransactionLocked() {
    .....................
    // Configure each display device.
    final int count = mDisplayDevices.size();
    for (int i = 0; i < count; i++) {
        DisplayDevice device = mDisplayDevices.get(i);
        configureDisplayInTransactionLocked(device);
        device.performTraversalInTransactionLocked();
    }
    ......................
}

       可以看到,在该方法内会遍历所有的DisplayDevice来主要干了两件事:
       1.执行configureDisplayInTransactionLocked()进行配置DisplayDevice要显示的layerStack;
       2.执行performTraversalInTransactionLocked()来关联Surface,即数据显示源;
       先看configureDisplayInTransactionLocked():

5.configureDisplayInTransactionLocked()

private void configureDisplayInTransactionLocked(DisplayDevice device) {
    final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
    final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
    LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);

    if (!ownContent) {
        if (display != null && !display.hasContentLocked()) {
            // If the display does not have any content of its own, then
            // automatically mirror the default logical display contents.
            display = null;
        }
        if (display == null) {
            display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
        }
    }

    display.configureDisplayInTransactionLocked(device, info.state == Display.STATE_OFF);
    ....................
}

       当DisplayDevice对应VirtualDisplayDevice时,返回的ownContent为false,即没有自己内容显示的,进入判断,通过display.hasContentLocked()返回false,所以得到的display为null,接下来如果display为null,会从mLogicalDisplays中取出DEFAULT_DISPLAY赋值给display,那有什么用呢?
       关键逻辑在display.configureDisplayInTransactionLocked():

6.configureDisplayInTransactionLocked()

public void configureDisplayInTransactionLocked(DisplayDevice device, boolean isBlanked) {
    // Set the layer stack.
    device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack);
    .....................
    .....................
}

public final void setLayerStackInTransactionLocked(int layerStack) {
    if (mCurrentLayerStack != layerStack) {
        mCurrentLayerStack = layerStack;
        SurfaceControl.setDisplayLayerStack(mDisplayToken, layerStack);
    }
}

       在该方法内部执行device.setLayerStackInTransactionLocked(),即将VirtualDisplayDevice的layerStack设置为DEFAULT_DISPLAY对应的layerStack,继而调用SurfaceControl内部的setDisplayLayerStack()方法,mDisplayToken用于唯一标识SurfaceFliger创建的VirtualDisplay,跟随调用关系,再一起看一下:

public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
    nativeSetDisplayLayerStack(displayToken, layerStack);
}

static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz,jobject tokenObj, jint layerStack) {
    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
    SurfaceComposerClient::setDisplayLayerStack(token, layerStack);
}

void SurfaceComposerClient::setDisplayLayerStack(const sp<IBinder>& token,uint32_t layerStack) {
    Composer::getInstance().setDisplayLayerStack(token, layerStack);
}

void Composer::setDisplayLayerStack(const sp<IBinder>& token,uint32_t layerStack) {
    Mutex::Autolock _l(mLock);
    DisplayState& s(getDisplayStateLocked(token));
    s.layerStack = layerStack;
    s.what |= DisplayState::eLayerStackChanged;
}

       跟随调用关系,主要有以下几个要点:
       getDisplayStateLocked():如果不存在该token对应的 DisplayState就会为该token创建一个DisplayState,并保存在mDisplayStates中进行管理;
       接下来将layerStack赋值给DisplayyState的layerStack变量,此时layerStack配置完成;
       前面讲到,在创建VirtualDisplay时会传入Surface,那该Surface是如何与VirtualDisplay进行关联的呢?关键在performTraversalInTransactionLocked()方法:

7.performTraversalInTransactionLocked()

@Override
public void performTraversalInTransactionLocked() {
    if ((mPendingChanges & PENDING_RESIZE) != 0) {
         SurfaceControl.setDisplaySize(getDisplayTokenLocked(), mWidth, mHeight);
    }
    if ((mPendingChanges & PENDING_SURFACE_CHANGE) != 0) {
        setSurfaceInTransactionLocked(mSurface);
    }
    mPendingChanges = 0;
}

public final void setSurfaceInTransactionLocked(Surface surface) {
    if (mCurrentSurface != surface) {
        mCurrentSurface = surface;
        SurfaceControl.setDisplaySurface(mDisplayToken, surface);
    }
}

       mSurface是在创建VirtualDisplay时传入的,可以是通过SurfaceView的getSurface()获取的Surface,用来直接显示;也可以是通过MediaCodec获取的Surface,用来实现远端视频流传输;

public static void setDisplaySurface(IBinder displayToken, Surface surface) {
    ..........
    if (surface != null) {
        synchronized (surface.mLock) {
            nativeSetDisplaySurface(displayToken, surface.mNativeObject);
        }
    } else {
        nativeSetDisplaySurface(displayToken, 0);
    }
}

static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz,jobject tokenObj, jlong nativeSurfaceObject) {
    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
    sp<IGraphicBufferProducer> bufferProducer;
    sp<Surface> sur(reinterpret_cast<Surface *>(nativeSurfaceObject));
    if (sur != NULL) {
        bufferProducer = sur->getIGraphicBufferProducer();
    }
    status_t err = SurfaceComposerClient::setDisplaySurface(token,
        bufferProducer);
    }
}

       bufferProducer主要用于申请 Buffer,申请Buffer是写入图形数据;consumer主要用于消费Buffer,消费Buffer就是把应用写入数据准备好的Buffer取出来处理;

status_t SurfaceComposerClient::setDisplaySurface(const sp<IBinder>& token,sp<IGraphicBufferProducer> bufferProducer) {
    return Composer::getInstance().setDisplaySurface(token, bufferProducer);
}

status_t Composer::setDisplaySurface(const sp<IBinder>& token,sp<IGraphicBufferProducer> bufferProducer) {
    DisplayState& s(getDisplayStateLocked(token));
    s.surface = bufferProducer;
    s.what |= DisplayState::eSurfaceChanged;
    return NO_ERROR;
}

       把 bufferProducer 保存到 DisplayState 的 surface中;
       经过上面的处理后,为VirtualDisplay配置好了 layerStack (镜像屏)和bufferProducer,那么接下来是如何进行后续显示处理的呢?
       前面讲到,在RootWindowContainer内部的performSurfacePlacement()在执行applySurfaceChangesTransaction()完后会执行mService.closeSurfaceTransaction(),一起看一下该方法:

8.closeSurfaceTransaction()

void closeSurfaceTransaction() {
    try {
        synchronized (mWindowMap) {
            if (mRoot.mSurfaceTraceEnabled) {
                mRoot.mRemoteEventTrace.closeSurfaceTransaction();
            }
            SurfaceControl.closeTransaction();
        }
    } finally {
    }
}

public static void closeTransaction() {
    nativeCloseTransaction(false);
}

static void nativeCloseTransaction(JNIEnv* env, jclass clazz, jboolean sync) {
    SurfaceComposerClient::closeGlobalTransaction(sync);
}

void SurfaceComposerClient::closeGlobalTransaction(bool synchronous) {
    Composer::closeGlobalTransaction(synchronous);
}

void Composer::closeGlobalTransactionImpl(bool synchronous) {
    sp<ISurfaceComposer> sm(ComposerService::getComposerService());
    Vector<ComposerState> transaction;
    Vector<DisplayState> displayTransaction;
    ......
    {
        displayTransaction = mDisplayStates;
    }
     ......
   sm->setTransactionState(transaction, displayTransaction, flags); //sm  SurfaceFlinger
}

       sm:指向SurfaceFlinger 代理对象;mDisplayStates:里面保存了之前创建的VirtualDisplay对应的DisplayState,在DisplayState中保存了layerStack(镜像屏)和 bufferProducer(写入屏幕数据);

9.setTransactionState()

void SurfaceFlinger::setTransactionState(const Vector<ComposerState>& state,const Vector<DisplayState>& displays,uint32_t flags){
    .....
    size_t count = displays.size();
    for (size_t i=0 ; i<count ; i++) {
        const DisplayState& s(displays[i]);
        transactionFlags |= setDisplayStateLocked(s);
    }
    ......
    setTransactionFlags(transactionFlags);
    ......
}

       1.遍历mDisplayStates执行setDisplayStateLocked()更新 mCurrentState.displays 中的数据,通过token取出 VirtualDisplay然后更新surface和layerStack;

uint32_t SurfaceFlinger::setDisplayStateLocked(const DisplayState& s){
     ssize_t dpyIdx = mCurrentState.displays.indexOfKey(s.token);
     ......
     DisplayDeviceState& disp(mCurrentState.displays.editValueAt(dpyIdx));
     if (disp.isValid()) {
         const uint32_t what = s.what;
         if (what & DisplayState::eSurfaceChanged) {
            if (IInterface::asBinder(disp.surface) != IInterface::asBinder(s.surface)) {
                disp.surface = s.surface;
                flags |= eDisplayTransactionNeeded;
            }
         }
         if (what & DisplayState::eLayerStackChanged) {
             if (disp.layerStack != s.layerStack) {
                 disp.layerStack = s.layerStack;
                 flags |= eDisplayTransactionNeeded;
             }
         }
         ......
     }
     return flags;
}

       2.通过setTransactionFlags()发起一次刷新;

uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) {
    signalTransaction();
}

void SurfaceFlinger::signalTransaction() {
    mEventQueue.invalidate();
}

void MessageQueue::invalidate() {
    mEvents->requestNextVsync();
}

       SurfaceFlinger 请求下一个同步帧,执行一次刷新,最终调用如下: SurfaceFlinger::handleTransaction()-->SurfaceFlinger::handleTransactionLocked();

10.handleTransactionLocked()

void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags){
    .......................
    .......................
    const KeyedVector<  wp<IBinder>, DisplayDeviceState>& curr(mCurrentState.displays);
    const KeyedVector<  wp<IBinder>, DisplayDeviceState>& draw(mDrawingState.displays);
    const size_t cc = curr.size();
    size_t dc = draw.size();
    for (size_t i=0 ; i<cc ; i++) {
       if (draw.indexOfKey(curr.keyAt(i)) < 0) {
           ..................
           if (state.isVirtualDisplay()) {
               ......
               if (state.surface != NULL) {
                  if (mUseHwcVirtualDisplays || mHwc->isUsingVrComposer()) {
                      .....
                      displayUtils->initVDSInstance(*mHwc, hwcId, state.surface,dispSurface, producer, bqProducer, bqConsumer,state.displayName, state.isSecure);
                  }
               }
          }
          ......
          if (dispSurface != NULL && producer != NULL) {
              sp<DisplayDevice> hw =new DisplayDevice(this, state.type, hwcId, state.isSecure, display,
                                                  dispSurface, producer,mRenderEngine->getEGLConfig(),
                                                  hasWideColorDisplay);
              hw->setLayerStack(state.layerStack);
              hw->setProjection(state.orientation,state.viewport, state.frame);
              hw->setDisplayName(state.displayName);
              .......
              mDisplays.add(display, hw);
              if (!state.isVirtualDisplay()) {
                  mEventThread->onHotplugReceived(state.type, true);
              }
         }
     }
}

       先看一下两个变量含义:
       mCurrentState:SurfaceFlinger中最新的状态,包括所有的display和所有要绘制的Layer;
       mDrawingState:SurfaceFlinger中当前显示的状态,包括所有的display和所有要绘制的Layer;
       通过 mCurrentState 和 mDrawingState 比较是否有增加的display,如果有增加的dislay并且是 VirtualDisplay,首先会通过 initVDSInstance 函数创建一个 VirtualDisplaySurface封装了producer、consumer以及surface,然后创建一个 DisplayDevice并设置其 layerStack,最后保存到 mDisplays 中进行管理,此时虚拟屏幕的 DisplayDevice创建好了。
       在SurfaceFliner刷新所有的Layer到屏幕上显示之前,需要确定那些Layer应该显示到哪个屏幕上,以上逻辑是通过rebuildLayerStacks()来进行实现的:

11.rebuildLayerStacks()

void SurfaceFlinger::rebuildLayerStacks() {
     ......
     for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) {
          Region opaqueRegion;
          Region dirtyRegion;
          Vector<sp<Layer>> layersSortedByZ;
          Vector<sp<Layer>> layersNeedingFences;
          const sp<DisplayDevice>& displayDevice(mDisplays[dpy]);
          const Transform& tr(displayDevice->getTransform());
          const Rect bounds(displayDevice->getBounds());
          if (displayDevice->isDisplayOn()) {
              computeVisibleRegions(displayDevice, dirtyRegion, opaqueRegion);
              mDrawingState.traverseInZOrder([&](Layer* layer) {
                 if (layer->belongsToDisplay(displayDevice->getLayerStack(),displayDevice->isPrimary())) {
                      ......................
                      layersSortedByZ.add(layer);
                      ...........
                    });
                }
                displayDevice->setVisibleLayersSortedByZ(layersSortedByZ);
                ......
        }
    }
}

bool belongsToDisplay(uint32_t layerStack, bool isPrimaryDisplay) const {
     return getLayerStack() == layerStack && (!mPrimaryDisplayOnly || isPrimaryDisplay);
}

       在该方法内部会遍历mDisplays来将要显示的layer分配到对应的displayDevice上,主要有以下几个变量:
       Layer:对应WMS中的WindowState;
       layersSortedByZ:保存Layer的Vector;
       displayDevice->getLayerStack():创建VirtualDisplayDevice时为VirtualDisplay分配的LayerStack;
       belongsToDisplay():判断Layer的layerStack是否和 displayDevice的LayerStack一致,如果一致是就把该Layer加入到layersSortedByZ中保存;
       最后把所有要显示到该display上的Layer (layersSortedByZ) 保存到 displayDevice 中;
       总结一下:前面创建的VirtualDisplay的layerStack 对应就是主屏的layerStack,也就是说,此方法会把所有要显示到主屏的layer全部都保存到VirtualDisplay 中,这样VirtualDisplay就拿到了所有要显示到主屏的内容。
       关于录屏实现,可以参考文章Android 屏幕直播分享之MediaProjection和MediaCodec分析

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

推荐阅读更多精彩内容