Flutter狀態(tài)管理

在響應(yīng)式編程中塘秦,狀態(tài)即數(shù)據(jù)建蹄,狀態(tài)變化碌更,頁面即發(fā)生變化裕偿,F(xiàn)lutter作為響應(yīng)式開發(fā)框架,狀態(tài)管理是Flutter開發(fā)過程中代碼架構(gòu)的重點(diǎn)痛单,本文中嘿棘,我們將通過分析常用的Flutter狀態(tài)管理框架,給大家深入解析狀態(tài)管理的核心實(shí)現(xiàn)方法旭绒,方便大家在后續(xù)開發(fā)中鸟妙,挑選合適的狀態(tài)管理框架。

Flutter本身已經(jīng)給我們提供了一個(gè)狀態(tài)管理方式快压,即Flutter自帶的StatefulWidget圆仔,但是我們?cè)趹?yīng)用過程中垃瞧,會(huì)發(fā)現(xiàn)蔫劣,這個(gè)狀態(tài)僅僅適合在單個(gè)StatefulWidget中進(jìn)行維護(hù),當(dāng)我們需要一個(gè)跨組件狀態(tài)時(shí)个从,StatefulWidget將不再是一個(gè)好的選擇脉幢,雖然我們可以使用callBack進(jìn)行解決,但這個(gè)方式一旦業(yè)務(wù)增長到一定程度嗦锐,嵌套較深的時(shí)候嫌松,將會(huì)造成很大的代碼耦合,因此奕污,我們需要使用一個(gè)狀態(tài)管理組件進(jìn)行維護(hù)這些狀態(tài)萎羔。

常用的狀態(tài)管理組件,包含了ScopedModel碳默,BLoC贾陷,RxDart,Provider等嘱根,今天我們將在這里對(duì)這些狀態(tài)管理機(jī)制的使用方法及原理進(jìn)行剖析髓废。

一、 狀態(tài)管理分類:

首先该抒,常用的狀態(tài)管理慌洪,按照范圍可以劃分為局部狀態(tài)管理和全局狀態(tài)管理:

局部狀態(tài):

Flutter提供了類似StatefulWidget、InheritWidget組件來實(shí)現(xiàn)局部狀態(tài)管理凑保,當(dāng)這些Widget發(fā)生變化時(shí)冈爹,所有子樹中依賴其數(shù)據(jù)的widget都會(huì)進(jìn)行rebuild。

全局狀態(tài):

Flutter沒有提供原生的全局狀態(tài)管理機(jī)制欧引,雖然可以在根布局控件使用InheritWidget來實(shí)現(xiàn)全局狀態(tài)管理频伤,但是這樣會(huì)存在類似依賴傳遞過深等問題。因此大多數(shù)情況下维咸,需要依賴一些第三方庫實(shí)現(xiàn)全局狀態(tài)管理

最簡單的狀態(tài)管理

我們可以使用 State + InheritedWidget實(shí)現(xiàn)最簡單的狀態(tài)管理機(jī)制剂买。

二惠爽、 狀態(tài)管理——Stream

Stream在Flutter中標(biāo)志著的事件流或者管道一類的概念,通過Stream可以快速的實(shí)現(xiàn)給予事件流驅(qū)動(dòng)的業(yè)務(wù)邏輯瞬哼。界面通過訂閱事件婚肆,并針對(duì)各個(gè)事件進(jìn)行變化處理,實(shí)現(xiàn)響應(yīng)式更新界面坐慰。

/// 展示文本
String textString = "等待接收文本";

/// 單訂閱Stream
Stream<String> stream = new Stream.fromFuture(_doFutureTask());

@override
void initState() {
StreamSubscription subscription = stream.listen((data) {
  textString = data;
  print("接收數(shù)據(jù)成功");
}, onDone: () {
  print("流處理完成");
}, onError: () {
  print("流處理出現(xiàn)異常");
});

/// Subscription_API
subscription.cancel();
subscription.pause();
subscription.resume();
}

/// 異步任務(wù)
static _doFutureTask() {
    return Future.delayed(Duration(seconds: 5), () {
    print('Hello World');
    });
}

這是一個(gè)最簡單的较性,通過異步任務(wù)創(chuàng)建流對(duì)象的方法,包含了流的創(chuàng)建结胀、監(jiān)聽赞咙、管理,Stream流糟港,分為單訂閱流和廣播流

單訂閱流

單訂閱流只允許在該Stream的整個(gè)生命周期攀操,注冊(cè)一個(gè)監(jiān)聽器,即使第一個(gè)監(jiān)聽器被取消了秸抚,也無法在這個(gè)時(shí)間流中速和,監(jiān)聽到第二次事件。

// 初始化
StreamController<String> singleStream = StreamController();

// 消息發(fā)送
singleStream.add('Hello World');

使用過程中剥汤,經(jīng)常會(huì)在log平臺(tái)輸出: Bad state: Stream has already been listened to.
其含義就是指:單訂閱流不能有多個(gè)收聽者

廣播流

廣播流颠放,和單訂閱流不同,允許任意個(gè)數(shù)的監(jiān)聽者吭敢,可以隨時(shí)隨地為其添加監(jiān)聽器碰凶,只要新的監(jiān)聽器,被添加進(jìn)去鹿驼,就可以收到新的事件

// 初始化
StreamController<int> singleStream = StreamController.broadcast();

// 消息發(fā)送
singleStream.sink.add(6);

StreamController的構(gòu)造函數(shù)是一個(gè)泛型欲低,意味著StreamController可以往流上推送任意類型的數(shù)據(jù),當(dāng)然這里需要考慮接收時(shí)候的數(shù)據(jù)類型處理蠢沿。

StreamBuilder如何實(shí)現(xiàn)刷新

在上邊的例子中伸头,我們看到了在頁面中有使用一個(gè)StreamBuilder,來構(gòu)建一個(gè)UI展示:

StreamBuilder<String>(
        builder: (context, snapshot) {
          if (snapshot == null || !snapshot.hasData) {
            return CircularProgressIndicator();
          } else {
            if (snapshot.hasError) {
              return Text("發(fā)生錯(cuò)誤");
            } else {
              return Text(snapshot.data);
            }
          }
        },
        stream: singleStream.stream,
      )

那么StreamBuilder和Widget的刷新舷蟀,是怎么關(guān)聯(lián)起來的呢恤磷?通過閱讀代碼,我們發(fā)現(xiàn)野宜,其實(shí)StreamBuilder的主要邏輯在_StreamBuilderBaseState中扫步,_StreamBuilderBaseState在initState、didUpdateWidget中會(huì)調(diào)用_subscribe方法匈子,從而調(diào)用Stream的listen河胎,然后通過setState更新UI。

void _subscribe() {
if (widget.stream != null) {
  _subscription = widget.stream.listen((T data) {
    setState(() {
      _summary = widget.afterData(_summary, data);
    });
  }, onError: (Object error) {
    setState(() {
      _summary = widget.afterError(_summary, error);
    });
  }, onDone: () {
    setState(() {
      _summary = widget.afterDone(_summary);
    });
  });
  _summary = widget.afterConnected(_summary);
}

}

而setState的刷新機(jī)制虎敦,其實(shí)我們大家應(yīng)該都知道游岳,實(shí)質(zhì)上是調(diào)用了markNeedsBuild政敢,markNeedsBuild方法會(huì)標(biāo)記element為dirty,這樣在下一幀WidgetsBinding.drawFrame的時(shí)候胚迫,會(huì)進(jìn)行繪制

StreamController整體架構(gòu)

從上邊幾個(gè)demo中喷户,我們看到Flutter的Stream流中,存在Stream访锻、StreamController褪尝、Sink、以及StreamSubscription這樣四個(gè)比較關(guān)鍵的組件期犬,那么這四個(gè)組件是以一個(gè)什么樣子的形式互相結(jié)合起來的呢河哑。我們通過下邊這樣一副圖,進(jìn)行說明:

image

整個(gè)流程龟虎,概括起來就是:StreamController作為一個(gè)統(tǒng)籌管理的“Boss”璃谨,主要負(fù)責(zé)協(xié)調(diào)和維護(hù)整個(gè)事件流的輸入和輸出,StreamController暴露了一個(gè)Sink屬性遣总,主要負(fù)責(zé)事件流的輸入睬罗,在這里輸入事件轨功。暴露一個(gè)Stream屬性旭斥,主要負(fù)責(zé)流事件的輸出,除自身提供了事件轉(zhuǎn)換方法古涧,例如where垂券、take等,主要進(jìn)行事件流的轉(zhuǎn)換羡滑。同時(shí)菇爪,Stream對(duì)外提供了事件的監(jiān)聽,分別可以處理在收到事件以后的處理onData柒昏,事件處理完成以后的onDone以及事件處理異常的onError等方法凳宙,通過注冊(cè)這樣的監(jiān)聽,我們又可以得到StreamSubscription這個(gè)屬性职祷,其功能氏涩,主要管理事件的訂閱,包含取消有梆、暫停是尖、恢復(fù)等操作

StreamController同步、異步處理

在初始化StreamController的時(shí)候泥耀,我們可以看到饺汹,有一個(gè)構(gòu)造參數(shù),sync痰催,針對(duì)我們傳遞的sync值兜辞,決定使用同步流還是異步流迎瞧。

return sync
    ? new _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
    : new _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);

這里具體怎么實(shí)現(xiàn)的呢,我們一起來看一下逸吵。在同步流中夹攒,直接調(diào)用了subscription的_add方法,直接將數(shù)據(jù)添加進(jìn)事件回掉監(jiān)聽中胁塞,實(shí)現(xiàn)同步:

void _sendData(T data) {
    if (_isEmpty) return;
    if (_hasOneListener) {
          _state |= _BroadcastStreamController._STATE_FIRING;
          _BroadcastSubscription<T> subscription = _firstSubscription;
          subscription._add(data);
          _state &= ~_BroadcastStreamController._STATE_FIRING;
          if (_isEmpty) {
            _callOnCancel();
          }
          return;       
    }
    _forEachListener((_BufferingStreamSubscription<T> subscription) {
           subscription._add(data);
    });
}

而在異步流中咏尝,則是使用_addPending方法,添加了一個(gè)繼承自_DelayedEvent的方法啸罢,實(shí)現(xiàn)異步:

abstract class _AsyncStreamControllerDispatch<T>
implements _StreamController<T> {
    void _sendData(T data) {
        _subscription._addPending(new _DelayedData<T>(data));
    }

    void _sendError(Object error, StackTrace stackTrace) {
        _subscription._addPending(new _DelayedError(error, stackTrace));
    }

    void _sendDone() {
        _subscription._addPending(const _DelayedDone());
    }
}

至此编检,我們已經(jīng)基本了解了Stream的原理及一些常用的api,這方便我們對(duì)后續(xù)的RxDart扰才、Provider等進(jìn)行分析

三允懂、狀態(tài)管理——RxDart

說到RxDart就不得不提一下ReactiveX,http://reactivex.io/

在其官網(wǎng)上衩匣,對(duì)ReactiveX的介紹為

“An API for asynchronous programming with observable streams”

用于可觀察流的異步編程的API蕾总,一句話概括了ReactiveX的核心設(shè)計(jì)思想,為各個(gè)平臺(tái)提供了異步編程的可觀察流API。當(dāng)然在Dart上也不例外愤兵。

/// 創(chuàng)建一個(gè)Subject
var subject = PublishSubject<String>();

/// 通過listen實(shí)現(xiàn)訂閱
subject.listen((String data) {
  print("OnData " + data);
}, onError: () {
  print("onError ");
}, onDone: () {
  print("onDone ");
});

/// 使用完成飞蹂,關(guān)閉
subject.close();

這里是一個(gè)最簡單的RxDart的使用方法,細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn)了蚀浆,這不就是之前的StreamController么。的確是這樣的搜吧,RxDart內(nèi)部市俊,其實(shí)也是對(duì)Stream的一個(gè)封裝。PublishSubject的內(nèi)部實(shí)現(xiàn)滤奈,其實(shí)也是一個(gè)廣播類型的StreamController:

factory PublishSubject({void onListen(), void onCancel(), bool sync = false}) {
    // ignore: close_sinks
    final controller = StreamController<T>.broadcast(
      onListen: onListen,
      onCancel: onCancel,
      sync: sync,
    );

    return PublishSubject<T>._(
      controller,
      Observable<T>(controller.stream),
    );
}

至此我們也明白了:

  1. 在RxDart中Subject摆昧,無論是PublishSubject還是BehaviorSubject(只保留最后一個(gè)值的特殊流),其核心其實(shí)還是StreamController

  2. RxDart中的Observable實(shí)質(zhì)上相當(dāng)于一個(gè)Stream

總結(jié)一下蜒程,RxDart绅你,實(shí)際是對(duì)上邊所說的Stream進(jìn)行了概念轉(zhuǎn)換,變成了ReactiveX用戶熟悉的對(duì)象和操作符搞糕,本質(zhì)上還是一個(gè)Stream勇吊,這也是為什么可以直接在StreamBuilder中使用RxDart。

四窍仰、狀態(tài)管理——Provider

Provider是Flutter官方推薦的狀態(tài)管理方式之一汉规,它的特點(diǎn)是,不復(fù)雜,好理解针史,可控度較高晶伦。

Provider使用

第一步 添加依賴

provider: ^2.0.1+1

第二步 創(chuàng)建數(shù)據(jù)模型

/// 計(jì)數(shù)module
class CounterModel extends ChangeNotifier {
  /// 計(jì)數(shù)
  int _count = 0;
  int get value => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

該Module繼承自ChangeNotifier,主要負(fù)責(zé)數(shù)據(jù)模型保存和管理啄枕,同時(shí)暴露出來的notifyListeners()方法婚陪,在調(diào)用后,可以自動(dòng)更新其所有的監(jiān)聽者频祝。

第三步 創(chuàng)建全局共享數(shù)據(jù)依賴

void main() {
  /// 數(shù)據(jù)模型
  final counter = CounterModel();
  
  runApp(
    ChangeNotifierProvider.value(
      notifier: counter,
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: FirstScreen(),
    );
  }
}

在這里我們使用ChangeNotifierProvider泌参,并設(shè)置其value為之前創(chuàng)建的數(shù)據(jù)模型。ChangeNotifierProvider<T>.value類型的數(shù)據(jù)常空,不僅可以將數(shù)據(jù)共享給其所有的子節(jié)點(diǎn)進(jìn)行使用沽一,同時(shí)還可以在數(shù)據(jù)發(fā)生變化時(shí),通過調(diào)用之前數(shù)據(jù)模型中的notifyListeners()方法進(jìn)行刷新漓糙。

第四步 在其他頁面中獲取共享的狀態(tài)

這里我們通過兩個(gè)頁面铣缠,相互之間共同持有一份數(shù)據(jù)的例子,來看看Provider是怎么處理兩個(gè)頁面之間數(shù)據(jù)的共享的昆禽,首先我們創(chuàng)建第一個(gè)頁面

/// Provider 頁面
class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Provider " + "FirstScreen build");
 
    final _counter = Provider.of<CounterModel>(context);

    return Scaffold(
      appBar: AppBar(title: Text('FirstPage')),
      body: Center(
          child: GestureDetector(
              onTap: () => {_counter.increment()},
              child: Text(
                'Value: ${_counter.value}',
                style: TextStyle(fontSize: 48),
              ))),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Navigator.of(context)
            .push(MaterialPageRoute(builder: (context) => SecondPage())),
        child: Icon(Icons.navigate_next),
      ),
    );
  }
}

在這個(gè)頁面中蝗蛙,我們使用Provider.of<T>(context)方法來向上尋找最近存儲(chǔ)了T的祖先節(jié)點(diǎn)數(shù)據(jù)。我們這里獲取到了存儲(chǔ)的CounterModel醉鳖,并對(duì)其屬性value進(jìn)行展示捡硅。然后在點(diǎn)擊文本的時(shí)候,自增辐棒,希望在第二個(gè)頁面中病曾,可以拿到并展示。

/// Provider 頁面
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext buildContext) {
    print("Provider " + "SecondPage build");
    return Scaffold(
        appBar: AppBar(
          title: Text('Second Page'),
        ),
        body: Consumer<CounterModel>(
          builder: (context, CounterModel counter, _) => Center(
              child: GestureDetector(
                  onTap: () => {counter.increment()},
                  child: Text(
                    'Value: ${counter.value}',
                    style: TextStyle(
                      fontSize: 48,
                    ),
                  ))),
        ));
  }
}

在第二個(gè)頁面中漾根,我們并沒有像在第一個(gè)頁面中一樣,使用Provider.of<T>(context)方式去獲取Provider中共享的數(shù)據(jù)鲫竞,而是使用了Consumer這個(gè)方式去獲取辐怕,這兩個(gè)有什么不一樣呢,先看完演示結(jié)果从绘,然后繼續(xù)往下分析寄疏。

Consumer

在上邊的例子中,我們使用了Consumer獲取Provider中共享的數(shù)據(jù)模型僵井,Consumer使用了Builder模式陕截,收到更新通知就會(huì)通過builder重新構(gòu)建。Consumer<T>代表了它要獲取哪一個(gè)祖先中的Model批什。

final Widget Function(BuildContext context, T value, Widget child) builder;

從Consumer的構(gòu)造方法中农曲,我們可以看到,其builder實(shí)際上就是一個(gè)Funcation,它接受三個(gè)參數(shù)乳规,用于構(gòu)建自身形葬。同樣原理的還有Consumer2,和Consumer類似暮的,只是入?yún)⒌姆盒腕弦裕兂闪藘蓚€(gè):

final Widget Function(BuildContext context, A value, B value2, Widget child) builder;

其實(shí)在源碼中,這里最多可以到6個(gè)冻辩,大家在使用的過程中猖腕,可以根據(jù)自己需要進(jìn)行選取。

那么為什么需要區(qū)分這兩個(gè)獲取數(shù)據(jù)類型的方法呢恨闪,我們?cè)谏鲜鰞蓚€(gè)頁面的build方法中谈息,分別添加了log,進(jìn)行日志打印凛剥,操作步驟為:

1. 進(jìn)入第一個(gè)頁面侠仇,點(diǎn)擊兩次Value,使數(shù)據(jù)自增
2. 點(diǎn)擊進(jìn)入下一個(gè)頁面
3. 在第二個(gè)頁面犁珠,同樣點(diǎn)擊兩次Value逻炊,使數(shù)據(jù)自增
4. 返回第一個(gè)頁面,點(diǎn)擊一次Value犁享,自增

我們可以看到Log打印如下:

2020-12-12 18:30:26.873 4273-6507/jd.com.state.flutter_state_manager I/flutter: Provider FirstScreen build
2020-12-12 18:30:27.777 4273-6507/jd.com.state.flutter_state_manager I/flutter: Provider FirstScreen build
2020-12-12 18:30:29.290 4273-6507/jd.com.state.flutter_state_manager I/flutter: Provider SecondPage build
2020-12-12 18:30:30.203 4273-6507/jd.com.state.flutter_state_manager I/flutter: Provider FirstScreen build
2020-12-12 18:30:31.726 4273-6507/jd.com.state.flutter_state_manager I/flutter: Provider FirstScreen build
2020-12-12 18:30:35.223 4273-6507/jd.com.state.flutter_state_manager I/flutter: Provider FirstScreen build

可以看到使用Provider.of<T>(context)的第一個(gè)頁面余素,在每一次點(diǎn)擊按鈕的時(shí)候,都會(huì)重新build炊昆,而頁面二桨吊,則沒有。也就是說凤巨,使用Consumer進(jìn)行數(shù)據(jù)共享的時(shí)候视乐,僅僅只更新自身的Widget。那么為什么Consumer可以做到局部更新呢敢茁,我們來看一下Consumer的內(nèi)部構(gòu)造:

@override
  Widget build(BuildContext context) {
    return builder(
      context,
      Provider.of<T>(context),
      child,
    );
  }

也就是在Consumer中佑淀,實(shí)際上,也是通過使用Provider.of<T>(context),來實(shí)現(xiàn)的彰檬。那么問題就來了伸刃,同樣是Provider.of<T>(context)實(shí)現(xiàn)的,為什么Consumer就可以實(shí)現(xiàn)局部刷新呢逢倍?

這里我們可以看一下在第二個(gè)頁面中捧颅,我們?cè)跇?gòu)建Consumer的時(shí)候,傳遞的context较雕,并不是Widget中build方法提供的buildContext碉哑,而是使用了自己的context,所以在刷新的時(shí)候,可以做到局部刷新谭梗,這樣就方便我們?cè)贔lutter開發(fā)的時(shí)候忘晤,使用局部刷新進(jìn)行頁面性能優(yōu)化。

image

至此激捏,我們已經(jīng)基本上了解到了Provider的基礎(chǔ)用法设塔,當(dāng)然Provider還提供了核心的dispose方法,方便用戶進(jìn)行回收远舅,這里因?yàn)檫€沒有徹底搞清楚闰蛔,所以暫不進(jìn)行介紹。

Provider還有更多图柏,更詳盡的用法序六,比如 ValueListenableProvider、FutureProvider蚤吹、StreamProvider等多種Provider例诀,可見整個(gè)Provider的設(shè)計(jì)上更貼近Flutter的原生特性,同時(shí)設(shè)計(jì)也更好理解裁着,并且兼顧了性能等問題繁涂。這些后邊在使用到的時(shí)候,我們?cè)谶M(jìn)行逐步分析和解析

五二驰、 總結(jié)

總結(jié)上述所有的狀態(tài)管理機(jī)制扔罪,無論是Flutter原生提供的Stream,還是ReactiveX提供的RxDart桶雀,亦或是Provider矿酵,以及沒有在文章中出現(xiàn)的scoped_model、阿里開源的fish_redux矗积,這一系列的組件全肮,都為我們提供了一個(gè)很好的狀態(tài)管理機(jī)制,而我們?cè)谑褂眠^程中漠魏,大可通過自身業(yè)務(wù)需求倔矾,按需選型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柱锹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丰包,更是在濱河造成了極大的恐慌禁熏,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邑彪,死亡現(xiàn)場(chǎng)離奇詭異瞧毙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門宙彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矩动,“玉大人,你說我怎么就攤上這事释漆”唬” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵男图,是天一觀的道長示姿。 經(jīng)常有香客問我,道長逊笆,這世上最難降的妖魔是什么栈戳? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮难裆,結(jié)果婚禮上子檀,老公的妹妹穿的比我還像新娘。我一直安慰自己乃戈,他們只是感情好褂痰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偏化,像睡著了一般脐恩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侦讨,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天驶冒,我揣著相機(jī)與錄音,去河邊找鬼韵卤。 笑死骗污,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沈条。 我是一名探鬼主播需忿,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蜡歹!你這毒婦竟也來了屋厘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤月而,失蹤者是張志新(化名)和其女友劉穎汗洒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體父款,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溢谤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年瞻凤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片世杀。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阀参,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞻坝,到底是詐尸還是另有隱情蛛壳,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布湿镀,位于F島的核電站炕吸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏勉痴。R本人自食惡果不足惜赫模,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒸矛。 院中可真熱鬧瀑罗,春花似錦、人聲如沸雏掠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乡话。三九已至摧玫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绑青,已是汗流浹背诬像。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闸婴,地道東北人坏挠。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像邪乍,于是被迫代替她去往敵國和親降狠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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