Flutter框架啟動源碼剖析

入口函數(shù)担败,其主要作用是注入給定的小控件并將其附加到屏幕上揽浙。

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

1.初始化一個widgetsBinding的全局單例
2.創(chuàng)建跟widget并添加到renderView上鼠渺,在這個過程中完成Element樹和RenderObject樹的生成
3.執(zhí)行渲染

WidgetsBinding 初始化

 static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }

這里通過子類widgetsFlutterBinding實(shí)例化了一個widgetsBinding對象遵岩。但是這里widgetsFlutterBinding沒有顯示聲明構(gòu)造方法你辣,因此我們查看它的父類構(gòu)造方法實(shí)現(xiàn)

abstract class BindingBase {
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');
    initInstances();
    initServiceExtensions();
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    developer.Timeline.finishSync();
  }

可以看到,這里主要調(diào)用了initInstances()做了一些初始化操作,但是基類BindingBase自己的initInstances()是一個空的實(shí)現(xiàn)绢记,因此要查看其他父類中的實(shí)現(xiàn)扁达。這里我們需要注意到widgetsFlutterBinding類的繼承情況

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

因此,這里initInstances()方法會調(diào)用最外側(cè)的widgetsBinding中的具體實(shí)現(xiàn)蠢熄。檢查代碼可知跪解,混入的每一個類中都實(shí)現(xiàn)了initInstances方法,并且還調(diào)用了super.initInstances(),這樣一來签孔,就會從最外側(cè)的widgetsBinding開始叉讥,依次鏈?zhǔn)秸{(diào)用每一個混入類中的initInstances()方法,完成各個Binding的初始化饥追。

接下來我們先進(jìn)入widgetsBinding中的initInstances實(shí)現(xiàn)图仓,邏輯不多,主要是創(chuàng)建了BuildOwner對象但绕,并給window設(shè)置了一些回調(diào)函數(shù)

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;

    assert(() {
      _debugAddStackFilters();
      return true;
    }());

    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }

再看看RendererBinding中的initInstances實(shí)現(xiàn)救崔,這里創(chuàng)建了PipelineOwner對象,也給window設(shè)置了另一些回調(diào)函數(shù)

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    // 創(chuàng)建一個根RenderObject
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    // 注冊presistentCallbacks回調(diào)
addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      addPostFrameCallback(_handleWebFirstFrame);
    }
  }

這里我們可以查看一些跟RenderObject的創(chuàng)建

void initRenderView() {
    assert(!_debugIsRenderViewInitialized);
    assert(() {
      _debugIsRenderViewInitialized = true;
      return true;
    }());
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }

這里其他Binding的初始化先略過捏顺,我們查看一下出現(xiàn)多次的window是什么東西六孵。根據(jù)官方的解釋,window是Flutter框架鏈接宿主操作系統(tǒng)的接口

我們可以發(fā)下幅骄,混入的那些Binding基本上都是監(jiān)聽并處理window對象的一些事件劫窒,然后將這些事件根據(jù)Flutter框架層的模型進(jìn)行包裝、抽象最后分發(fā)拆座。

查看官方文檔可知主巍,widgetsFlutterBinding是將Framework與Flutter引擎榜單的膠水。BindingBase相當(dāng)于所以Binding的基類挪凑,定義了一些公共的行為孕索。

*GestureBinding:榜單Framework手勢子系統(tǒng),是Framework事件模型與底層事件的綁定入口
*ServiceBinding:用于綁定平臺消息通道(message channel)岖赋,主要處理原生和Flutter通信
*SchedulerBinding:監(jiān)聽刷新事件檬果,綁定Framework繪制調(diào)度子系統(tǒng)
*PaintingBinding:綁定繪制庫,主要用于處理圖片緩存
*SemanticsBinding:語義化層與Flutter引擎的橋梁唐断,主要是輔助功能的底層這次
*RendererBinding:是渲染樹與Flutter引擎的橋梁
*WidgetsBinding:它是Flutter Widget層與引擎的橋梁

構(gòu)建Element和RenderObject樹

再看runApp下方法中scheduleAttachRootWidget方法选脊,它實(shí)際上調(diào)用了如下方法,它完成了Widget脸甘、RenderObjectElement三者的關(guān)聯(lián)恳啥。具體代碼在attachToRenderTree方法中

void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
  }

BuildOwner是widget框架的管理器類,該類跟著哪些widgets需要重建丹诀,并處理其他使用于widgets樹的任務(wù)钝的,如管理樹的非活動元素列表翁垂,并在調(diào)試時的熱重載期間在必要時觸發(fā)“reassemble”命令。

  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);
      });
      // This is most likely the first time the framework is ready to produce
      // a frame. Ensure that we are asked for one.
      SchedulerBinding.instance!.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  } 

首先執(zhí)行 element 為null硝桩, 所以執(zhí)行creteElement方法創(chuàng)建Element沿猜,而真正執(zhí)行構(gòu)建樹的操作是owner.buildScope方法。這個方法首先執(zhí)行傳入的回調(diào)碗脊,即執(zhí)行element.mount(null,null)方法

 @override
  void mount(Element? parent, dynamic newSlot) {
    assert(parent == null);
    super.mount(parent, newSlot);
    _rebuild();
  }

mount 方法會首先調(diào)用父類的mount方法啼肩,即調(diào)用到RenderObjectElement類的 mount 方法如下,在此處構(gòu)建RenderObject對象衙伶,同時調(diào)用attachRenderObject方法生成RenderObject

 @override
  void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    // ...
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }

再看上面的_rebuild方法祈坠,其中主要調(diào)用了updateChild方法,updateChild方法的主要作用是用給定的新配置(widge)更新給定的子元素矢劲。這里可以關(guān)注catch中的代碼赦拘,正是此處社鞥從了Flutter中常見的紅屏報錯頁面的ErrorWidget

void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
      assert(_child != null);
    } catch (exception, stack) {
      final FlutterErrorDetails details = FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'widgets library',
        context: ErrorDescription('attaching to the render tree'),
      );
      FlutterError.reportError(details);
      final Widget error = ErrorWidget.builder(details);
      _child = updateChild(null, error, _rootChildSlot);
    }
  }

updateChilid 方法移除注釋與斷言后如下芬沉,newWidgetchild的值存在幾種不同的組合情況躺同, 當(dāng)首次進(jìn)入時,會執(zhí)行inflateWidget丸逸,為根Widget創(chuàng)建一個新的element笋籽。

@protected
  Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
            assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

inflateWidget方法刪除斷言如下,可以看到椭员,這里創(chuàng)建了newChild后又調(diào)用了mount方法,依次遞歸變了Widget樹的子節(jié)點(diǎn)笛园,不斷為Widget創(chuàng)建對應(yīng)的Element隘击、RenderObject, 以完成“三棵樹”的構(gòu)建與關(guān)聯(lián)研铆。

@protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
      final Element? newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        newChild._activateWithParent(this, newSlot);
        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
        return updatedChild!;
      }
    }
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }

執(zhí)行渲染

接下runApp 調(diào)用scheduleWarmUpFrame進(jìn)行了第一次繪制埋同,具體實(shí)現(xiàn)在SchedulerBinding中。 該方法會鎖定事件調(diào)度直到完成為止棵红,即在這次繪制完成之前都不會接收event(觸摸事件等)凶赁。
該方法中主要調(diào)用了handleBeginFrame()handleDrawFrame()

void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
// 在這里使用計時器來確保microtasks在兩者之間刷新 
    Timer.run(() {
      handleBeginFrame(null);
    });
    Timer.run(() {
      handleDrawFrame();
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    // 鎖定事件,使觸摸事件在等到預(yù)定幀結(jié)束前不會自行插入
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

其中handleBeginFrame方法用于渲染前的一些準(zhǔn)備逆甜,主要是處理transientCallbacks回調(diào)虱肄,也就是觸發(fā)動畫相關(guān)的Ticker回調(diào)。 而真正處理渲染的是handleDrawFrame方法交煞,它由引擎調(diào)用以生成新幀咏窿。

void handleDrawFrame() {
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // 處理persistentCallbacks回調(diào)
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);

      // 處理postFrameCallbacks回調(diào)
      _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;
    }
  }

根據(jù)官方文檔的解釋,有一下三個回調(diào)隊列

  • transientCallbacks:由系統(tǒng)的Window.onBenginFrame回調(diào)觸發(fā)素征,一般用于處理動畫的回調(diào)集嵌。
  • persistentCallbacks:由系統(tǒng)的Window.onDrawFrame回調(diào)觸發(fā)萝挤。用久callback,一經(jīng)注冊無法移除根欧,由widgetsBinding.instance.addPersitentFrameCallback()注冊怜珍,主要產(chǎn)后護(hù)理布局與繪制工作。
  • postFrameCallbacks:在persistentCallbacks回調(diào)之后凤粗,Window.onDrawFrame返回之前執(zhí)行酥泛。它只會調(diào)用一次,調(diào)用后就會被系統(tǒng)移除侈沪〗伊В可由widgetsBinding.Instance.addPostFrameCallback()注冊,通常用于State的更新亭罪。

可由看的瘦馍,這里的代碼邏輯主要就是處理persistentCallbackspostFrameCallbacks的回調(diào),而框架正好又在 RenderBinding 初始化時注冊了一個persistentCallbacks回調(diào)

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    /// ...
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    /// ....
  }

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


   void drawFrame() {
    assert(renderView != null);
    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;
    }
  }

以上代碼中需要特別注意应役,回調(diào)中并不是直接調(diào)用RenderBinding中的drawFrame()方法情组,而是依據(jù)widgetsFlutterBinding混入的順序調(diào)用。
根據(jù)Dart mixin 語法箩祥,混入多個類后院崇,調(diào)用同名方法時,是從最外層開始調(diào)用袍祖〉装辏可以看到,這里是調(diào)用的widgetsBinding.drawFrame()

/// [WidgetsBinding]
/// 抽取構(gòu)建和渲染管道以生成一幀蕉陋。
/// 這個方法被handleDrawFrame調(diào)用捐凭,當(dāng)需要布局和繪制一幀時,引擎會自動調(diào)用這個方法
  void drawFrame() {
    TimingsCallback? firstFrameCallback;
    if (_needToReportFirstFrame) {
      firstFrameCallback = (List<FrameTiming> timings) {
        if (!kReleaseMode) {
          developer.Timeline.instantSync('Rasterized first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        }
        SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
        firstFrameCallback = null;
        _firstFrameCompleter.complete();
      };
      // 只有再調(diào)用 [window.render] 時才會被調(diào)用
      // 當(dāng) [sendFramesToEngine] 在幀中被設(shè)置為false時凳鬓,它將不會被調(diào)用茁肠,我們需要刪除回調(diào)
      SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
    }

    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally {
     // assert
    }
    if (!kReleaseMode) {
      if (_needToReportFirstFrame && sendFramesToEngine) {
        developer.Timeline.instantSync('Widgets built first useful frame');
      }
    }
    _needToReportFirstFrame = false;
    if (firstFrameCallback != null && !sendFramesToEngine) {
      // 這個幀是延時的,并不是第一個發(fā)送給引擎的應(yīng)該報告的幀
      _needToReportFirstFrame = true;
      SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
    }
  }

實(shí)際上缩举,這里的主要邏輯是try中的代碼

try {
      if (renderViewElement != null)
        // 將被標(biāo)記為dirty的Element進(jìn)行rebuild()
        buildOwner!.buildScope(renderViewElement!);
        // 調(diào)用父類的drawFrame垦梆,這里實(shí)際上調(diào)用的是 RenderBinding中的drawFrame()方法
        super.drawFrame();
        // 通過卸載任何不再活動的元素來完成元素構(gòu)建過程
        buildOwner!.finalizeTree();
    } 

其中通過調(diào)用super.drawFrame()又再次回到RenderBinding中的drawFrame()方法

//更新需要計算布局的誼染對象。在這個階段仅孩,計算每個渲染對象的大小和位置托猩。
pipelineOwner.flushLayout();
//更新具有dirty compositing bits的所有渲染對象。在此階段辽慕, 每個渲染對象將了解其子對象是否需要合成站刑。
//
在選擇如何實(shí)現(xiàn)視覺效果(例如剪裁)時,在繪畫階段將使用此信息鼻百。
pipeline0wner.flushCompositingBits();
//訪問需要繪制的渲染對象進(jìn)行繪制
pipeline0wner.flushPaint();
//該方法將畫好的layer傳給引擎绞旅, 該方法調(diào)用結(jié)束后摆尝,屏幕就會顯示內(nèi)容
renderView.compositeFrame();
//如果啟用了語義因悲,則該方法將編譯渲染對象的語義,
傳給系統(tǒng)用于輔助功能晃琳,如搜索等。
pipelineOwner.flushSemantics();

小結(jié)

根據(jù)官方drawFrame 文檔的描述卫旱,繪制過程經(jīng)歷一下十個階段:
1、動畫階段:handleBeginFrame方法是用window.onBeginFrame注冊的顾翼,按注冊順序調(diào)用所有用scheduleFrameCallback注冊的transientCallbacks, 其中包括所有驅(qū)動AnimationController對象的Ticker實(shí)例,也就是說此時所有的活動Animation對象的tick在此刻回調(diào)适贸。
2、Microtask: 在handleBeginFrame放回后拜姿,任何被transientCallBacks安排的微任務(wù)都會被執(zhí)行。這通常包括自Ticker和AnimationController的完成該幀的Future回調(diào)蕊肥。
3试读、構(gòu)建階段:重建widget樹中所有的dirty元素(見 State.build),查閱State.setState了解更多關(guān)于標(biāo)記一個widget dirty的構(gòu)建細(xì)節(jié),有關(guān)此步驟的更新信息辖源,請參見BuildOwner。
4狼速、布局階段:系統(tǒng)中的所有標(biāo)記dirty的 RenderObject都會被布局。(參見 RenderObject.performLayout)向胡。 請參閱RenderObject.markNeedsLayout, 了解關(guān)于標(biāo)記一個對象以進(jìn)行布局更多的細(xì)節(jié)恼蓬。)
5、合成位階段:更新任何標(biāo)記dirty的RenderObject對象上的 compositing bits僵芹。(請參見RenderObject.markNeedsCompositingBitsUpdate)处硬。
6、繪制階段:系統(tǒng)中的所有標(biāo)記dirty的 RenderObject都會被重新繪制拇派,這將生產(chǎn)Layer樹荷辕。 (請參閱RenderObject.markNeedsPaint凿跳,以獲取有關(guān)將對象標(biāo)記為dirty的更新詳細(xì)信息)。
7疮方、合成階段:圖層將被轉(zhuǎn)化為一個Scene并發(fā)送給GPU控嗜。
8、語義階段:系統(tǒng)中所有標(biāo)記dirty的RenderObject的語義都會被更新(見 RenderObject.assembleSemanticsNode)骡显。這將生產(chǎn)SemanticsNode樹疆栏。請參閱RenderObject.markNeedsSemanticsUpdate以了解關(guān)于標(biāo)記一個對象為dirty的語義的細(xì)節(jié)。
9惫谤、widget層的完成階段:widget樹已完成壁顶,這將導(dǎo)致在從widget樹中刪除的任何對象上調(diào)用State.dispose。 請參見``BuildOwner.finalizeTree````
10溜歪、調(diào)度層的最終化階段:在 drawFrame返回后若专, handleDrawFrame會調(diào)用postFrameCallbacks回調(diào)(用 addPostFrameCallback注冊的)

總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痹愚,隨后出現(xiàn)的幾起案子富岳,更是在濱河造成了極大的恐慌,老刑警劉巖拯腮,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窖式,死亡現(xiàn)場離奇詭異,居然都是意外死亡动壤,警方通過查閱死者的電腦和手機(jī)萝喘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來琼懊,“玉大人阁簸,你說我怎么就攤上這事『哒桑” “怎么了启妹?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長醉旦。 經(jīng)常有香客問我车胡,道長匈棘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任鹃愤,我火速辦了婚禮昼浦,結(jié)果婚禮上关噪,老公的妹妹穿的比我還像新娘乌妙。我一直安慰自己藤韵,他們只是感情好泽艘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布天试。 她就那樣靜靜地躺著然低,像睡著了一般雳攘。 火紅的嫁衣襯著肌膚如雪吨灭。 梳的紋絲不亂的頭發(fā)上喧兄,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天繁莹,我揣著相機(jī)與錄音咨演,去河邊找鬼薄风。 笑死遭赂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茄猫。 我是一名探鬼主播划纽,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潭枣!你這毒婦竟也來了盆犁?” 一聲冷哼從身側(cè)響起蚣抗,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤翰铡,失蹤者是張志新(化名)和其女友劉穎锭魔,沒想到半個月后迷捧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漠秋,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庆锦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年艇搀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衷笋。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡辟宗,死狀恐怖慢蜓,靈堂內(nèi)的尸體忽然破棺而出郭膛,到底是詐尸還是另有隱情则剃,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布调煎,位于F島的核電站士袄,受9級特大地震影響娄柳,放射性物質(zhì)發(fā)生泄漏艘绍。R本人自食惡果不足惜诱鞠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一航夺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧始衅,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畸肆。三九已至宙址,卻和暖如春抡砂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碴巾。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工厦瓢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮仇,地道東北人欺抗。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓绞呈,卻偏偏與公主長得像间景,于是被迫代替她去往敵國和親倘要。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容