深入剖析flutter Getx框架狀態(tài)管理機制

楔子

最近寫Flutter項目的時候接觸了getx框架肥印,深深被這個它所吸引识椰,功能強大,api簡潔深碱,狀態(tài)管理機制也堪稱優(yōu)秀腹鹉,本篇就簡單分析一下getx是如果通過Obx和obs就能實現(xiàn)狀態(tài)管理的。

文有點長莹痢,你忍一下种蘸!

計數(shù)器Demo

首先,我們還是以一個計數(shù)器的demo作為引子竞膳,使用getx實現(xiàn)計數(shù)器功能如下:

void main() {
  runApp(MaterialApp(
    home: CounterDemoPage(),
  ));
}

class CounterDemoPage extends StatelessWidget {
  var counter = 0.obs;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("計數(shù)器Demo"),
      ),
      body: Center(
        child: Obx(() => Text("計數(shù)結(jié)果 = ${counter.value}")),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.value += 1;
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

在getx框架的支持下航瞭,CounterDemoPage不再需要繼承StatefulWidget和使用setState()方法而是通過以上代碼就能完成和官方提供Demo一樣的功能,不得不讓人贊嘆一句:有點東西坦辟!

那getx是如何通過如此簡單的操作就完成了狀態(tài)管理刊侯,接下來我們深入的剖析一下。

拓展函數(shù)obs

首先跟蹤代碼obs锉走,發(fā)現(xiàn)這是一個對int類型的拓展函數(shù)滨彻,在調(diào)用obs時會返回一個RxInt的對象(雖然是Rx開頭的藕届,但并不是RxDart框架)。同時getx對其他的一些基本數(shù)據(jù)類型和對象類型都有所拓展亭饵,具體請查看getx的官方文檔休偶。

extension IntExtension on int {
 RxInt get obs => RxInt(this);
}

class RxInt extends Rx<int> {
 RxInt(int initial) : super(initial);

 /// Addition operator.
 RxInt operator +(int other) {
 value = value + other;
 return this;
 }

 /// Subtraction operator.
 RxInt operator -(int other) {
 value = value - other;
 return this;
 }
}

RxInt類里面除了重寫了+和-的運算符外,好像并沒有什么值得關(guān)注的了辜羊,那就繼續(xù)追蹤父類踏兜。

class Rx<T> extends _RxImpl<T> {
 Rx(T initial) : super(initial);

 @override
 dynamic toJson() {
 /// 略去無關(guān)代碼
 }
}

Rx 里面只實現(xiàn)了一個toJson的方法,繼續(xù)網(wǎng)上追溯_RxImpl

abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
 _RxImpl(T initial) {
 _value = initial;
 }

 void addError(Object error, [StackTrace? stackTrace]) {
 ///略...
 }

 Stream<R> map<R>(R mapper(T? data)) => stream.map(mapper);

 void update(void fn(T? val)) {
 ///略...
 }

 void trigger(T v) {
 ///略...
 }
}

_RxImpl中實現(xiàn)了四個方法八秃,addError是發(fā)送錯誤通知碱妆,updatetrigger是更新數(shù)據(jù)相關(guān)方法,map是對Stream類中map方法的封裝昔驱,貌似都不是我們要找的疹尾。不過該類繼承了RxNotifier(重要)并混入了RxObjectMixin,由于RxNotifier過于重要骤肛,我們先看混入的RxObjectMixin

mixin RxObjectMixin<T> on NotifyManager<T> {
 late T _value;

 /// 刷新數(shù)據(jù)
 void refresh() {
 subject.add(value);
 }

 ///調(diào)用set value并返回get value值
 T call([T? v]) {
 if (v != null) {
 value = v;
 }
 return value;
 }

 /// 是否第一次創(chuàng)建
 bool firstRebuild = true;

 ///...略去無關(guān)代碼

 /// 更新數(shù)據(jù)
 set value(T val) {
 /// 略...
 }

 /// 獲取_value的值
 T get value {
 /// #預(yù)留問題1 這個地方是個重點纳本!這個地方是個重點!萌衬!這個地方是個重點R肌!秕豫!重要的事情說三遍
 RxInterface.proxy?.addListener(subject);
 return _value;
 }

 /// 通過subject拿到strema
 Stream<T?> get stream => subject.stream;

 ///下面兩行為getx類庫注釋的翻譯朴艰,我并沒有找到這個方法調(diào)用的位置,應(yīng)該是留給開發(fā)者調(diào)用的混移。
 /// 將現(xiàn)有的Stream<T>綁定到此 Rx<T> 以保持值同步祠墅。 (注釋翻譯)
 /// 您可以綁定多個源來更新值。 當(dāng)觀察者小部件( GetX或Obx )從小部件樹中卸載時歌径,將自動關(guān)閉訂閱(注釋翻譯)
 void bindStream(Stream<T> stream) {
 final listSubscriptions =
 _subscriptions[subject] ??= <StreamSubscription>[];
 listSubscriptions.add(stream.listen((va) => value = va));
 }
}

可以看出RxObjectMixin中基本上都是關(guān)于更新數(shù)據(jù)的方法毁嗦,但是數(shù)據(jù)更新后怎么通知到視圖,這里也并沒有提現(xiàn)出來回铛,但是這個類實現(xiàn)了NotifyManager類狗准,我們先不看NotifyManager而是回到_RxImpl類,看它繼承的父類茵肃。

class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;

_RxImpl的父類RxNotifier繼承自RxInterface并和RxObjectMixin一樣混入和NotifyManager腔长。

先來看一下RxInterface:

abstract class RxInterface<T> {
 ///看名字也知道,是個判斷是否可以更新的方法
 bool get canUpdate;

 void addListener(GetStream<T> rxGetx);

 void close();

 static RxInterface? proxy;

 StreamSubscription<T> listen(void Function(T event) onData,
 {Function? onError, void Function()? onDone, bool? cancelOnError});

 /// Avoids an unsafe usage of the `proxy`
 static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
 /// #預(yù)留問題2 下面會介紹到該方法

 }
}

RxInterface中有四個未實現(xiàn)的方法验残,分別是get方法canUpdate捞附、addListenercloselisten,但是都沒有具體實現(xiàn)鸟召。而靜態(tài)方法notifyChildren在前面的追蹤中也沒有發(fā)現(xiàn)在哪個地方有調(diào)用胆绊,所以暫時不用管它,靜態(tài)變量proxy倒是在上面標(biāo)記重點的地方出現(xiàn)了欧募,不過既然是重點压状,要肯定要留到最后再說。

RxNotifier繼承了RxInterface同時也繼承了四個為實現(xiàn)的方法槽片,這四個方法在混入的NotifyManager中得到了實現(xiàn):

mixin NotifyManager<T> {
 GetStream<T> subject = GetStream<T>();
 final _subscriptions = <GetStream, List<StreamSubscription>>{};

 ///根據(jù)_subscriptions是否為空判斷是否可以更新
 bool get canUpdate => _subscriptions.isNotEmpty;

 /// #預(yù)留問題3 這個放到后面介紹
 void addListener(GetStream<T> rxGetx) {
 if (!_subscriptions.containsKey(rxGetx)) {
 final subs = rxGetx.listen((data) {
 if (!subject.isClosed) subject.add(data);
 });
 final listSubscriptions =
 _subscriptions[rxGetx] ??= <StreamSubscription>[];
 listSubscriptions.add(subs);
 }
 }


 /// #預(yù)留問題4 看起來像是監(jiān)聽數(shù)據(jù)的更新何缓,但是在前面的分析中肢础,并沒有發(fā)現(xiàn)調(diào)用的地方还栓,那我們就先放著吧
 StreamSubscription<T> listen(
 void Function(T) onData, {
 Function? onError,
 void Function()? onDone,
 bool? cancelOnError,
 }) =>
 subject.listen(
 onData,
 onError: onError,
 onDone: onDone,
 cancelOnError: cancelOnError ?? false,
 );

 /// 對外提供close方法清理內(nèi)存
 void close() {
 /// 略...
 }
}

NotifyManager是obs繼承鏈條可以追蹤到最終的類了,它里面持有了一個GetStream類型的屬性subject传轰,GetStream也是getx框架狀態(tài)管理的核心類剩盒,subjectaddListenerlisten中都有用到,不過我們暫時先緩一緩慨蛙,看一下Obx的實現(xiàn)辽聊。

自定義Widget Obx

分析完obs的實現(xiàn),然我們看一下自定義Widget Obx是如何實現(xiàn)的:

typedef WidgetCallback = Widget Function();

class Obx extends ObxWidget {
 final WidgetCallback builder;

 const Obx(this.builder);

 @override
 Widget build() => builder();
}

Obx類繼承了ObxWidget期贫,但類的功能非常簡單跟匆,只是在構(gòu)造函數(shù)中聲明了一個必傳的參數(shù),而參數(shù)的類型則是一個返回值為Widget的無參函數(shù)通砍,然后在重寫父類的build方法中直接調(diào)用該函數(shù)玛臂。

看來核心的東西應(yīng)該都在Obx的父類ObxWidget中了,我們繼續(xù)深入封孙。

abstract class ObxWidget extends StatefulWidget {
 //... 略去無用代碼
 @override
 _ObxState createState() => _ObxState();

 ///這個build方法時getx框架定義的迹冤,而非系統(tǒng)方法
 @protected
 Widget build();
}

ObxWidget是一個繼承自StatefulWidget的Widget,總所周知虎忌,StatefulWidget是需要有一個對應(yīng)的State來管理Widget泡徙,然后查看_ObxState的具體實現(xiàn)。

class _ObxState extends State<ObxWidget> {

 #2 /// 又見到了這個類
 final _observer = RxNotifier();

 #1
 late StreamSubscription subs;

 #3
 @override
 void initState() {
 super.initState();
 ///這里調(diào)用了RxNotifier的listen方法膜蠢,也就是NotifyManager的listen方法堪藐,這是 #預(yù)留問題4 中方法調(diào)用的地方
 subs = _observer.listen(_updateTree, cancelOnError: false);
 }

 #4
 void _updateTree(_) {
 if (mounted) {
 setState(() {});
 }
 }

 #5
 @override
 void dispose() {
 subs.cancel();
 _observer.close();
 super.dispose();
 }

 #6
 @override
 Widget build(BuildContext context) =>
 RxInterface.notifyChildren(_observer, widget.build);
}

在這里可以看到_ObxState持有兩個屬性。

#1 StreamSubscription是Dart的Steam中提供的類想了解Steam的請查閱Dart | 什么是Stream挑围,定義了訂閱事件的各種方法以及取消方法礁竞。在當(dāng)前類中,StreamSubscription的作用就是在#5 dispose生命周期中取消訂閱贪惹,防止內(nèi)存泄漏的發(fā)生苏章。

#2 我們在追蹤obs的實現(xiàn)的時候,標(biāo)記了一個非常重要的類RxNotifier,在這里又再次遇見了它枫绅,我們創(chuàng)建的每一個Obx對象內(nèi)部都持有一個RxNotifier的實現(xiàn)泉孩。

_ObxState#3 initState生命周期中,_observer調(diào)用listen方法開始監(jiān)聽數(shù)據(jù)并淋,并把_updateTree()函數(shù)作為參數(shù)傳了過去寓搬,當(dāng)數(shù)據(jù)更新時,#3 _updateTree()會被調(diào)用县耽,而它又調(diào)用了setState方法句喷,當(dāng)setState方法被調(diào)用的時候,#6 build方法就會被調(diào)用兔毙,重新返回一個數(shù)據(jù)更新后的Widget唾琼,從而實現(xiàn)了界面的刷新。

我們繼續(xù)追蹤#3listen方法澎剥,最后在get_stream.dart類中锡溯,每個listen方法都會創(chuàng)建一個LightSubscription對象,緩存到了屬性List<LightSubscription<T>>? _onData中哑姚,等待著被臨幸祭饭,而而LightSubscription正是#1 StreamSubscription的子類,被_ObxState中的subs接收叙量。

 mixin NotifyManager<T> {
 GetStream<T> subject = GetStream<T>();
 //NotifyManager中的listen方法
 StreamSubscription<T> listen(
 void Function(T) onData, {
 Function? onError,
 void Function()? onDone,
 bool? cancelOnError,
 }) =>
 //直接調(diào)用了GetStream對象中的listen方法
 subject.listen(
 onData,
 onError: onError,
 onDone: onDone,
 cancelOnError: cancelOnError ?? false,
 );
 }

class GetStream<T> {
 List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];

 // GetStream中的listen方法
 LightSubscription<T> listen(void Function(T event) onData,
 {Function? onError, void Function()? onDone, bool? cancelOnError}) {
 final subs = LightSubscription<T>(
 removeSubscription,
 onPause: onPause,
 onResume: onResume,
 onCancel: onCancel,
 )
 ..onData(onData)
 ..onError(onError)
 ..onDone(onDone)
 ..cancelOnError = cancelOnError;
 addSubscription(subs);
 onListen?.call();
 return subs;
 }

 // 把LightSubscription添加到List中等待被臨幸 
 FutureOr<void> addSubscription(LightSubscription<T> subs) async {
 if (!_isBusy!) {
 return _onData!.add(subs);
 } else {
 await Future.delayed(Duration.zero);
 return _onData!.add(subs);
 }
 }
}

回到_ObxState類倡蝙,在#6 build方法中,并沒有直接返回widget.build()直接返回新創(chuàng)建的Widget而是調(diào)用了RxInterface的靜態(tài)方法notifyChildren绞佩,這個就是上面的 #預(yù)留問題2 寺鸥,我們繼續(xù)分析一下這個方法:

static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {

 final _observer = RxInterface.proxy;
 RxInterface.proxy = observer;
 final result = builder();
 if (!observer.canUpdate) {
 RxInterface.proxy = _observer;
 /// 這個就是使用Obx中并沒有用到obs修飾的變量,而拋出的異常征炼,用過getx的都知道這個異常??
 throw """
 [Get] the improper use of a GetX has been detected. 
 You should only use GetX or Obx for the specific widget that will be updated.
 If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
 or insert them outside the scope that GetX considers suitable for an update 
 (example: GetX => HeavyWidget => variableObservable).
 If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
 """;
 }
 RxInterface.proxy = _observer;
 return result;
 }

這個方法調(diào)用時做了一個校驗析既,如果使用如下方式使用Obx,則會拋出異常谆奥。

child:Obx(() => Text("計數(shù)結(jié)果"))
///這時就會拋出異常眼坏,因為Obx中沒有可觀察的對象。

這么做的初衷應(yīng)該是想要規(guī)避那些不正確的寫法導(dǎo)致增加內(nèi)存酸些,因為Obx是繼承自StatefulWidget的宰译,而上面的寫法完全可以用StatelessWidget實現(xiàn)。

通過上面的一頓操作魄懂,我們已經(jīng)知道此時我們創(chuàng)建Obx對象中已經(jīng)有一個訂閱者StreamSubscription在等待通知沿侈,那通知什么時候會來呢?我們繼續(xù)分析一下個步驟市栗。

obs和Obx的綁定

經(jīng)過上面的分析缀拭,我們已經(jīng)大致知道了obsObx的實現(xiàn)邏輯咳短,那obs的數(shù)據(jù)是如何最終綁定到Obx里呢?我們先回顧一下Demo里面你的這段代碼:

body: Center(
 child: Obx(() => Text("計數(shù)結(jié)果 = ${counter.value}")),
)

接下來跟蹤counter.value方法:

T get value {
 RxInterface.proxy?.addListener(subject);
 return _value;
}

赫然發(fā)現(xiàn)蛛淋,這個方法就是我們上面的#預(yù)留問題1咙好,這個地方有兩個問題:

RxInterface.proxy對象是什么?

② 參數(shù)subject是什么褐荷?

問題②比較好解答勾效,細(xì)心的同學(xué)應(yīng)該還記得我們創(chuàng)建的RxInt對象是繼承自NotifyManager的,而NotifyManager中就有個GetStream類型的屬性subject叛甫。

問題①比較難层宫,因為要從整個CounterDemoPage的加載說起,整個流程如下:

1其监、CounterDemoPage對象創(chuàng)建萌腿,作為屬性的counter隨后完成創(chuàng)建,counter父類為RxNotifier(1)

2棠赛、CounterDemoPagebuild的方法被調(diào)用哮奇,Obx對象被創(chuàng)建,Obx中持有一個Function睛约,返回值為Widget

3、Obx的父類ObxWidget_ObxState被創(chuàng)建哲身,_ObxState持有了對象RxNotifier(2)

4辩涝、_ObxStatebuild方法被調(diào)用,調(diào)用到RxInterface.notifyChildren方法勘天,該p1為_ObxState持有的對象RxNotifier(2)怔揩,p2為Obx中的Function

5、執(zhí)行notifyChildren方法

/// 當(dāng)前Obx中持有的obs對象
final _subscriptions = <GetStream, List<StreamSubscription>>{};

static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
 /// 此時RxInterface.proxy對象為null脯丝,將null值賦給_observer
 final _observer = RxInterface.proxy;
 /// 把RxNotifier(2)對象復(fù)給RxInterface.proxy商膊,此時proxy短暫不為空
 RxInterface.proxy = observer;
 /// 調(diào)用Obx中持有的函數(shù)Function,執(zhí)行步驟6
 final result = builder();

 /// 判斷是否能更新宠进,如果不能拋出異常晕拆,判斷依據(jù)就是_subscriptions是否為空
 if (!observer.canUpdate) {
 RxInterface.proxy = _observer;
 throw ""
 }

 /// proxy對象重新置空,開始下一個輪回
 RxInterface.proxy = _observer;
 /// Obx的Function返回值作為該方法的最終返回值
 return result;
}

6材蹬、Obx中的函數(shù)體為return Text("計數(shù)結(jié)果 = ${counter.value}")实幕,這便回到了本小節(jié)剛開始的地方,調(diào)用counter.value方法

結(jié)論:問題①中的RxInterface.proxy對象就是我們創(chuàng)建的Obx對象的父類的state _ObxState中持有的RxNotifier(2)

#預(yù)留問題1解決了堤器,我們繼續(xù)調(diào)用addListener方法昆庇,也就是我們上面的#預(yù)留問題3,我們繼續(xù)跟進(jìn)問題:

/// 當(dāng)前Obx中持有的obs對象
final _subscriptions = <GetStream, List<StreamSubscription>>{};


/// 該方法就是把RxInt中的subject與Obx中的subject做關(guān)聯(lián) 
/// 參數(shù)rxGetx為RxInt中的subject
void addListener(GetStream<T> rxGetx) {
 /// 判斷當(dāng)前RxInt是否已經(jīng)添加過了闸溃,避免重復(fù)添加
 if (!_subscriptions.containsKey(rxGetx)) {
 /// 在這個時候整吆,RxInt才開始監(jiān)聽value數(shù)據(jù)的變化
 final subs = rxGetx.listen((data) {
 /// RxInt中數(shù)據(jù)變化的通知直接轉(zhuǎn)發(fā)到Obx中的GetStream中去
 if (!subject.isClosed) subject.add(data);
 });
 /// 將RxInt中l(wèi)isten方法的返回值StreamSubscription類型的subs添加到_subscriptions中拱撵,作為canUpdate方法的判斷依據(jù)
 final listSubscriptions = _subscriptions[rxGetx] ??= <StreamSubscription>[];
 listSubscriptions.add(subs);
 }
}

看到這里,終于明白RxInt中的RxNotifier(1)Obx中的RxNotifier(2)是如何做到聯(lián)動通知的了表蝙,Obx的被通知數(shù)據(jù)有更新后裕膀,觸發(fā)了RxNotifier(2)中持有的_updateTree函數(shù),然后以setState的方式重走了_ObxStatebuild方法勇哗,最終完成了UI的更新昼扛。

在我們點擊按鈕時,觸發(fā)RxIntvalue方法欲诺,也是執(zhí)行相同的邏輯抄谐。

從上面的分析可的出,如果一個obs創(chuàng)建的Rx對象扰法,沒有在任何Obx中使用到蛹含,那么該對象不會添加監(jiān)聽方法,節(jié)約了內(nèi)存開銷塞颁,如果Obx沒有使用任何Rx對象浦箱,則會在開發(fā)階段拋出異常,提醒開發(fā)者不規(guī)范的操作祠锣。在RxNotifier(1)RxNotifier(2)的引用方式也保證了消息不會被錯誤的通知到酷窥,不得不說這是一個很有意思的設(shè)計。

結(jié)案陳詞

最后用簡單的方式總結(jié)一下狀態(tài)管理機制的實現(xiàn)邏輯:

1伴网、通過obs的方式創(chuàng)建RxInt類型的待觀察數(shù)據(jù)counter蓬推,創(chuàng)建了stream對象但沒有被訂閱。

2澡腾、創(chuàng)建Obx并使用了counter沸伏,在Obxbuild第一次被調(diào)用時,Obx中的streamcounterstream設(shè)置了訂閱事件动分,并把counter中觀察到的變化同步給Obx毅糟。

3、Obxstream觀察到變化澜公,調(diào)用Obx中傳入的函數(shù)參數(shù)姆另,完成UI的刷新。

4玛瘸、以上蜕青。

寫在最后

原創(chuàng)不易,您的關(guān)注和點贊是我最大的動力~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末糊渊,一起剝皮案震驚了整個濱河市右核,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渺绒,老刑警劉巖贺喝,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菱鸥,死亡現(xiàn)場離奇詭異,居然都是意外死亡躏鱼,警方通過查閱死者的電腦和手機氮采,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來染苛,“玉大人鹊漠,你說我怎么就攤上這事〔栊校” “怎么了躯概?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長畔师。 經(jīng)常有香客問我娶靡,道長,這世上最難降的妖魔是什么看锉? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任姿锭,我火速辦了婚禮,結(jié)果婚禮上伯铣,老公的妹妹穿的比我還像新娘呻此。我一直安慰自己,他們只是感情好懂傀,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布趾诗。 她就那樣靜靜地躺著,像睡著了一般蹬蚁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上郑兴,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天犀斋,我揣著相機與錄音,去河邊找鬼情连。 笑死叽粹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的却舀。 我是一名探鬼主播虫几,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挽拔!你這毒婦竟也來了辆脸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤螃诅,失蹤者是張志新(化名)和其女友劉穎啡氢,沒想到半個月后状囱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡倘是,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年亭枷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀崭。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡叨粘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘤睹,到底是詐尸還是另有隱情升敲,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布默蚌,位于F島的核電站冻晤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绸吸。R本人自食惡果不足惜鼻弧,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锦茁。 院中可真熱鬧攘轩,春花似錦、人聲如沸码俩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稿存。三九已至笨篷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓣履,已是汗流浹背率翅。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袖迎,地道東北人冕臭。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像燕锥,于是被迫代替她去往敵國和親辜贵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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