Flutter 生命周期和渲染原理

Flutter-widget生命周期

生命周期基本概念

什么是生命周期

  • 本质是回调方法(函数)
  • 让开发者知道这个widget它处于什么样的状态

有什么作用

1.监听widget的事件
2.初始化数据

  • 创建数据
  • 发送网络请求
    3.内存管理
  • 销毁数据,销毁监听者
  • 销毁timer等等

widget的生命周期

StatelessWidget

class MyHomePage extends StatelessWidget {
 final String title;
 MyHomePage({this.title}) {
   print('构造函数被调用了!');
 }
 @override
 Widget build(BuildContext context) {
   print('build方法被调用了!');
   return Center(
     child: Text(title),
   );
 }
}

打印结果

flutter: 构造函数被调用了!
flutter: build方法被调用了!

StatefulWidget

包含两个对象Widget,State

class MyHomePage extends StatefulWidget {
  final String title;
  MyHomePage({this.title}) {
    print('构造函数被调用了!');
  }
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _count = 0;

  _MyHomePageState() {
    print('State构造方法来了!');
  }

  @override
  void initState() {
    print('State的init来了!');
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('State的build来了!');
    return Column(
      children: <Widget>[
        RaisedButton(
          child: Icon(Icons.add),
          onPressed: () {
            _count++;
            setState(() {});
          },
        ),
        Text('$_count'),
      ],
    );
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies');
    super.didChangeDependencies();
  }

  //当State对象从渲染树中移出的时候,就会调用!即将销毁!
  @override
  void deactivate() {
    super.deactivate();
  }

  @override
  void dispose() {
    print('State的dispose');
    super.dispose();
  }
}

打印结果

flutter: 构造函数被调用了!
flutter: State构造方法来了!
flutter: State的init来了!
flutter: didChangeDependencies
flutter: State的build来了!

从上面的demo可以看出StatefulWidget中函数调用顺序为

1.Widget构造方法
2.Widget的CreateState
3.State的构造方法
4.State的initState方法

  • initState是StatefulWidget创建完后调用的第一个方法,而且只执行一次
  1. didChangeDependencies方法(改变依赖关系)
  • 在StatefulWidget第一次创建的时候didChangeDependencies会被调用一次, 会在initState方法之后会被立即调用
  • 从其他对象中依赖一些数据发生改变时, 比如所依赖的InheritedWidget状态发生改变时, 也会被调用
    6.State的build方法
    当调用setState方法,会重新调用build方法进行渲染
    7.当State对象从渲染树中移出的时候,会先调用deactivate(),即将销毁,然后调用dispose()
    8当Widget销毁的时候,调用State的dispose

didChangeDependencies()

这里我们看一个demo

class InheritedDemo extends StatefulWidget {
  @override
  _InheritedDemoState createState() => _InheritedDemoState();
}

class _InheritedDemoState extends State<InheritedDemo> {
  int count = 1;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Test1(count),
        RaisedButton(
          child: Text('我是按钮'),
          //setState!

          onPressed: () => setState(() {
            count++;
          }),
        )
      ],
    );
  }
}

class Test1 extends StatelessWidget {
 final count;
 Test1(this.count);
  @override
  Widget build(BuildContext context) {
    return Test2(count);
  }
}

class Test2 extends StatelessWidget {
 final count;
 Test2(this.count);
  @override
  Widget build(BuildContext context) {
    return Test3(count);
  }
}

class Test3 extends StatefulWidget {
 final count;
 Test3(this.count);
  @override
  _Test3State createState() => _Test3State();
}

class _Test3State extends State<Test3> {
  @override
  Widget build(BuildContext context) {
    return Text(widget.count.toString());
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies');
    super.didChangeDependencies();
  }
}


这里widget树层级非常多,一层一层的传递count,会很繁琐,这里我们可以使用数据共享的方式(也可以成为状态管理),使用InheritedWidget,能做到其子Widget能共享InheritedWidget的数据

import 'package:flutter/material.dart';

//数据共享!
class MyData extends InheritedWidget {
  MyData({this.data, Widget child}) : super(child: child);
  final int data; //需要在子Widget中共享的数据!

  //提供一个方法让子Widget访问的共享数据!
  static MyData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyData>();
  }

  @override
  bool updateShouldNotify(MyData oldWidget) {
//如果数据发生变化,其依赖InheritedWidget的子Widget就能收到通知,子Widget就会调用build()
    return oldWidget.data != data;
  }
}

class InheritedDemo extends StatefulWidget {
  @override
  _InheritedDemoState createState() => _InheritedDemoState();
}

class _InheritedDemoState extends State<InheritedDemo> {
  int count = 1;

  @override
  Widget build(BuildContext context) {
////Column被包在了MyData中,MyData其子Widget 都能使用 data字段
    return MyData(
      data: count,
      child: Column(
        children: <Widget>[
          Test1(),
          RaisedButton(
            child: Text('我是按钮'),
            //setState!

            onPressed: () => setState(() {
              count++;
            }),
          )
        ],
      ),
    );
  }
}

class Test1 extends StatelessWidget {
//  final count;
//  Test1(this.count);
  @override
  Widget build(BuildContext context) {
    return Test2();
  }
}

class Test2 extends StatelessWidget {
//  final count;
//  Test2(this.count);
  @override
  Widget build(BuildContext context) {
    return Test3();
  }
}

class Test3 extends StatefulWidget {
//  final count;
//  Test3(this.count);
  @override
  _Test3State createState() => _Test3State();
}

class _Test3State extends State<Test3> {
  @override
  Widget build(BuildContext context) {
    return Text(MyData.of(context).data.toString());
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies');
    super.didChangeDependencies();
  }
}


这时候我们再点击按钮改变count值时,发现每次都会调用didChangeDependencies()方法,日常开发中Theme.of(context).textTheme;、MediaQuery.of(context).size;都是使用这个技术。

Flutter渲染原理

在flutter渲染过程中,有三颗重要的树,Widget树,Element树,Render树。下面是flutter中三种树结构


截屏2021-08-18 下午2.59.03.png

Widget树

  • 在整个Flutter项目结构也是由很多个Widget构成的, 本质上就是一个Widget Tree
  • 在上面的类似Widget Tree结构中, 很可能会有大量的Widget在树结构中存在引用关系, 而且每个Widget所依赖的配置和状态发生改变的时候, Widget都会重新build, Widget会被不断的销毁和重建,那么意味着这棵树非常不稳定
  • 所以Flutter Engin也不可能直接把Widget渲染到界面上, 这是极其损耗性能的, 所以在渲染层面Flutter引用了另外一个树结构RenderObject Tree

Render树

  • 每一个RenderObject都是渲染树上的一个对象
  • RenderObject层是渲染库的核心, 最终Flutter Engin是把RenderObject真正渲染到界面上的,flutter引擎是针对Render树进行渲染,要注意并不是所有的Widget都会被独立渲染,只有继承RenderObjectWidget的才会创建RenderObject对象。

Element树

  • Element是Widget在树中具有特定位置的是实例化
  • Element Tree中的每一个Element是和Widget Tree中的每一个Widget一一对应的
  • 当Widget Tree所依赖的状态发生改变(更新或者重新创建Widget)的时候, Element根据拿到之前所保存的旧的Widget和新的Widget做一个对比, 判断两者的Key和类型是否是相同的, 相同的就不需要重新创建, 有需要的话, 只需要更新对应的属性,并将真正需要修改的部分同步到真实的RenderObject树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

简而言之,Widget 树就是配置信息的树,我们平时写代码写的就是这棵树,RenderObject 树是渲染树,负责计算布局,绘制,Flutter 引擎就是根据这棵树来进行渲染的,Element 树作为中间者,管理着将 Widget 生成 RenderObject和一些更新操作。

对象的创建过程

Widget

来到Widget 类里面可以看到有以下方法,所以每一个Widget都会创建一个Element对象,它会隐式调用createElement方法,将Element加入Element树中。

   @protected
  @factory
  Element createElement();

我们再看一下Column,可以看到它继承自Flex,Flex继承自MultiChildRenderObjectWidget,MultiChildRenderObjectWidget继承自RenderObjectWidget,点进去可以看到里面有一个方法createRenderObject和创建RenderObjectElement的方法

abstract class RenderObjectWidget extends Widget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const RenderObjectWidget({ Key? key }) : super(key: key);

  /// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
  @override
  @factory
  RenderObjectElement createElement();

  /// Creates an instance of the [RenderObject] class that this
  /// [RenderObjectWidget] represents, using the configuration described by this
  /// [RenderObjectWidget].
  ///
  /// This method should not do anything with the children of the render object.
  /// That should instead be handled by the method that overrides
  /// [RenderObjectElement.mount] in the object rendered by this object's
  /// [createElement] method. See, for example,
  /// [SingleChildRenderObjectElement.mount].
  @protected
  @factory
  RenderObject createRenderObject(BuildContext context);

在mount方法中会调用createRenderObject方法,来创建RenderObject

@override
  void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    _renderObject = widget.createRenderObject(this);
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    assert(() {
      _debugUpdateRenderObjectOwner();
      return true;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }

我们再看一下常用的StatefulWidget和StatelessWidget

  • StatelessWidget
    这里会创建一个StatelessElement,继承自ComponentElement
abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key? key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);

这里可以看到在创建Element之后, 创建出来的elment会拿到传过来的widget, 然后调用widget自己的build方法, 这也就是为什么所有的Widget创建出来之后都会调用build方法的原因

class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}
  • StatefulWidget
    这里会创建一个StatefulElement,它继承自ComponentElement
abstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key? key }) : super(key: key);

  /// Creates a [StatefulElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatefulElement createElement() => StatefulElement(this);

  /// Creates the mutable state for this widget at a given location in the tree.
  ///
  /// Subclasses should override this method to return a newly created
  /// instance of their associated [State] subclass:
  ///
  /// ```dart
  /// @override
  /// _MyState createState() => _MyState();
  /// ```
  ///
  /// The framework can call this method multiple times over the lifetime of
  /// a [StatefulWidget]. For example, if the widget is inserted into the tree
  /// in multiple locations, the framework will create a separate [State] object
  /// for each location. Similarly, if the widget is removed from the tree and
  /// later inserted into the tree again, the framework will call [createState]
  /// again to create a fresh [State] object, simplifying the lifecycle of
  /// [State] objects.
  @protected
  @factory
  State createState(); // ignore: no_logic_in_create_state, this is the original sin
}

StatefulElement会调用widget的createState(),这里调用build的时候,调用的是state中的build方法:

/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : state = widget.createState(),
        super(widget) {
    assert(() {
      if (!state._debugTypesAreRight(widget)) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
          ErrorDescription(
            'The createState function for ${widget.runtimeType} returned a state '
            'of type ${state.runtimeType}, which is not a subtype of '
            'State<${widget.runtimeType}>, violating the contract for createState.'
          ),
        ]);
      }
      return true;
    }());
    assert(state._element == null);
    state._element = this;
    assert(
      state._widget == null,
      'The createState function for $widget returned an old or invalid state '
      'instance: ${state._widget}, which is not null, violating the contract '
      'for createState.',
    );
    state._widget = widget;
    assert(state._debugLifecycleState == _StateLifecycle.created);
  }

  @override
  Widget build() => state.build(this);
  • BuildContext
    从上面的代码可以看到build方法传入的参数都是Element,所以本质上BuildContext就是当前的Element

Key的原理

我们之前创建的每一个Widget, 在其构造方法中我们都会看到一个参数Key,那个这个key有什么作用呢,我们下面就来看下

abstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key? key }) : super(key: key);

先看一个demo,希望每次点击按钮后删除数组第一个元素,先看第一种实现方式,用StatelessWidget,可以看到运行效果正常,依次删除

class KeyDemo extends StatefulWidget {
  @override
  _KeyDemoState createState() => _KeyDemoState();
}

class _KeyDemoState extends State<KeyDemo> {
  List<Widget> items = [
    StlItem(
      'aaaaa',

    ),
    StlItem(
      'bbbbb',

    ),
    StlItem(
      'ccccc',

    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('keyDemo'),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: items,
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            items.removeAt(0);
          });
        },
      ),
    );
  }
}


//做一个正方形!
class StlItem extends StatelessWidget {
  final title;
  StlItem(this.title);
  final _color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: _color,
      child: Text(title),
    );
  }
}

现在我们用StatefulWidget来做一个正方形,点击按钮删除时,发生了一个神奇的事,删除的第一条数据,但是从颜色上看,是删除了最后一条,好像复用了

class StfulItem extends StatefulWidget {
  final title;
  StfulItem(this.title, {Key key}) : super(key: key);

  @override
  _StfulItemState createState() => _StfulItemState();
}

class _StfulItemState extends State<StfulItem> {
  final _color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: _color,
      child: Text(widget.title),
    );
  }
}

查看Widget源码,里面有段代码,是否要更新Element是由这个方法决定的,做到增量更新

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

当不设置key的是时候,右边的element会从前面第一个依次判断比较,第一个Element跟之前第二个widget比较,canUpdate返回true,所以Element中对应的State引用也没有发生改变,第一个Element指向之前的第二个widget,第二个Element指向之前的第三个widget,第三个Element被干掉

截屏2021-08-23 下午5.41.49.png

我们在之前的基础上,为每一个StfulItem添加一个key

 List<Widget> items = [
    StfulItem(
      'aaaaa',
      key: ValueKey(111),

    ),
    StfulItem(
      'bbbbb',
      key: ValueKey(222),
    ),
    StfulItem(
      'ccccc',
      key: ValueKey(333),
    ),
  ];

运行代码,效果正常,根据runtimeType和key进行比对, 和新的Widget Tree相同的会被继续复用, 否则就会删除

Key的分类

Key本身是一个抽象类,子类包含LocalKey和GlobalKey。

  • LocalKey
    1.ValueKey,以一个数据作为Key,如:数字,字符
    class ValueKey<T> extends LocalKey {
    /// Creates a key that delegates its [operator==] to the given value.
    const ValueKey(this.value);
    
    /// The value to which this key delegates its [operator==]
    final T value;
    
    @override
    bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is ValueKey<T>
        && other.value == value;
    }
    
    

2.ObjectKey,以object对象作为Key,例如 ObjectKey(Text('222')),

class ObjectKey extends LocalKey {
/// Creates a key that uses [identical] on [value] for its [operator==].
const ObjectKey(this.value);

/// The object whose identity is used by this key's [operator==].
final Object? value;

@override
bool operator ==(Object other) {
  if (other.runtimeType != runtimeType)
    return false;
  return other is ObjectKey
      && identical(other.value, value);
}

3.UniqueKey 可以保证key的唯一性(一旦使用UniqueKey那么就不存在Element复用了)

class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
///
/// The key cannot be created with a const constructor because that implies
/// that all instantiated keys would be the same instance and therefore not
/// be unique.
// ignore: prefer_const_constructors_in_immutables , never use const for this class
UniqueKey();

@override
String toString() => '[#${shortHash(this)}]';
}
  • GlobalKey
    GlobalKey 可以获取到对应的widget的state对象
    GlobalKey 使用了一个静态常量 Map 来保存它对应的 Element。你可以通过 GlobalKey 找到持有该GlobalKey的 Widget,State 和 Element。
    需要注意:GlobalKey 是非常昂贵的,需要谨慎使用。
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
 /// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for
 /// debugging.
 ///
 /// The label is purely for debugging and not used for comparing the identity
 /// of the key.
 factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);

 /// Creates a global key without a label.
 ///
 /// Used by subclasses because the factory constructor shadows the implicit
 /// constructor.
 const GlobalKey.constructor() : super.empty();

 static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
 static final Set<Element> _debugIllFatedElements = HashSet<Element>();
 // This map keeps track which child reserves the global key with the parent.
 // Parent, child -> global key.
 // This provides us a way to remove old reservation while parent rebuilds the
 // child in the same slot.
 static final Map<Element, Map<Element, GlobalKey>> _debugReservations = <Element, Map<Element, GlobalKey>>{};


这里我们看一个使用GlobalKey的demo,一个StatelessWidget包含子StatefulWidget,点击父亲里面的一个按钮改变子节点的内容

class GlobalKeyDemo extends StatelessWidget {
final GlobalKey<_ChildPageState> _globalKey = GlobalKey();

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('GlobalKeyDemo'),
    ),
    body: ChildPage(
      key: _globalKey,
    ),
    floatingActionButton: FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () {
        _globalKey.currentState.data =
            'old:' + _globalKey.currentState.count.toString();
        _globalKey.currentState.count++;

        _globalKey.currentState.setState(() {});
      },
    ),
  );
}
}

class ChildPage extends StatefulWidget {
ChildPage({Key key}) : super(key: key);
@override
_ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends State<ChildPage> {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        Text(count.toString()),
        Text(data),
      ],
    ),
  );
}
}

总结

1.Widget会隐式调用createElement方法创建Element,每一个Widget都会创建一个Element对象,然后调用mount方法,但是不同Element中mount的处理方式不同
2.会创建三种Element

  • RenderElement
    RenderElement主要是创建RenderObject对象,只有继承自RenderObjectWidget的Widget会创建RenderObjectElement,创建步骤先创建RanderElement,创建出来后调用mount方法,在mount方法中会调用createRenderObject方法,来创建RenderObject

  • StatefulElement
    StatefulWidget会创建StatefulElement,创建出来后调用createState方法,创建state,将Widget赋值给state,最后调用state的build方法,并且将Element传出去

  • StatelessElement

StatelessWidget会创建StatelessElement,这里主要就是调用build方法,将Element传出去

补充几个面试题

1.createState 方法在什么时候调用?state 里面为啥可以直接获取到 widget 对象?

答:Flutter 会在遍历 Widget 树时调用 Widget 里面的 createElement 方法去生成对应节点的 Element 对象,同时执行 StatefulWidget 里面的 createState 方法创建 state,并且赋值给 Element 里的 _state 属性,当前 widget 也同时赋值给了 state 里的_widget,state 里面有个 widget 的get 方法可以获取到 _widget 对象。

2.build 方法是在什么时候调用的?

答:Element 创建好以后 Flutter 框架会执行 mount 方法,对于非渲染的 ComponentElement 来说 mount 主要执行 widget 里的 build 方法,StatefulElement 执行 build 方法的时候是执行的 state 里面的 build 方法,并且将自身传入,也就是常见的 BuildContext

3.BuildContext 是什么?

答:StatefulElement 执行 build 方法的时候是执行的 state 里面的 build 方法,并且将自身传入,也就是 常见的 BuildContext。简而言之 BuidContext 就是 Element。

4.Widget 频繁更改创建是否会影响性能?复用和更新机制是什么样的?

答:不会影响性能,widget 只是简单的配置信息,并不直接涉及布局渲染相关。Element 层通过判断新旧 widget 的runtimeType 和 key 是否相同决定是否可以直接更新之前的配置信息,也就是替换之前的 widget,而不必每次都重新创建新的 Element。

5.创建 Widget 里面的 Key 到底是什么作用?
答:Key 作为 Widget 的标志,在widget 变更的时候通过判断 Element 里面之前的 widget 的 runtimeType 和 key来决定是否能够直接更新。

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

推荐阅读更多精彩内容