Flutter中三棵樹(shù)的理解

Widget太闺、Element和RenderObject

Widget

Widget 是用戶頁(yè)面的描述,表示了Element的配置信息返敬,F(xiàn)lutter頁(yè)面都是由各種各樣的Widget組合聲明成的讹剔。Widget本身是不可變的immutable,注解如下:

@immutable
abstract class Widget extends DiagnosticableTree {/// ...}

這也就意味著啊易,所有它直接聲明或繼承的變量都必須為final類(lèi)型的。如果想給widget關(guān)聯(lián)一個(gè)可變的狀態(tài)饮睬,考慮使用StatefulWidget租谈,它會(huì)通過(guò)[StatefulWidget.createState]創(chuàng)建一個(gè)State對(duì)象,然后捆愁,每當(dāng)它轉(zhuǎn)化成一個(gè)element時(shí)會(huì)合并到樹(shù)上割去。

子類(lèi):


image

StatelessWidget、StatefulWidget我們很熟悉是用來(lái)編寫(xiě)頁(yè)面和組件的昼丑,那另外三個(gè)都是做什么用的呢呻逆?

  • RenderObjectWidget,從名字上就能看出它是一個(gè)Widget菩帝,然后和實(shí)際渲染對(duì)象RenderObject有撇不清的關(guān)系咖城。它提供了RenderObjectElement的配置信息,其中包裝了RenderObject呼奢。也就是從頁(yè)面上編寫(xiě)的StatelessWidget和StatefulWidget在遞歸的build過(guò)程中宜雀,會(huì)最終返回實(shí)際可渲染的Widget對(duì)象,也就是RenderObjectWidget控妻,那么這個(gè)轉(zhuǎn)化關(guān)系是一一對(duì)應(yīng)的嗎州袒,其實(shí)不是的,后邊再具體分析
  • PreferredSizeWidget弓候,一個(gè)返回它自身想要大小的組件郎哭,如果它在布局過(guò)程中是不受限制的他匪,例如,AppBar和TabBar
  • ProxyWidget夸研,代理組件邦蜜,提供一個(gè)子組件,而不是自己創(chuàng)建亥至,例如悼沈,InheritedWidget和ParentDataWidget

Element

元素樹(shù),是Widget在具體位置的實(shí)例化姐扮,它負(fù)責(zé)控制Widget的生命周期絮供,持有了widget實(shí)例和renderObject實(shí)例,它和Widget繼承自同一個(gè)類(lèi)茶敏,DiagnosticableTree可診斷樹(shù)壤靶,并且實(shí)現(xiàn)了BuildContext類(lèi)。

image

Element有兩種基本類(lèi)型:

  • ComponentElement惊搏,其他elements的宿主贮乳,它本身不包含RenderObject,而由它持有的element節(jié)點(diǎn)包含恬惯,像StatelessWidget 和StatefulWidget 中分別創(chuàng)建的StatelessElement和StatefulElement都是繼承自ComponentElement
  • RenderObjectElement向拆,參與layout或者繪制階段的元素

RenderObject

渲染樹(shù)中的每個(gè)節(jié)點(diǎn)基類(lèi)是RenderObject,它定義了布局和繪制的抽象模型酪耳。每一個(gè)RenderObject有一個(gè)parent浓恳,和一個(gè)parentData,父級(jí)的RenderObject可以在其中存儲(chǔ)孩子的具體數(shù)據(jù)葡兑,例如奖蔓,child的位置信息。

image
  • RenderObject 僅實(shí)現(xiàn)了基本的布局和繪制讹堤,沒(méi)有具體的布局繪制模型,相當(dāng)于ViewGroup厨疙,其子類(lèi)RenderBox使用了笛卡爾坐標(biāo)系洲守,它的一些子類(lèi)是真正的渲染樹(shù)上的節(jié)點(diǎn)。大多數(shù)情況下沾凄,當(dāng)我們想自定義一個(gè)渲染對(duì)象時(shí)梗醇,直接繼承RenderObject有些過(guò)重overkill,更好的選擇是繼承RenderBox撒蟀,除非你不想使用笛卡爾坐標(biāo)系統(tǒng)叙谨。
  • RenderView,通常情況下是Flutter渲染樹(shù)的根節(jié)點(diǎn)保屯,可以理解為DecorView手负,它只有一個(gè)子節(jié)點(diǎn)涤垫,必須是RenderBox類(lèi)型的。

對(duì)應(yīng)關(guān)系

從Widget構(gòu)建Element

看這段簡(jiǎn)單的代碼片段竟终,顯示了widget樹(shù)形結(jié)構(gòu)

Container(
  color: Colors.blue,
  child: Row(
    children: [
      Image.network('https://www.example.com/1.png'),
      const Text('A'),
    ],
  ),
);

當(dāng)Flutter要渲染這個(gè)Container到頁(yè)面時(shí)蝠猬,會(huì)調(diào)用它的build()方法,返回一個(gè)widget的子樹(shù)统捶,包含它的child樹(shù)Row及其children的子樹(shù)榆芦,還有一些其它的樹(shù)的節(jié)點(diǎn),看下它的build()函數(shù):

class Container extends StatelessWidget {
  ///  創(chuàng)建一個(gè)結(jié)合常用的繪畫(huà)喘鸟、定位和控制大小的組件
    Container({
    Key? key,
    this.alignment,
    this.padding,
    this.color,
    this.decoration,
    this.foregroundDecoration,
    double? width,
    double? height,
    BoxConstraints? constraints,
    this.margin,
    this.transform,
    this.transformAlignment,
    this.child,
    this.clipBehavior = Clip.none,
  }) : // ...
  
  @override
  Widget build(BuildContext context) {
    Widget? current = child;
        // ...
    if (alignment != null)
      current = Align(alignment: alignment!, child: current);

    // ...
    if (effectivePadding != null)
      current = Padding(padding: effectivePadding, child: current);

    if (color != null)
      current = ColoredBox(color: color!, child: current);
        // ...
    if (decoration != null)
      current = DecoratedBox(decoration: decoration!, child: current);

    return current!;
  }
}

可以看到匆绣,Container的一些屬性,都代表插入一個(gè)控制該屬性的新節(jié)點(diǎn)widget什黑,所以它本身就是一個(gè)封裝崎淳,替我們組合了大量小部件,減輕了開(kāi)發(fā)工作量兑凿。我們?cè)O(shè)置了color屬性凯力,它會(huì)插入一個(gè)ColoredBox節(jié)點(diǎn),顯示它的顏色礼华。

相應(yīng)的咐鹤,Image和Text在build期間也可能插入子節(jié)點(diǎn)比如RawImage和RichText,所以widget樹(shù)的層級(jí)結(jié)構(gòu)可能比代碼展示的更深

image.png

在構(gòu)建階段圣絮,F(xiàn)lutter將上述的widget轉(zhuǎn)換成相應(yīng)的element tree 祈惶,一一對(duì)應(yīng),樹(shù)的層級(jí)結(jié)構(gòu)上的每個(gè)元素代表了一個(gè)具體位置的widget實(shí)例扮匠。

這里的一一對(duì)應(yīng)其實(shí)是framework層的經(jīng)過(guò)轉(zhuǎn)化后的widget捧请,并不是代碼層的用戶編寫(xiě)的widget跟element的對(duì)應(yīng),比如一個(gè)Container在設(shè)置屬性后被轉(zhuǎn)化成多個(gè)子widget棒搜,同時(shí)對(duì)應(yīng)了多個(gè)element節(jié)點(diǎn)疹蛉。

image

上邊提到了Element實(shí)現(xiàn)了BuildContext,任何widget的element可以通過(guò)build()方法中傳入的BuildContext參數(shù)訪問(wèn)到力麸,它是widget在樹(shù)上操作的句柄可款。例如,可以調(diào)用Theme.of(context)克蚂,查找widget樹(shù)上最近的主題闺鲸,如果widget定義了單獨(dú)的主題就返回它,如果沒(méi)有返回app的主題

/// An [Element] that uses a [StatelessWidget] as its configuration.
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();
  }
}

可以看到埃叭,StatelessElement元素在構(gòu)建的時(shí)候調(diào)用build方法摸恍,會(huì)調(diào)用StatelessWidget的build方法,傳入BuildContext為this赤屋。

因?yàn)閣idgets是immutable的立镶,包括節(jié)點(diǎn)之間的父/子關(guān)系壁袄,對(duì)widget樹(shù)的任何修改(比如,Text('A') to Text('B'))會(huì)導(dǎo)致一系列新的widget對(duì)象的被重建谜慌。但這并不意味下層必須被重建然想,element tree可能在界面刷新時(shí)是持久的(persistent),因此對(duì)性能起著關(guān)鍵作用欣范,因?yàn)镕lutter緩存了底層表示变泄,使它表現(xiàn)的可以像完全丟棄上層的widget層一樣。通過(guò)遍歷widgets的修改恼琼,可以做到只重新構(gòu)建一部分的element tree妨蛹。

Element到RenderObject

只繪制單個(gè)的widget的應(yīng)用是很少見(jiàn)的,所以晴竞,任何的UI框架的一個(gè)重要的部分就是能夠高效的布局一個(gè)層級(jí)結(jié)構(gòu)的widget蛙卤,確定它們的大小、位置然后繪制到屏幕上噩死。

渲染樹(shù)上的每個(gè)節(jié)點(diǎn)的基類(lèi)型是RenderObject颤难,在構(gòu)建階段,F(xiàn)lutter僅將element tree中的RenderObjectElement對(duì)象生成可渲染的對(duì)象已维,不同的Render對(duì)象渲染不同類(lèi)型行嗤,RenderParagraph渲染text酱畅,RenderImage 渲染image

image

Flutter中多數(shù)widgets的渲染對(duì)象是繼承自RenderBox的鹦倚,它使用了笛卡爾坐標(biāo)系在2D空間,它提供了一個(gè)盒子約束模型耸彪,限制了widget的最小和最大寬度和高度堂鲜。

layout期間栈雳,F(xiàn)lutter會(huì)以深度優(yōu)先遍歷渲染樹(shù),并將constraints約束傳遞給child缔莲,用來(lái)確定child的大小哥纫,然后將結(jié)果傳遞給parent的size變量。

/// 子類(lèi)不應(yīng)該直接重寫(xiě)[layout]方法痴奏,而應(yīng)該重寫(xiě)[performResize] and/or [performLayout]磺箕, [layout]方法
/// 代理它的工作放在 [performResize] and [performLayout]
/// parent's的[performLayout]方法應(yīng)該無(wú)條件的調(diào)用所有它的child的[layout]
void layout(Constraints constraints, { bool parentUsesSize = false }) {
   /// ...
    try {
      performLayout();
      markNeedsSemanticsUpdate();
      
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    /// ...
    _needsLayout = false;
    markNeedsPaint();
 }

/// 空實(shí)現(xiàn),由子類(lèi)重寫(xiě)
  @protected
    void performLayout();

舉例抛虫,看下RenderPadding的performLayout方法:

@override
  void performLayout() {
    /// 第一步,拿到constraints
    final BoxConstraints constraints = this.constraints;
    // ...
    /// 第二步简僧,根據(jù)parent的constraints建椰,計(jì)算自己內(nèi)部的constraints
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
    /// 第三步,繼續(xù)向下遍歷layout
    child!.layout(innerConstraints, parentUsesSize: true);
    final BoxParentData childParentData = child!.parentData! as BoxParentData;
    childParentData.offset = Offset(_resolvedPadding!.left, _resolvedPadding!.top);
    /// 第四步岛马,根據(jù)constraints生成size
    size = constraints.constrain(Size(
      _resolvedPadding!.left + child!.size.width + _resolvedPadding!.right,
      _resolvedPadding!.top + child!.size.height + _resolvedPadding!.bottom,
    ));
  }

這樣就完成了樹(shù)的深度遍歷過(guò)程

image

盒子約束模型是一種很強(qiáng)大的布局對(duì)象的方式棉姐,時(shí)間復(fù)雜度為O(n)

所有RenderObjects的根節(jié)點(diǎn)是RenderView屠列,它代表了整個(gè)渲染樹(shù)的輸出。當(dāng)平臺(tái)需要渲染新的幀時(shí)(例如伞矩,一個(gè)vsync信號(hào)觸發(fā)笛洛,或者texture的解壓/上傳完成)會(huì)調(diào)用RenderView對(duì)象中的compositeFrame()方法,它創(chuàng)建了一個(gè)SceneBuilder觸發(fā)屏幕的更新乃坤。當(dāng)更新完成時(shí)苛让,RenderView會(huì)傳遞這個(gè)壓縮的scene到dart:ui包中的Window.render()方法,該方法控制GPU將它渲染湿诊。

是一一對(duì)應(yīng)的關(guān)系嗎

從上面圖中可以輕松看出狱杰,并不是。

image.png

表中僅列出了常用Widget和對(duì)應(yīng)關(guān)系厅须,并不代表全部

所以說(shuō)widget和element和renderObject是一一對(duì)應(yīng)是有語(yǔ)境的仿畸,在展示型這一行的情況下是沒(méi)問(wèn)題的,但是在全局范圍這么說(shuō)朗和,是不準(zhǔn)確的错沽。

建立過(guò)程

上面粗略的看了三顆樹(shù)的轉(zhuǎn)化過(guò)程,那么在代碼層面眶拉,他們是如何經(jīng)過(guò)方法的調(diào)用串聯(lián)起來(lái)的呢千埃?可以主要分為兩個(gè)過(guò)程:

根view的attachRootWidget

初始化Widget樹(shù)Element樹(shù)和RenderObject樹(shù)的root節(jié)點(diǎn),分別是RenderObjectToWidgetAdapter镀层、RenderObjectToWidgetElement镰禾、RenderView。

然后在WidgetsBinding.attachRootWidget方法中唱逢,將runApp傳入的rootWidget添加到widget樹(shù)根RenderObjectToWidgetAdapter實(shí)例的child上吴侦,調(diào)用它的attachToRenderTree,將element關(guān)聯(lián)到RenderTree上坞古,調(diào)用了element的mount方法备韧。

/// Takes a widget and attaches it to the [renderViewElement], creating it if
  /// necessary.
  /// This is called by [runApp] to configure the widget tree.
  ///  * [RenderObjectToWidgetAdapter.attachToRenderTree], which inflates a
  ///    widget and attaches it to the render tree.
  void attachRootWidget(Widget rootWidget) {
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
    if (isBootstrapFrame) {
      SchedulerBinding.instance!.ensureVisualUpdate();
    }
  }

其中的renderView就是RenderObject tree上的根節(jié)點(diǎn),它是在RendererBinding類(lèi)中被初始化的

/// The glue between the render tree and the Flutter engine.
/// render tree 和 Flutter engine之間的膠水
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
     @override
  void initInstances() {
    super.initInstances();
    /// ...
    initRenderView();
   /// ...
  }
  
  void initRenderView() {
        /// ...
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }

}

attachToRenderTree方法

/// Used by [runApp] to bootstrap applications.
/// 供runApp使用來(lái)引導(dǎo)程序
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
    /// Used by [runApp] to bootstrap applications.
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [              RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }

    RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

}

這里element為空痪枫,所以創(chuàng)建了RenderObjectToWidgetElement的實(shí)例织堂,然后mount。

子view的attachToRenderTree

element的mount方法中奶陈,這里觸發(fā)了掛載element到Element tree易阳,判斷是包含渲染對(duì)象的RenderObjectElement就創(chuàng)建RenderObject,調(diào)用attachRenderObject掛載到RenderObject tree上吃粒。然后_rebuild→updateChild→inflateWidget→newWidget.createElement→newChild.mount(this, newSlot)觸發(fā)了樹(shù)的深度遍歷潦俺,時(shí)序圖如下(粗略)

時(shí)序圖

關(guān)鍵的一點(diǎn)是,newChild.mount方法會(huì)調(diào)用Element的子類(lèi)型主要是兩個(gè)SingleChildRenderObjectElement和MultiChildRenderObjectElement,名字起的很明顯事示,一個(gè)孩子或者多個(gè)孩子的Element早像。mount方法如下

class SingleChildRenderObjectElement extends RenderObjectElement {
    @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
}

class MultiChildRenderObjectElement extends RenderObjectElement {
    @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
    Element? previousChild;
    for (int i = 0; i < children.length; i += 1) {
      final Element newChild = inflateWidget(widget.children[i],        IndexedSlot<Element?>(i, previousChild));
      children[i] = newChild;
      previousChild = newChild;
    }
    _children = children;
  }
}

可見(jiàn)它們都做了兩件事:

  • 調(diào)用super.mount(),掛載element到Element tree肖爵,createRenderObject卢鹦,attachRenderObject,掛載_renderObject到RenderObject tree
  • updateChild劝堪,傳入widget.child冀自,繼續(xù)下一層級(jí)的widget樹(shù)的轉(zhuǎn)換,這里slot分別傳的為null幅聘,和IndexedSlot對(duì)象

如果Element節(jié)點(diǎn)是ComponentElement類(lèi)型凡纳,mount方法如下

abstract class ComponentElement extends Element {
    @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    /// ...
    _firstBuild();
    assert(_child != null);
  }
  
  /// 最終會(huì)調(diào)到performRebuild
  @override
  void performRebuild() {
    Widget? built;
    try {
      /// 我們經(jīng)常在代碼中重寫(xiě)的build()函數(shù),就是這里
      built = build();
    } catch (e, stack) {
      /// 構(gòu)建錯(cuò)誤頁(yè)面ErrorWidget帝蒿,我們看的到錯(cuò)誤紅色頁(yè)面
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } 
    /// 更新widget荐糜,繼續(xù)循環(huán)
    _child = updateChild(_child, built, slot);
     
  }
  /// 在StatelessWidget/StafulWidget中重寫(xiě)的方法
  @protected
  Widget build();
}

Slot對(duì)象

updateChild傳入的slot對(duì)象是干什么用的呢?一句話總結(jié)就是葛超,為了標(biāo)記RenderObject掛載到RenderObject tree上的位置暴氏。

首先,每一個(gè)Element都會(huì)最終包裹一個(gè)RenderObject绣张,最終掛載到RenderObject tree上答渔,不管是自身包裹,或者是它的子孫包裹侥涵。所以沼撕,當(dāng)Element的直接child不包含RenderObject時(shí),例如StatelessElement/StatefulElement芜飘,它就要標(biāo)記下一個(gè)RenderObject對(duì)象要掛載到RenderObject tree上的哪個(gè)節(jié)點(diǎn)务豺。所以,在它們的父類(lèi)ComponentElement的updateChild方法中傳的slot值就是要掛載的位置嗦明。比如這樣的element節(jié)點(diǎn)笼沥,會(huì)一直向下傳遞slot直到是RenderObjectElement節(jié)點(diǎn)。

image

那么這個(gè)值什么情況下會(huì)初始化并往下傳遞呢娶牌?SingleChildRenderObjectElement往下傳遞的是null奔浅,看來(lái)它并不需要插槽,看下attachRenderObject方法

@override
  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    /// 找到是RenderObjectElement對(duì)象的祖先節(jié)點(diǎn)
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    /// 根據(jù)newSlot插槽诗良,插入renderObject到渲染樹(shù)
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
  }

RenderObjectElement? _findAncestorRenderObjectElement() {
    Element? ancestor = _parent;
  /// 循環(huán)向上找到第一個(gè)RenderObjectElement的對(duì)象汹桦,其實(shí)就是為了找到RenderObject的父節(jié)點(diǎn)
    while (ancestor != null && ancestor is! RenderObjectElement)
      ancestor = ancestor._parent;
    return ancestor as RenderObjectElement?;
  }

所以單個(gè)孩子的SingleChildRenderObjectElement不需要slot,因?yàn)榭偰苷业?ancestor掛載點(diǎn)鉴裹。而MultiChildRenderObjectElement营勤,由于多個(gè)孩子都找到同一個(gè)ancestor節(jié)點(diǎn)灵嫌,所以就有了slot將兄弟節(jié)點(diǎn)按順序排列起來(lái),生成IndexedSlot<Element?>(i, previousChild)的slot葛作,這就有了初始的slot往下傳遞,所以slot是從MultiChildRenderObjectElement這樣的節(jié)點(diǎn)開(kāi)始分化的

這里排除了剛開(kāi)始建立渲染樹(shù)的根節(jié)點(diǎn)_rootChildSlot

image

這樣就完成了猖凛,Element tree赂蠢,和RenderObject tree的父子節(jié)點(diǎn)/兄弟節(jié)點(diǎn)之間的錯(cuò)落有致的樹(shù)型結(jié)構(gòu)。RenderObjectElement在整個(gè)過(guò)程中辨泳,占據(jù)核心的功能虱岂,同時(shí)負(fù)責(zé)控制widget向下更新,和RenderObject生成菠红,掛載到Render tree的正確節(jié)點(diǎn)上第岖。

總結(jié)

本篇為三棵樹(shù)理解的第一篇,重點(diǎn)分析了三棵樹(shù)的建立過(guò)程试溯,下一篇我們繼續(xù)分析三棵樹(shù)的刷新過(guò)程蔑滓,以及為什么要設(shè)計(jì)三棵樹(shù),以及理解了三棵樹(shù)的概念遇绞,對(duì)我們開(kāi)發(fā)中有哪些指導(dǎo)或者注意的點(diǎn)键袱。

文中難免有個(gè)人理解,有偏差的地方摹闽,請(qǐng)大家批評(píng)指正蹄咖,多謝!

參考

https://flutter.dev/docs/resources/architectural-overview

https://www.yuque.com/xytech/flutter/tge705

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末付鹿,一起剝皮案震驚了整個(gè)濱河市澜汤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舵匾,老刑警劉巖俊抵,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纽匙,居然都是意外死亡务蝠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)烛缔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)馏段,“玉大人,你說(shuō)我怎么就攤上這事践瓷≡合玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵晕翠,是天一觀的道長(zhǎng)喷舀。 經(jīng)常有香客問(wèn)我砍濒,道長(zhǎng),這世上最難降的妖魔是什么硫麻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任爸邢,我火速辦了婚禮,結(jié)果婚禮上拿愧,老公的妹妹穿的比我還像新娘杠河。我一直安慰自己,他們只是感情好浇辜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布券敌。 她就那樣靜靜地躺著,像睡著了一般柳洋。 火紅的嫁衣襯著肌膚如雪待诅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天熊镣,我揣著相機(jī)與錄音卑雁,去河邊找鬼。 笑死轧钓,一個(gè)胖子當(dāng)著我的面吹牛序厉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毕箍,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼弛房,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了而柑?” 一聲冷哼從身側(cè)響起文捶,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媒咳,沒(méi)想到半個(gè)月后粹排,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涩澡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年顽耳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妙同。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡射富,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粥帚,到底是詐尸還是另有隱情胰耗,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布芒涡,位于F島的核電站柴灯,受9級(jí)特大地震影響卖漫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赠群,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一羊始、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乎串,春花似錦店枣、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)闷旧。三九已至长豁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忙灼,已是汗流浹背匠襟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留该园,地道東北人酸舍。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像里初,于是被迫代替她去往敵國(guó)和親啃勉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356