Flutter运行过程(一):一文搞懂Widget更新机制

作者:古嘉明同学
链接:https://juejin.cn/post/7161681082851524622

本系列将从Flutter框架runApp()运行开始,结合框架源码,分析flutter UI渲染、更新机制,布局、绘制过程,以及解析flutter主要的生命周期过程。认真读完本系列,读者一定会对Flutter运行过程了如指掌、胸有成竹。

本系列将有小量源码出没,建议读者打开编译器,配合框架源码食用,效果更佳。

开始的开始

本文主要介绍Flutter的更新机制,为了更好地理解Flutter的更新过程,首先得知道操作系统是如何协调图像的绘制和显示的,这里要引入一个概念:VSync(Vertical Synchronization,垂直同步信号)

在计算机操作系统中,CPU、GPU和显示器以一钟特定方式协作。

如上图,CPU将计算好的显示内容交给GPU,GPU将内容渲染后放入帧缓冲区(Buffer Queue),他们是帧生产者,不断往帧缓冲区填充数据。

而显示器则是帧消费者,不断从缓冲区中提取帧内容,将其显示在屏幕上。

为了更新屏幕的画面,显示器会以固定的频率从缓冲区中取数据。比如一般的手机屏幕刷新率是60Hz,当一帧图像显示完准备显示下一帧时,显示器会发出一个垂直同步信号(VSync),来提示CPU和GPU进行下一帧的渲染工作,60Hz的屏幕一秒就会发出60次这样的信号。

为什么需要VSync?

这里有两个概念:

  1. Refresh Rate:代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60Hz。
  2. Frame Rate:代表了GPU在一秒内绘制操作的帧数,例如30fps,60fps。

如果没有VSync,会出现两种情况:

  1. 屏幕刷新率大于帧(渲染)率:屏幕显示完一帧时,此时GPU还没渲染完下一帧,此时屏幕会依然显示原来那帧,出现卡帧的情况,也即我们常见的卡顿现象。
  2. 帧率大于屏幕刷新率:假如GPU的帧率为120 fps,而屏幕的刷新率只有60Hz。屏幕一次刷新时,GPU会输出两帧的画面,前一帧会被屏幕丢弃。那么对于用户来说,用户也只能从屏幕上感知到60Hz的刷新率,多余的帧,对于画面的提升没有任何帮助,还会浪费GPU的资源。

所以VSync的作用在于协调屏幕与显卡不同的工作速率,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步。

关于Vsync的更多知识,参见02-Understanding VSYNC 理解VSYNC

VSync在Flutter中的作用

那么在Flutter中,VSync是何作用呢?

作用在于通知Flutter框架刷新页面中需要刷新的元素。

这里的刷新,意指app显示出来后(第一帧显示完毕后)的更新页面操作,而flutter app页面第一帧的渲染,是不需要等待VSync信号到来的,而是通过手动刷新。这点读者需要提前知道,后文也会详细提到

Flutter通过向手机平台注册VSync信号监听,当信号来临时,通过回调方法,触发widget的build、layout、paint过程,来更新需要刷新的元素。

具体注册VSync监听的代码体现在PlatformDispatcher这个类里面,这个类封装于dart:ui库中的platform_dispatcher.dart文件中。

PlatformDispatcher

这个类负责派发从平台过来的各种从平台配置到屏幕和窗口的创建或销毁的事件,里面定义了许多的native方法,负责flutter与平台底层的交互。

PlatformDispatcher向上层暴露了核心调度程序API、输入事件回调、图形绘制API和其他此类核心服务。

其中有个方法值得我们关注:

/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
///
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

这个方法用于注册VSync信号的监听,当下一个VSync信号来临时,会调用PlatformDispatcher.onBeginFramePlatformDispatcher.onDrawFrame

在onDrawFrame中会一步步执行build、layout、paint等过程。

更多关于PlatformDispatcher的信息,可以参见源码,也可以阅读谷歌官方对外分享的API文档: 请科学上网

Flutter刷新页面的源码执行流程

先看当VSync信号回调后,Flutter做的工作

前面说到信号回调后,会执行PlatformDispatcher的onBeginFrame和onDrawFrame。

那PlatformDispatcher的实例什么时候被创建?其属性onBeginFrame和onDrawFrame是在哪里赋值的?
FrameCallback? get onBeginFrame => _onBeginFrame;
FrameCallback? _onBeginFrame;

VoidCallback? get onDrawFrame => _onDrawFrame;
VoidCallback? _onDrawFrame;

PlatformDispatcher绝大部分时间都是以一个单例存在,会在用到它的第一刻被初始化。

PlatformDispatcher._() {    // 私有构造方法,用于创建单例
  _setNeedsReportTimings = _nativeSetNeedsReportTimings;
}
static PlatformDispatcher get instance => _instance;
static final PlatformDispatcher _instance = PlatformDispatcher._();

而在分析onBeginFrame和onDrawFrame的赋值时机前,我们得简单先过一遍app初始化时都做些什么,因为在runApp初始化时有一些必要的初始化是后文需要用到的。

runApp()的方法源码如下:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

首先会调用WidgetsFlutterBindingensureInitialized()方法,看看内部实现

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  static WidgetsBinding ensureInitialized() {
    print("WidgetsFlutterBinding ensureInitialized() begin");
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }
}

第一次初始化需要调用 WidgetsFlutterBinding();,在执行自身的构造方法前,会默认调用父类的无参构造方法,该类继承自BindingBase,看看父类的实现:

abstract class BindingBase {
  /// Default abstract constructor for bindings.
  ///
  /// First calls [initInstances] to have bindings initialize their
  /// instance pointers and other state, then calls
  /// [initServiceExtensions] to have bindings initialize their
  /// observatory service extensions, if any.
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');

    print("BindingBase BindingBase() called");
    initInstances();

    initServiceExtensions();

    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});

    developer.Timeline.finishSync();
  }
  ....
}

跑下去会调用initInstances(),这个方法BindingBase里面有默认实现,在WidgetsBinding、RendererBinding和其他with的众兄弟间也有实现,他们会调用一遍吗?

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {}

关于dart,我也是这两天才学的。。extends和with配合使用,还有mixin、on这些的特性我现在也还没理清楚,有知道的读者可以评论告诉我,链接或私聊都是欢迎的。

不过我通过在各个方法中打log知道,其他的initInstances()也会调用,并按着WidgetsBinding,RendererBinding,SemanticsBinding...GestureBinding,BindingBase的顺序分别执行一遍,详见下图

那么在WidgetsBindinginitInstances() 方法做了什么呢?

// WidgetsBinding
void initInstances() {
  print("WidgetsBinding initInstances() called");
  super.initInstances();
  _instance = this;

  // Initialization of [_buildOwner] has to be done after
  // [super.initInstances] is called, as it requires [ServicesBinding] to
  // properly setup the [defaultBinaryMessenger] instance.
  _buildOwner = BuildOwner();       // 负责管理widgets的对象
  buildOwner!.onBuildScheduled = _handleBuildScheduled;
  window.onLocaleChanged = handleLocaleChanged;
  window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
  SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    return true;
  }());
}

首先创建BuildOwner对象,然后执行buildOwner!.onBuildScheduled = _handleBuildScheduled;,这里将_handleBuildScheduled赋值给了buildOwnder的onBuildScheduled属性。

BuildOwner对象,它负责跟踪哪些widgets需要重新构建,并处理应用于widgets树的其他任务,其内部维护了一个_dirtyElements列表,用以保存被标“脏”的elements。

每一个element被新建时,其BuildOwner就被确定了。一个页面只有一个buildOwner对象,负责管理该页面所有的element

当调用buildOwner.onBuildScheduled()时,便会走下面的流程。

// WidgetsBinding类
buildOwner!.onBuildScheduled = _handleBuildScheduled;

// WidgetsBinding类
void _handleBuildScheduled() {
  ensureVisualUpdate();
}

// SchedulerBinding类
void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

当schedulerPhase处于idle状态(空闲,当前没有帧正在被处理)

或postFrameCallbacks状态(当负责[当前帧的clean-up和安排下一帧]的callbacks正在被调用时的状态)时,会调用scheduleFrame()。

这个scheduleFrame()是不是和PlatfromDispatcher中的scheduleFrame()很像?看看实现

// 以下都在SchedulerBinding类
void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled)
    return;
  // ①
  ensureFrameCallbacksRegistered(); 
  // ②
  window.scheduleFrame();                       
  _hasScheduledFrame = true;
}

// 在这个方法里完成对onBeginFrame,onDrawFrame的赋值
@protected
void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame;
}
// window的类型是一个FlutterView,FlutterView里面有一个PlatformDispatcher属性
ui.SingletonFlutterWindow get window => ui.window;

// 初始化时把PlatformDispatcher.instance传入,完成初始化
ui.window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);

// SingletonFlutterWindow的类结构
class SingletonFlutterWindow extends FlutterWindow {
  ...
  // 实际上是给platformDispatcher.onBeginFrame赋值
  FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
  set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }

  VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
  set onDrawFrame(VoidCallback? callback) {
    platformDispatcher.onDrawFrame = callback;
  }

  // window.scheduleFrame实际上是调用platformDispatcher.scheduleFrame()
  void scheduleFrame() => platformDispatcher.scheduleFrame();
  ...
}

class FlutterWindow extends FlutterView {
  FlutterWindow._(this._windowId, this.platformDispatcher);

  final Object _windowId;

  // PD
  @override
  final PlatformDispatcher platformDispatcher;

  @override
  ViewConfiguration get viewConfiguration {
    return platformDispatcher._viewConfigurations[_windowId]!;
  }
}

从代码可以看出

①:scheduleFrame()这个方法会被调用多次(setState()也会调用),在第一次调用时,ensureFrameCallbacksRegistered()方法会对onBeginFrame和onDrawFrame进行初始化。也即,在第一次调用SchedulerBinding.ScheduleFrame()时会对onBeginFrame和onDrawFrame进行初始化

②:调用window.schedule()实际上是调用performDispatcher.scheduleFrame()去注册一个VSync监听。

关于runApp的ensureInitialized()部分,目前就说到这,后面有提到时再根据需要补充

WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();

而紧跟ensureInitialized()后面调用的scheduleAttachRootWidget(app)和scheduleWarmUpFrame();涉及到的是app启动后界面第一帧的绘制,这方面后面再谈,这里先跳过。

说到这感觉有些乱了,在说下一部分前,先对以上部分做个总结:

  1. 在runApp()时,会调用ensureInitialized()方法进行必要的初始化,会以WidgetsBinding,RendererBinding,SemanticsBinding...GestureBinding,BindingBase的顺序执行initInstances(),每个类都有各自的职责,目前只看到WidgetsBinding的部分,它主要负责BuildOwner对象的初始化,这个对象内部维护了dirtyElements列表,用以保存被标“脏”的elements
  2. 在BuildOwner对象中,有一个onBuildScheduled属性,它是一个方法回调,在WidgetsBinding.initInstances()内被同步赋值,调用这个方法,内部会对onDrawFrame()和onBeginFrame()进行赋值(如果之前没被初始化的话),同时调用PlatformDispatcher.scheduleFrame()方法,注册VSync信号监听。

VSync信号回调时,Flutter具体做了什么操作?

那得看看_handleBeginFrame_handleDrawFrame的具体实现了。

_handleBeginFrame()的内部会调用handleBeginFrame()

// SchedulerBinding类
void handleBeginFrame(Duration? rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;

  _hasScheduledFrame = false;
  try {
    // TRANSIENT FRAME CALLBACKS
    Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
    _schedulerPhase = SchedulerPhase.transientCallbacks;
    final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
    _transientCallbacks = <int, _FrameCallbackEntry>{};
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id))
        _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
    });
    _removedIds.clear();
  } finally {
    _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
  }
}

在这里会遍历_transientCallbacks集合,并执行回调方法,在handleBeginFrame()主要进行的是绘制下一帧的准备工作,让framework准备好下一帧的绘制工作,例如重新设置状态、变量等等。

_handleDrawFrame()内部会调用handleDrawFrame()

// SchedulerBinding类
void handleDrawFrame() {
  Timeline.finishSync(); // end the "Animate" phase
  try {
    // PERSISTENT FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    for (final FrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);

    // POST-FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    final List<FrameCallback> localPostFrameCallbacks =
        List<FrameCallback>.from(_postFrameCallbacks);
    _postFrameCallbacks.clear();
    for (final FrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);
  } finally {
    _schedulerPhase = SchedulerPhase.idle;
    Timeline.finishSync(); // end the Frame
    _currentFrameTimeStamp = null;
  }
}

主要工作是遍历两个callback集合

1. _persistentCallbacks

persisentcallback用于执行layout、paint、composite过程

这个callbacks集合通过下面方式注册

void addPersistentFrameCallback(FrameCallback callback) {
  _persistentCallbacks.add(callback);
}

而这个方法,会在RendererBinding的initInstances()中调用,也即我们一开始执行初始化的地方。并且在RendererBinding的初始化中实际上还做了许多工作,一起看看吧

// RendererBinding类
void initInstances() {
  print("RendererBinding initInstances() called");
  super.initInstances();
  _instance = this;
  _pipelineOwner = PipelineOwner(
    onNeedVisualUpdate: ensureVisualUpdate,
    onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
    onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
  );
  window
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsAction = _handleSemanticsAction;
  initRenderView();
  _handleSemanticsEnabledChanged();
  addPersistentFrameCallback(_handlePersistentFrameCallback);
  initMouseTracker();
  if (kIsWeb) {
    addPostFrameCallback(_handleWebFirstFrame);
  }
}

首先是,初始化_pipelineOwner,这个对象很重要,它是render树的所有者,负责维护布局、复合、绘制和可访问性语义的脏状态,即负责页面绘制的各种过程。

同时会对window窗口进行初始化赋值,并调用initRenderView()

// RendererBinding类
void initRenderView() {
  renderView = RenderView(configuration: createViewConfiguration(), window: window);
  renderView.prepareInitialFrame();
}

RenderView继承自RenderObject,是整个Render Object树的根对象。

该方法只做了两件事,一是初始化renderView,二是调用renderView.prepareInitialFrame(),将这个根RenderObject加入到pipelineOwner的_nodesNeedingLayout_nodesNeedingPaint列表中,这两个列表很重要,后文说build过程会提到,这里就先说到这里。

接着往下看,调用addPersistentFrameCallback(_handlePersistentFrameCallback);

注册persistentFrameCallback,到了我们的重头戏部分,看看_handlePersistentFrameCallback的实现:

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

drawFrame(),在RenderBindng中的实现如下:

// RendererBinding
void drawFrame() {
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

在WidgetsBindng中实现如下:

// WidgetsBindng
@override
void drawFrame() {
  ...
  try {
    if (renderViewElement != null)
      buildOwner!.buildScope(renderViewElement!);
    super.drawFrame();
    buildOwner!.finalizeTree();
  } finally {}
  ...
}

首先会调用buildOwner!.buildScope(renderViewElement!);在这个方法里面,会执行所有被标记为dirty的element的rebuild过程,rebuild内部又会调用widget.build来新建widget,更新element。

接着调用super.drawFrame(),会进入RendererBinding的drawFrame(),即走layout、paint、composite过程。

所以更新的绘制流程实际上是:rebuild -> layout -> paint -> composite

以上执行完后,如果更新了element树,一些element不再被用到时,会被放入_inactiveElements列表中,标记为不再活动

紧接着调用buildOwner!.finalizeTree(); unmount 所有不再活动的elements

至此一次界面刷新完成。

2. _postFrameCallbacks

_persistentCallbacks是负责帧的绘制过程,而_postFrameCallbacks紧跟着_persistentCallbacks调用,此时当前帧还在显示,post-frame callback负责当前帧的清理工作以及下一帧的工作调度。

值得注意的是,portFrameCallbacks注册后只会被按顺序执行一次,且不可unRegister,执行完毕后就会调用clear(),把callback清理掉。

以上,就是当VSync信号回调时,Flutter所做的工作,主要是更新"脏"element

,执行其rebuild,layout、paint、composite流程。

调用setState()的实际操作

已经知道flutter是更新界面的原理,那我们开发者如何触发flutter更新界面呢?

按照上文的理解,我们只需要将要更新的element标”脏“,然后注册VSync信号监听,当下一次VSync信号到来时,flutter就自动为我们遍历这些脏的elements去更新了。

我们最常用的setState()方法内部又是如何做的呢,看看源码吧!

有了前文的铺垫,再看setState()的源码相信会容易很多。

setState()源码位于framework.dart文件中

// example
setState(() {
  _a = 3;
});

// framework.dart
void setState(VoidCallback fn) {
  final Object? result = fn() as dynamic;
  _element!.markNeedsBuild();
}

我们一般会在setState调用的同时,修改一些属性,这样在下次绘制时,就会使用我们更新后的属性,例如example的代码。

到了setState()内部,先是执行了fn,然后执行_element!.markNeedsBuild();

_element就是statefullWidget创建的StatefulElement对象,其markNeedsBuild()方法实现在父类Element中,看看源码:

// Element类
void markNeedsBuild() {
  if (_lifecycleState != _ElementLifecycle.active)
    return;
  if (dirty)
    return;
  _dirty = true;
  owner!.scheduleBuildFor(this);
}

首先先把element标”脏“,然后调用owner!.scheduleBuildFor(this);

这个owner是BuildOwner对象,它就是我们一开始通过runApp()初始化时创建的buildOwner对象。前文说过,它内部维护了一个_dirtyElements列表,用以保存被标“脏”的elements。

接下来看看buildOwner的scheduleBuildFor干了些什么

void scheduleBuildFor(Element element) {
  if (element._inDirtyList) {
    _dirtyElementsNeedsResorting = true;
    return;
  }
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled!();
  }
  _dirtyElements.add(element);
  element._inDirtyList = true;
}

首先,如果该element已经在dirtyList中了,则设置_dirtyElementsNeedsResorting = true后直接返回。

_dirtyElementsNeedsResorting表,是否需要对dirtElements重新排序,因为之前被标脏的element,它在树中的位置(深度)可能已经变了,需要对dirtyElement重新排序。

否则,如果是新的dirty element,且当前这个dirtyElement列表还未注册过VSync信号监听,则还需要执行onBuildScheduled();,这个方法我们在前文初始化时说过,会调用PlatformDispatcher.scheduleFrame()向平台注册VSync监听。

然后将该dirty element加入到dirty element列表内。

至此,setState()的工作就完成了,当下一个VSync信号到来时,flutter就会自动帮我们更新那些”脏“的element了。

最后的最后

这篇可谓一篇长文,我相信认真读完的你一定有所收获,最后我将以上更新过程总结成一幅图,送给大家,帮助大家理解。

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

推荐阅读更多精彩内容