这可能是最详细的 EventBus 源码分析02 - EventBus 的注册(上篇)

这可能是最详细的 EventBus 源码分析01 - EventBus 对象的创建
这可能是最详细的 EventBus 源码分析02 - EventBus 的注册(上篇)
这可能是最详细的 EventBus 源码分析03 - EventBus 的注册(下篇)
这可能是最详细的 EventBus 源码分析04 - 事件的发送与执行
这可能是最详细的 EventBus 源码分析05 - Subscriber Index

上一章分析学习了 EventBus 对象的创建, 本章内容将对 EventBus 的注册源码进行分析.
由于代码较长, 所以分为上下两个章节.

  • 上篇: 分析的是如何获取订阅者类中的订阅方法的.
  • 下篇: 分析的是在获取到订阅者类中的所有订阅方法后如何进行订阅的流程.

简单来说本章的核心思想就是: 订阅者调用 EventBus.getDefault().register(this) 进行注册. EventBus 就会通过反射寻找订阅者内所有的订阅方法, 然后进行挨个遍历进行订阅. 如果是粘性 sticky 事件, 在订阅的时候就取出保存的 sticky 事件直接发送, 这样就做到了发布者先发布事件, 之后订阅者订阅事件, 接收订阅之前发布的粘性事件.
Ps: 上篇只分析是如何获取订阅者内所有的订阅方法.


本章涉及到的类及方法

|---EventBus.register()
|       1---SubscriberMethodFinder.findSubscriberMethods()
|             1.1---SubscriberMethodFinder.findUsingInfo()
|                   1.1.1---SubscriberMethodFinder.prepareFindState()
|                   1.1.2---FindState.initForSubscriber()
|                   1.1.3---SubscriberMethodFinder.getSubscriberInfo()
|                   1.1.4---SubscriberMethodFinder.findUsingReflectionInSingleClass()
|                   1.1.5---FindState.moveToSuperclass()
|                   1.1.6---SubscriberMethodFinder.getMethodsAndRelease()
|       2---EventBus.subscribe() 在下篇进行分析


EventBus 3.0 后的版本中增加了注解处理器, 在程序的编译时候, 就可以根据注解生成相对应的代码, 相对于之前的直接通过运行时反射, 大大的提高了程序的运行效率. 目前还是先使用最基本的运行时反射的方式来分析, 后面也会根据运行时反射的方式来分析.


EventBus.register()

public void register(Object subscriber) {
    //通过反射获得订阅者的 Class 对象
    Class<?> subscriberClass = subscriber.getClass();
    //通过订阅者 Class 对象找到该对象内所有的订阅方法集合
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        //遍历并进行单个方法的订阅
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

register 方法中, 通过反射获得我们传入的 thisclass 对象, 也就是订阅者的 class 对象. 然后调用了 subscriberMethodFinder.findSubscriberMethods 传入订阅者 class 对象后获得订阅对象内的所有订阅方法的集合. 这个是获得订阅方法集合的入口. 接着遍历所有的订阅方法挨个对其进行订阅..


1. SubscriberMethodFinder.findSubscriberMethods()

//是一个方法缓存池. key 为我们订阅者的 class 对象. value 为订阅者内所有的订阅方法
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
      //分析 1
      List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
      if (subscriberMethods != null) {
          return subscriberMethods;
      }
      //分析 2
      if (ignoreGeneratedIndex) {
          //直接使用反射获取当前订阅者类的所有订阅方法
          subscriberMethods = findUsingReflection(subscriberClass); 
      } else {
          //先通过自动生成的自定义索引类  SubscriberIndex 方式获取, 
          //获取不到再通过反射的方式获取
          subscriberMethods = findUsingInfo(subscriberClass);
      }
      if (subscriberMethods.isEmpty()) {
          throw new EventBusException("Subscriber " + subscriberClass
                  + " and its super classes have no public methods with the @Subscribe annotation");
      } else {
           //分析 3
          METHOD_CACHE.put(subscriberClass, subscriberMethods);
          return subscriberMethods;
      }
  }
  • 分析 1
    从方法缓存池中获取我们传入订阅者的class对象的所有订阅方法集合. 若存在就直接返回.
    首先看到根据我们传入的订阅者的class对象从 METHOD_CACHE 这个map中获取一个 SubscriberMethod类型的列表.

    那么METHOD_CACHE是用来存放什么呢? 看一下它的声明.
    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>()
    METHOD_CACHE 是一个方法缓存池, 以订阅者的class对象为key, 以订阅者内部所有的订阅方法集合为value

    为什么说METHOD_CACHEvalue SubscriberMethod是订阅者内部的所有订阅方法的集合呢?

    SubscriberMethod 就是将我们平时使用 EventBus 过程中的那些参数基本上都封装起来了, 例如 Method 方法. threadMode线程模型, eventType事件类型, priority优先级, sticky是否是粘性事件等等.

  • 分析 2
    根具ignoreGeneratedIndex的值来采用不同的方式获取订阅方法的集合

    ignoreGeneratedIndex: 是否忽略注解器生成的索引类, 默认为 false. 可以通过EventBusBuilder来设置它的值.

    Ps: 配置生成自定义索引类是 EventBus3.0后新增的特性. 如何生成索引类以及他的使用,可以参考官方文档. 我们后面也会分析到.

  • 分析 3
    subscriberClass 就是订阅者的class对象.
    subscriberMethods 就是 findUsingInfo(subscriberClass) 返回的当前订阅者的所有订阅方法集合.
    将当前订阅者class对象的所有订阅方法存储到METHOD_CACHE 中并返回 subscriberMethods

按照默认的 ignoreGeneratedIndex = false 调用findUsingInfo(subscriberClass)这个方法, 看它是如何拿到当前订阅者 class 对象内的所有订阅方法的.


1.1.SubscriberMethodFinder.findUsingInfo()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
      //从 findState 池中获取 findState 对象, 池中没有就创建一个新的对象
      FindState findState = prepareFindState();
      findState.initForSubscriber(subscriberClass);
      ...
  }

由于在这个方法内调用的方法较多, 所以会逐个分析.
先是通过调用 prepareFindState() 方法, 获得了一个 FindState 对象. 接着调用了 FindState.initForSubscriber 方法. 看名字大概能猜得出, 是做了一些初始化的操作.
那我们先来看 prepareFindState() 方法.


1.1.1.SubscriberMethodFinder.prepareFindState()

private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

private FindState prepareFindState() {
    synchronized (FIND_STATE_POOL) {
        //长度为 4, 遍历 findState 对象池
        for (int i = 0; i < POOL_SIZE; i++) {
            FindState state = FIND_STATE_POOL[i];
            //如果遍历到的对象非空,表示找到了可复用的 findState
            if (state != null) {
                //将该位置清空, 为了以后再来复用
                FIND_STATE_POOL[i] = null;
                //返回这个 findState
                return state;
            }
        }
    }
    //如果遍历完都没有找到可用的 findState, 就创建一个.
    return new FindState();
}

首先是初始化了一个长度为 4 的 FindState类型数组 FIND_STATE_POOL, 也可以叫它 FindState 对象池. 这个方法就是从 FindState 对象池中获取一个 FindState 对象返回, 如果对象池中没有找到可复用的那么就创建一个新的对象返回.

从 FIND_STATE_POOL 中取出不为空的FindState对象, 然后将对应位置置空, 并返回FindState对象, 这样可以避免多线程读写冲突以及读写不一致情况发生.

在 1.1 中获取了 FindState 对象后, 又调用了它的 initForSubscriber()初始化方法. 现在跟进进入 FindState 看一下这是什么类.


1.1.2.FindState.initForSubscriber

static class FindState {
      //用来保存所有的订阅方法信息
      final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
      //以 Event 事件类型为 key, 订阅方法为 value
      final Map<Class, Object> anyMethodByEventType = new HashMap<>();
      //key 为方法名, Value 为订阅者 class 对象
      final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
      final StringBuilder methodKeyBuilder = new StringBuilder(128);

      //subscriberClass 代表的是订阅者本身,也就是register函数的参数,自始至终不会变
      // 会在遍历方法过程中变化, 比如目前在遍历父类方法,clazz 变量就指向父类
      Class<?> subscriberClass;
      Class<?> clazz;
      //是否跳过父类. 即对父类也要查找, 默认为 false
      boolean skipSuperClasses; 
      SubscriberInfo subscriberInfo;
      //做赋值及清理操作
      void initForSubscriber(Class<?> subscriberClass) {
          this.subscriberClass = clazz = subscriberClass;
          skipSuperClasses = false;
          subscriberInfo = null;
      }
     //资源回收
      void recycle() {
          subscriberMethods.clear();
          anyMethodByEventType.clear();
          subscriberClassByMethodKey.clear();
          methodKeyBuilder.setLength(0);
          subscriberClass = null;
          clazz = null;
          skipSuperClasses = false;
          subscriberInfo = null;
      }
     //判断订阅方法是否可以添加到 anyMethodByEventType 中
      boolean checkAdd(Method method, Class<?> eventType) {
         //通常,订阅者没有监听相同事件类型的方法。但是会有一种情况就是 子类订阅了当前事件, 它的父类也订阅了
         //如果 key 没有重复,put 成功,则返回 null, 如果 key 重复了,返回的是被覆盖前的 valu 值.
          Object existing = anyMethodByEventType.put(eventType, method);
          if (existing == null) {
              return true;
          } else {
              if (existing instanceof Method) {
                  ...
                  anyMethodByEventType.put(eventType, this);
              }
                //再根据方法签名进行检查
              return checkAddWithMethodSignature(method, eventType);
          }
      }

      //主要目的还是为了, 不要出现一个订阅者有多个相同订阅方法订阅同一个事件. 如果有, 就将以前的 value 重新放进去覆盖掉新的.
      private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
          ...
          //获得当前 Method 对象表示的方法的类的 Class 对象
          Class<?> methodClass = method.getDeclaringClass();
          //这里也会返回 null, 或者是覆盖前的 value 值
          Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
          // 为 null 表示添加成功并且不是覆盖.
          // 父类Class.isAssignableFrom(子类Class) 判断新添加的类是否是被覆盖类的子类.
          if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
              return true;
          } else {
              //如果不满足上述判断, 将被覆盖前的value, 重新放进去, 并返回 false.
              subscriberClassByMethodKey.put(methodKey, methodClassOld);
              return false;
          }
      }

      //将成员变量clazz指向clazz的父类并排除系统类
      void moveToSuperclass() {
          if (skipSuperClasses) {
              clazz = null;
          } else {
              clazz = clazz.getSuperclass();
              String clazzName = clazz.getName();
              if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
                  //排除系统类
                  clazz = null;
              }
          }
      }
  }

FindState 类. 它是 SubscriberMethodFinder 中的一个静态内部类. 内部存储了对订阅者class对象一些列的集合, 并保存了一些订阅者的方法以及对订阅方法的校验.

FindState 中又出现了一个陌生的 SubscriberInfo, 这是一个接口, 描述了一个订阅者应当具备的特性. 这是在使用自动生成的索引类后才会使用到的. 后面会对配置索引类的流程也进行分析. 这里先不管这些.

public interface SubscriberInfo {
    Class<?> getSubscriberClass();//获取父类列表
    SubscriberMethod[] getSubscriberMethods();//获取注册的订阅方法
    SubscriberInfo getSuperSubscriberInfo();//获取父类(直接父类)的订阅信息
    boolean shouldCheckSuperclass();//是否需要对父类进行检查
}

继续回到 1.1 findUsingInfo() 中向下分析

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
      //从 findState 池中获取 findState 对象, 池中没有就创建一个新的对象
      FindState findState = prepareFindState();
      //传入订阅者的 Class 对象, 为 findState 做一些初始化工作
      findState.initForSubscriber(subscriberClass);
      //循环直到 findState 的 clazz 为空( clazz 为 java. 或 android. 或 javax. 等系统类则结束查找). 
      while (findState.clazz != null) {
          //如果没有使用索引类的情况, 这里默认基本都是返回 null
          findState.subscriberInfo = getSubscriberInfo(findState);
           ... 
      }
      ...
  }

看到初始化完 FindState后, 调用了 getSubscriberInfo()方法将返回值赋值给 findState.subscriberInfo. 不过在没有使用索引类的情况下, 这里返回的基本都是 null. 跟进 getSubscriberInfo() 方法.


1.1.3.SubscriberMethodFinder.getSubscriberInfo(findState)

private SubscriberInfo getSubscriberInfo(FindState findState) {
    //分析 1
    if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        if (findState.clazz == superclassInfo.getSubscriberClass()) {
            return superclassInfo;
        }
    }
    //分析 2
    if (subscriberInfoIndexes != null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}
  • 分析 1
    FindState.initForSubscriber()的方法中, 就已经将 subscriberInfo 置为了 null. 所以第一个 if 不会进入.

  • 分析 2
    在没有使用索引类的情况下, subscriberInfoIndexes 是在 SubscriberMethodFinder 的构造方法传入的, 默认为 null, 所以这里直接返回了 null.

如果配置了索引类, 就会进入到分析 2 的if内.
这里只考虑没有使用索引类的情况. 所以直接返回 null, 继续回到 1.1findUsingInfo ()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
      //从 findState 池中获取 findState 对象, 池中没有就创建一个新的对象
      FindState findState = prepareFindState();
      //传入订阅者的 Class 对象, 为 findState 做一些初始化工作
      findState.initForSubscriber(subscriberClass);
      //循环直到 findState 的 clazz 为空( clazz 为 java. 或 android. 或 javax. 等系统类则结束查找). 
      while (findState.clazz != null) {
          //如果没有使用索引类的情况, 这里默认基本都是返回 null
          findState.subscriberInfo = getSubscriberInfo(findState);
          if (findState.subscriberInfo != null) {
                ...
          } else {
               
              //在运行时利用反射来获取所有被 @Subscribe 注解标注的订阅方法
              findUsingReflectionInSingleClass(findState);
          }
      }
    ...
  }

接着就进入到 else 中的 findUsingReflectionInSingleClass方法, 开始利用反射来获取所有添加了@Subscribe注解的的订阅方法.接着跟进去.


1.1.4.SubscriberMethodFinder.findUsingReflectionInSingleClass(findState)

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        //通过反射获取订阅者 class 对象内的所有方法.
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        ...
        ...
    }
    //对获取到的所有方法进行遍历
    for (Method method : methods) {
        //获取到方法的修饰符
        int modifiers = method.getModifiers();
        //判断修饰符是否是 public, 并且是否可以忽略
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //获取方法的参数, 后面进行参数的判断, EventBus 只允许订阅事件中才参数只有一个
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                //判断这个方法是否有 @Subscribe 注解
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    //获得 Event 事件的 Class 对象.
                    Class<?> eventType = parameterTypes[0];
                    //检查是否需要保存到 findState 的 anyMethodByEventType 中, 返回 true 表示添加成功
                    if (findState.checkAdd(method, eventType)) {
                        //获得这个方法注解的 ThreadMode 线程模式, 根据不同的模式进行不同的线程调度
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 往 findState 用来保存所有的订阅方法信息集合里面添加 SubscriberMethod (方法以及注解信息的封装类) ,
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                //抛异常
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
           //抛异常
        }
    }
}

这段代码的执行过程其实并不是很复杂. 在这里主要是使用了Java的反射和对注解的解析.

  1. 首先通过反射来获取订阅者中所有的方法
  2. 遍历所有方法.
  3. 根据方法的类型, 参数和注解来找到真正的订阅方法.
  4. 找到订阅方法后调用 findState.checkAdd(method,eventType) 判断是否能将订阅方法相关信息保存到 FindState.subscriberMethods当中.

注: (findState.checkAdd 在上面 1.1.2 FindState 中有详细描述)

到这里已经完成对订阅者中所有订阅方法的查找. 接着再回到 1.1findUsingInfo()中, 看到findUsingInfo()while循环中还调用了一个方法, 就是FindState.moveToSuperclass().


1.1.5 FindState.moveToSuperclass()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
      //从 findState 池中获取 findState 对象, 池中没有就创建一个新的对象
      FindState findState = prepareFindState();
      //传入订阅者的 Class 对象, 为 findState 做一些初始化工作
      findState.initForSubscriber(subscriberClass);
      //循环直到 findState 的 clazz 为空( clazz 为 java. 或 android. 或 javax. 等系统类则结束查找). 
      while (findState.clazz != null) {
          //如果没有使用索引类的情况, 这里默认基本都是返回 null
          findState.subscriberInfo = getSubscriberInfo(findState);
          if (findState.subscriberInfo != null) {
              //获得编译期间订阅方法信息集合
              SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
              //接着遍历订阅方法.
              for (SubscriberMethod subscriberMethod : array) {
                  //检测是否添加成功
                  if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                      //将匹配的订阅方法添加到 findState 中保存所有订阅方法信息的集合中
                      findState.subscriberMethods.add(subscriberMethod);
                  }
              }
          } else {
              //在运行时利用反射来获取所有被 @Subscribe 注解标注的订阅方法
              findUsingReflectionInSingleClass(findState);
          }
          findState.moveToSuperclass();
      }
      //资源回收,并返回用来保存所有的订阅方法信息的集合(当前订阅者 class 对象中所有的订阅方法集合)  
      return getMethodsAndRelease(findState);
  }
      //将成员变量clazz指向clazz的父类并排除系统类
      void moveToSuperclass() {
          if (skipSuperClasses) {
              clazz = null;
          } else {
              clazz = clazz.getSuperclass();
              String clazzName = clazz.getName();
              if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
                  //排除系统类
                  clazz = null;
              }
          }
      }

这里的逻辑就是 在第一次循环的时候, FindState.clazz 指向的就是当前订阅者类, 第一次循环结束的时候, 调用 moveToSuperclass() 如果当前订阅者有非系统类的父类, 就将 FindState.clazz指向父类, 开始第二次循环, 接着查找父类中所有的订阅方法, 依次类推.

获取最后又调用了getMethodsAndRelease(findState) 方法并返回. 见名知意, 获取所有的方法并释放.


1.1.6 SubscriberMethodFinder. getMethodsAndRelease(FindState)

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    //先获取 findState 中用来保存所有的订阅方法信息的集合
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    //回收重置 findState.
    findState.recycle();
    //重置 findState 对象池
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}

getMethodsAndRelease就先获取FindState.subscriberMethods, 接着将FindState中的内容及数据重置, 最后重置FindState对象池.以便复用. 最后返回 subscriberMethods.


至此, EventBus.getDefault().register(this) 注册方法的第一步获取订阅者内所有的订阅方法就分析完了, 下一章将会遍历这些方法, 依次对其进行订阅.

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