剖析Flutter的常用库get_it

前言

通常外企的领导在工作群发通知消息

就马上看到有人回复:I get it.

这时候,也会有人说: I got it.

只是当你说 I get it. 的时候,更像是明白了一些之前不明白的事情,或者配合其他吐槽的话会显得不耐烦。

而说 I got it. 的时候,就是纯粹表示,我知道了明白了。

今天我们就来介绍Flutter中的常用库get_it

一、来由

在Dart和Flutter工程中,为一个组件提供对象/服务的默认方式是通过InheritedWidget。

还有Provider、Singleton、IoC等方式。

1.1 InheritedWidget

如果希望一个部件或其模型能够访问服务,则组件必须是继承的组件的子组件。然后这会导致不必要的嵌套。而且依赖性强,持续性维护差。

///创建数据类 DataWidget,然后在页面中用数据类DataWidget包含页面child部分
///1、创建 DataWidget
class DataWidget extends InheritedWidget {
  DataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);
  final int data;
  static DataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<DataWidget>();
  }
  @override
  bool updateShouldNotify(DataWidget old) {
    return old.data != data;
  }
}

///2、创建 InheritedWidgetTestRoute.dart 文件
class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return  Center(
      child: DataWidget( // 重点:使用DataWidget包裹
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: Text(DataWidget.of(context)
              .data.toString())
            ),
            RaisedButton(
              child: Text("Increment"),
              //每点击一次,将count自增,然后更新页面渲染,DataWidget的data将被更新  
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}

1.2 Provider

为组件提供对象/服务,还有一种方式是使用Provider,使用比较繁琐。

///创建数据类 `CountNotifier`,然后在页面中用数据类 `CountNotifier`包裹child部分。
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 创建 Widget 持有 CountNotifier 数据
    return ChangeNotifierProvider.value(
      value: CountNotifier(),
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: ProvidePage(title: 'Provider 测试页面'),
      ),
    );
  }
}

class ProvidePage extends StatelessWidget {
  final String title;
  ProvidePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 获取 CountNotifier 数据 (最简单的方式)
    final counter = Provider.of<CountNotifier>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text( '按下按钮,使数字增加:', ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

/// 核心:继承自ChangeNotifier
/// 可以单独放在一个类文件里
class CountNotifier with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  increment() {
    _count++;
    // 核心方法,通知刷新UI,调用build方法
    notifyListeners();
  }
}

1.3 Singleton、IoC

我们也可以通过其他的方式在App中的任意位置获取到要访问的对象,但是:

  • 如果使用Singleton,则无法轻松地将实现切换到另一个(例如用于单元测试的模拟版本)

  • 用于依赖项注入的IoC容器提供了类似的功能,但代价是启动时间慢且可读性差,因为不知道神奇注入的对象来自何处。 由于大多数IoC库都依赖反射,因此它们不能与Flutter一起使用。

1.4 GetIt

在App迭代发展中,随着代码工程的增长,在某些时候需要将App的部分逻辑放在与Widget分离的类中。使Widget不具有直接依赖关系可以使代码更好地组织并更易于测试和维护。但是现在需要一种从 UI 代码访问这些对象的方法。

作者escamoteur 借鉴.net中的Service Locator Splat概念,在Dart中开发而成。

Service Locators 的概念,它是一种将接口(抽象基类)与具体实现解耦的方法,同时允许通过接口从您的 App 中的任何地方访问具体实现。

故GetIt应运而生,从1.0到现在的7.x

二、介绍

2.1 定义

[GetIt] 官方介绍

This is a simple Service Locator for Dart and Flutter projects with some additional goodies highly inspired by Splat. It can be used instead of InheritedWidget or Provider to access objects e.g. from your UI.

Typical usage:

  • Accessing service objects like REST API clients or databases so that they easily can be mocked.
  • Accessing View/AppModels/Managers/BLoCs from Flutter Views

V7.0 has some breaking changes Check please check the release notes to see what's new.

译文:

GetIt是一个用于 Dart 和 Flutter 项目的简单服务定位器,其中包含一些受到 Splat 启发的附加功能。 它可以用来代替 InheritedWidget 或 Provider 比如从你的用户界面来访问对象。

典型用法(使用场景):

  • 访问 REST API 客户端或数据库等服务对象,以便轻松模拟它们
  • 从 Flutter 视图访问 View/AppModels/Managers/BLoCs

简而言之

GetIt是一个简单的直接服务定位器,允许将接口与具体实现分离,并从应用程序的任何地方访问具体实现。

一句话总结

GetIt是一个工具箱。

2.2 特点

  • 调用极快 (复杂度O(1))
  • 易学/易用
  • 不会像提供程序或 Redux 那样使用特殊的小部件来使您的 UI 树混乱以访问您的数据。

Redux 的设计思想很简单,就两句话。

(1)Web 应用是一个状态机,视图与状态是一一对应的。

(2)所有的状态,保存在一个对象里面。

三、使用

3.1 引入

在某包下的pubspec.yaml引入框架

dependencies:
  flutter:
    sdk: flutter
  domain_common:
    path: ../domain_common
  res_common:
    path: ../res_common
  provider: ^5.0.0
  get_it: ^7.1.3

3.2 获取

GetIt以单例模式使用

GetIt getIt = GetIt.instance;
//or
GetIt getIt = GetIt.I;

//重新自定义一个新的
GetIt getIt = GetIt.asNewInstance();

3.3 注册

GetIt locator = GetIt.instance;

//在GetIt里注册工厂类Mutou
locator.registerFactory<Mutou>(() => MutouImpl());

//在GetIt里注册单例
locator.registerSingleton<FileSystem>(() => LocalFileSystem());
locator.registerLazySingleton<ClientRepository>(() => ClientRepositoryImpl());

3.4 调用

GetIt locator = GetIt.instance;
//调用工厂对象
var mutouModel = locator.get<Mutou>();

//调用单例
var fileSystemSingleton = locator<FileSystem>();

3.5 注册方式

有三种常用的注册方式

GetIt locator = GetIt.instance;

Future<void> setupLocator({bool test = false}) async {
    // 一,单例模式,使用 registerSingleton 
    // 在main.dart中调用setupLocator()时,就会立即注册FileSystem
    // 注册单例,保证每一次调用FileSystem工具时,都是同一个
    locator.registerSingleton<FileSystem>(FileSystem());
  //有扩展函数可以传参可以异步
    //locator.registerFactoryParam
  //locator.registerFactoryAsync
  //locator.registerFactoryParamAsync
  //还可以扩展依赖
  //locator.registerSingletonWithDependencies

    // 二,懒汉模式单例,使用 registerLazySingleton 
    // 调用setupLocator()时,没有生成,直到在其他地方第一次调用ClientRepository工具时,才会注册
    // registerSingleton注册单例,保证每一次调用ClientRepository工具时,都是同一个
    locator.registerLazySingleton<ClientRepository>(ClientRepositoryImpl());
  //也支持异步模式
  //locator.registerLazySingletonAsync
    
    // 三, 工厂模式, 使用 registerFactory / registerFactoryParam / registerFactoryParamAsync
    // 调用setupLocator()时,没有生成,直到在其他地方第一次调用Mutou工具时,才会注册
    // 参数必须时工厂函数,工厂函数不需要参数用registerFactory
    // 参数必须时工厂函数,工厂函数需要参数用registerFactoryParam
    // 参数必须时工厂函数,工厂函数是异步且需要参数用registerFactoryParamAsync
    locator.registerFactory<Mutou>( () => MutouImpl()) );
    
    locator.registerFactoryParam<MutouPlus,String,int>((str,num) 
    => MutouPlusImpl(param1:str, param2: num));
    
    locator.registerFactoryParamAsync<MutouPro,String,int>((str,num) 
    => MutouProImpl(param1:str, param2: num));

}

3.6 其他

//判断某个对象是否已注册
  bool isRegistered<T extends Object>({Object? instance, String? instanceName});

//重置所有注册的对象
  Future<void> reset({bool dispose = true});

//画个圈V5.0后才有
//是否范围内重置
Future<void> resetScope({bool dispose = true});


  //之前的所有注册作废,在一个新的范围内新建
  void pushNewScope({
    void Function(GetIt getIt)? init,
    String? scopeName,
    ScopeDisposeFunc? dispose,
  });


 //在某个范围内退出,在这个范围内注册的所有对象作废
  Future<void> popScope();

 //若有多个范围,则制定某个范围内退出,相应范围注册的所有对象作废
  Future<bool> popScopesTill(String name);

  //获取当前范围名字
  String? get currentScopeName;


//在特定时间timeout内完成所有异步对象创建,ignorePendingAsyncCreation是是否忽略异步对象的创建
Future<void> allReady({
    Duration? timeout,
    bool ignorePendingAsyncCreation = false,
  });

//在特定timeout时长下,是否完成异步的对象创建
 Future<void> isReady<T extends Object>({
    Object? instance,
    String? instanceName,
    Duration? timeout,
    Object? callee,
  });

//不要被这个名字误解了,这个还是检查异步对象是否被创建,因为同步的不需要检查
 bool isReadySync<T extends Object>({
    Object? instance,
    String? instanceName,
  });

//检查是否完成所有异步对象创建,ignorePendingAsyncCreation是是否忽略异步对象的创建
  bool allReadySync([bool ignorePendingAsyncCreation = false]);

// 注册为异步对象的时候,在init函数里手动调动
/// If you want to use this mechanism you have to pass [signalsReady==true] when registering
/// the Singleton.
/// Typically this is used in this way inside the registered objects init
/// method `GetIt.instance.signalReady(this);`
void signalReady(Object? instance);

四、源码

本文以 get_it: ^7.1.3为样本分析

4.1 本身

本身是一个服务工厂或工具箱类

class _ServiceFactory<T extends Object, P1, P2> {
  final _ServiceFactoryType factoryType;

  //里面的单例
  final _GetItImplementation _getItInstance;

  late final Type param1Type;
  late final Type param2Type;

  //存储各种对象
  /// Because of the different creation methods we need alternative factory functions
  /// only one of them is always set.
  final FactoryFunc<T>? creationFunction;
  final FactoryFuncAsync<T>? asyncCreationFunction;
  final FactoryFuncParam<T, P1?, P2?>? creationFunctionParam;
  final FactoryFuncParamAsync<T, P1?, P2?>? asyncCreationFunctionParam;

  ///  Dispose function that is used when a scope is popped
  final DisposingFunc<T>? disposeFunction;

  /// In case of a named registration the instance name is here stored for easy access
  final String? instanceName;

  /// true if one of the async registration functions have been used
  final bool isAsync;

  /// If a an existing Object gets registered or an async/lazy Singleton has finished
  /// its creation it is stored here
  Object? instance;

  /// the type that was used when registering. used for runtime checks
  late final Type registrationType;

  /// to enable Singletons to signal that they are ready (their initialization is finished)
  late Completer _readyCompleter;

  /// the returned future of pending async factory calls or factory call with dependencies
  Future<T>? pendingResult;

  /// If other objects are waiting for this one
  /// they are stored here
  final List<Type> objectsWaiting = [];

  bool get isReady => _readyCompleter.isCompleted;

  bool get isNamedRegistration => instanceName != null;

  String get debugName => '$instanceName : $registrationType';

  bool get canBeWaitedFor =>
      shouldSignalReady || pendingResult != null || isAsync;

  final bool shouldSignalReady;

  _ServiceFactory(
    this._getItInstance,
    this.factoryType, {
    this.creationFunction,
    this.asyncCreationFunction,
    this.creationFunctionParam,
    this.asyncCreationFunctionParam,
    this.instance,
    this.isAsync = false,
    this.instanceName,
    required this.shouldSignalReady,
    this.disposeFunction,
  }) : assert(
            !(disposeFunction != null &&
                instance != null &&
                instance is Disposable),
            ' You are trying to register type ${instance.runtimeType.toString()} '
            'that implements "Disposable" but you also provide a disposing function') {
    registrationType = T;
    param1Type = P1;
    param2Type = P2;
    _readyCompleter = Completer();
  }

4.2 注册

以常用的registerLazySingleton为例看下注册的源码

 @override
  void registerLazySingleton<T>(FactoryFunc<T> func, {String instanceName}) {
    _register<T, void, void>(
        type: _ServiceFactoryType.lazy,
        instanceName: instanceName,
        factoryFunc: func,
        isAsync: false,
        shouldSignalReady: false);
  }

  void _register<T, P1, P2>({
    @required _ServiceFactoryType type,
    FactoryFunc<T> factoryFunc,
    FactoryFuncParam<T, P1, P2> factoryFuncParam,
    FactoryFuncAsync<T> factoryFuncAsync,
    FactoryFuncParamAsync<T, P1, P2> factoryFuncParamAsync,
    T instance,
    @required String instanceName,
    @required bool isAsync,
    Iterable<Type> dependsOn,
    @required bool shouldSignalReady,
  }) {
    //......参数判断已忽略
    final serviceFactory = _ServiceFactory<T, P1, P2>(
      type,
      creationFunction: factoryFunc,
      creationFunctionParam: factoryFuncParam,
      asyncCreationFunctionParam: factoryFuncParamAsync,
      asyncCreationFunction: factoryFuncAsync,
      instance: instance,
      isAsync: isAsync,
      instanceName: instanceName,
      shouldSignalReady: shouldSignalReady,
    );

    if (instanceName == null) {
      _factories[T] = serviceFactory;
    } else {
      _factoriesByName[instanceName] = serviceFactory;
    }

    // 普通单例立即被创建注册
    if (type == _ServiceFactoryType.constant &&
        !shouldSignalReady &&
        !isAsync &&
        (dependsOn?.isEmpty ?? false)) {
      serviceFactory.instance = factoryFunc();
      serviceFactory._readyCompleter.complete();
      return;
    }

    //若是异步或有其他依赖函数创建后的单例,要检查它若依赖于其他单例被创建后再创建
    if ((isAsync || (dependsOn?.isNotEmpty ?? false)) &&
        type == _ServiceFactoryType.constant) {
      /// Any client awaiting the completion of this Singleton
      /// Has to wait for the completion of the Singleton itself as well
      /// as for the completion of all the Singletons this one depends on
      /// For this we use [outerFutureGroup]
      /// A `FutureGroup` completes only if it's closed and all contained
      /// Futures have completed
      final outerFutureGroup = FutureGroup();
      Future dependentFuture;

      if (dependsOn?.isNotEmpty ?? false) {
        //有异步依赖 就 等待单例外部依赖的函数完成稿后 
        final dependentFutureGroup = FutureGroup();

        for (final type in dependsOn) {
          final dependentFactory = _factories[type];
          throwIf(dependentFactory == null,
              ArgumentError('Dependent Type $type is not registered in GetIt'));
          throwIfNot(dependentFactory.canBeWaitedFor,
              ArgumentError('Dependent Type $type is not an async Singleton'));
          dependentFactory.objectsWaiting.add(serviceFactory.registrationType);
          dependentFutureGroup.add(dependentFactory._readyCompleter.future);
        }
        dependentFutureGroup.close();//依赖的函数完成了
        
        dependentFuture = dependentFutureGroup.future;
      } else {//无依赖直接执行
        dependentFuture = Future.sync(() {}); 
      }
      outerFutureGroup.add(dependentFuture);

      /// if someone uses getAsync on an async Singleton that has not be started to get created
      /// because its dependent on other objects this doesn't work because [pendingResult] is not set in
      /// that case. Therefore we have to set [outerFutureGroup] as [pendingResult]
      dependentFuture.then((_) {
        Future<T> isReadyFuture;
        if (!isAsync) {//非异步,单例有依赖
          serviceFactory.instance = factoryFunc();
          isReadyFuture = Future<T>.value(serviceFactory.instance as T);
          if (!serviceFactory.shouldSignalReady) {//单例未创建完成
                        //标记为完成
            serviceFactory._readyCompleter.complete();
          }
        } else {
          //异步单例
          final asyncResult = factoryFuncAsync();

          isReadyFuture = asyncResult.then((instance) {
            serviceFactory.instance = instance;

            if (!serviceFactory.shouldSignalReady && !serviceFactory.isReady) {
              serviceFactory._readyCompleter.complete();
            }
            return instance;
          });
        }
        outerFutureGroup.add(isReadyFuture);
        outerFutureGroup.close();
      });

      //等待返回的结果是outerFutureGroup列表中的最近一个
      serviceFactory.pendingResult =
          outerFutureGroup.future.then((completedFutures) {
        return completedFutures.last as T;
      });
    }
  }


4.3 调用

以调用get函数的源码分析

T get<T extends Object>({
    String? instanceName,
    dynamic param1,
    dynamic param2,
  }) {
    //一、获取数组  _findFactoryByNameOrType
    final instanceFactory = _findFactoryByNameAndType<T>(instanceName);

    Object instance = Object; //late
    if (instanceFactory.isAsync || instanceFactory.pendingResult != null) {
      /// We use an assert here instead of an `if..throw` for performance reasons
      assert(
        instanceFactory.factoryType == _ServiceFactoryType.constant ||
            instanceFactory.factoryType == _ServiceFactoryType.lazy,
        "You can't use get with an async Factory of ${instanceName ?? T.toString()}.",
      );
      assert(
        instanceFactory.isReady,
        'You tried to access an instance of ${instanceName ?? T.toString()} that is not ready yet',
      );
      instance = instanceFactory.instance!;
    } else {
      //二、数组中获取对应对象
      instance = instanceFactory.getObject(param1, param2);
    }

    assert(
      instance is T,
      'Object with name $instanceName has a different type '
      '(${instanceFactory.registrationType.toString()}) than the one that is inferred '
      '(${T.toString()}) where you call it',
    );

    return instance as T;
  }

//一、获取数组  _findFactoryByNameOrType
/// Is used by several other functions to retrieve the correct [_ServiceFactory]
  _ServiceFactory _findFactoryByNameAndType<T extends Object>(
    String? instanceName, [
    Type? type,
  ]) {
    final instanceFactory =
        _findFirstFactoryByNameAndTypeOrNull<T>(instanceName, type: type);
    assert(
      instanceFactory != null,
      // ignore: missing_whitespace_between_adjacent_strings
      'Object/factory with ${instanceName != null ? 'with name $instanceName and ' : ' '}'
      'type ${T.toString()} is not registered inside GetIt. '
      '\n(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;'
      '\nDid you forget to register it?)',
    );
    return instanceFactory!;
  }

  /// Is used by several other functions to retrieve the correct [_ServiceFactory]
_ServiceFactory<T, dynamic, dynamic>?
      _findFirstFactoryByNameAndTypeOrNull<T extends Object>(
          String? instanceName,
          {Type? type,
          bool lookInScopeBelow = false}) {
    /// We use an assert here instead of an `if..throw` because it gets called on every call
    /// of [get]
    /// `(const Object() is! T)` tests if [T] is a real type and not Object or dynamic
    assert(
      type != null || const Object() is! T,
      'GetIt: The compiler could not infer the type. You have to provide a type '
      'and optionally a name. Did you accidentally do `var sl=GetIt.instance();` '
      'instead of var sl=GetIt.instance;',
    );

    _ServiceFactory<T, dynamic, dynamic>? instanceFactory;

    int scopeLevel = _scopes.length - (lookInScopeBelow ? 2 : 1);

  //快速查找
    while (instanceFactory == null && scopeLevel >= 0) {
      // 注意:factoriesByName = <String?, Map<Type, _ServiceFactory<Object, dynamic, dynamic>>>{};
      // factoriesByName这是个Map,所以复杂度是O(1))
      final factoryByTypes = _scopes[scopeLevel].factoriesByName[instanceName];
      if (type == null) {
        instanceFactory = factoryByTypes != null
            ? factoryByTypes[T] as _ServiceFactory<T, dynamic, dynamic>?
            : null;
      } else {
        /// in most cases we can rely on the generic type T because it is passed
        /// in by callers. In case of dependent types this does not work as these types
        /// are dynamic
        instanceFactory = factoryByTypes != null
            ? factoryByTypes[type] as _ServiceFactory<T, dynamic, dynamic>?
            : null;
      }
      scopeLevel--;
    }

    return instanceFactory;
  }

  //二、数组中获取对应对象
  /// returns an instance depending on the type of the registration if [async==false]
  T getObject(dynamic param1, dynamic param2) {
    assert(
        !(factoryType != _ServiceFactoryType.alwaysNew &&
            (param1 != null || param2 != null)),
        'You can only pass parameters to factories!');

    try {
      switch (factoryType) {
          //始终创建最新的模式
        case _ServiceFactoryType.alwaysNew:
          if (creationFunctionParam != null) {
 
            return creationFunctionParam(param1 as P1, param2 as P2);
          } else {
            return creationFunction();
          }
          break;
          //常量模式
        case _ServiceFactoryType.constant:
          return instance as T;
          break;
          //懒汉模式
        case _ServiceFactoryType.lazy:
          if (instance == null) {
            instance = creationFunction();
            _readyCompleter.complete();
          }
          return instance as T;
          break;
        default:
          throw StateError('Impossible factoryType');
      }
    } catch (e, s) {
      print('Error while creating ${T.toString()}');
      print('Stack trace:\n $s');
      rethrow;
    }
  }


总结

GetIt作为一个常用的动态服务定位器,一个工具箱,摆脱了InheritedWidget、Provider、Singleton、IoC等苛刻繁琐的流程,简单易用,脱颖而出。

作为Flutter开发者,经常流于表面的使用,当静下心来分析其源码,自有一套完整健全的逻辑。

作为使用者,管中窥豹,时见一斑。

作为剖析者,细品,别有一番风味。

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

推荐阅读更多精彩内容