EventBus 3.X 的源码解析及简单封装

前言

EventBus使用很简单,遵循着注册--发送--接收的一个步骤。EventBus 在3.0版本以后完全摒弃了以前固定接收方法,采用注解的形式来接收消息,注解的方法名可自定义,这样也更能明确的表现出方法的作用,不用再跟以前一样,所有的接收方法都一样的惨剧。当然官方给出了注解解析器,便于提高效率,做到编译时注解。Android Studio上有一个插件,eventbus3-intellij-plugin,可以为3.0及以后的版本提供快速跳转,不封装也可以愉快的使用。当然封装了就破坏了原有的结构,这个插件也没有效果了。一个是快速方便跳转,查找容易,一个是为了代码的统一性牺牲便利性,如何抉择,自己决定吧!

使用

使用方式及其简单,完全傻瓜教程只需三步:

注册:在需要接收EventBus传递消息的类注册EventBus。

EventBus.getDefault().register(object);

regist的参数是Object,也就说明在任何类中都可以注册EventBus。

发送:在任意类中,发送EventBus消息。

//发送即时事件
EventBus.getDefault().post(bean);
//发送粘性事件
EventBus.getDefault().postSticky(bean);

简单说下即时事件和粘性事件的区别:
即时事件:先注册接收,再发送事件。事件发出后即时传递下去,不考虑是否有注册这个事件的方法。
粘性事件:先发送,后注册接收事件。事件先发出,再检测是否有注册此事件的方法,若有就传递下去,若没有就停止传递,直到有此事件的方法注册了,才会传递。此事件EventBus会一直缓存,直到移除为止。
后续解析源码的时候再细说使用场景。

接收:在当前注册的类中,使用注解标记接收EventBus的发送来的数据

EventBus 3.0及后续的方法不再局限方法名,可使用任意方法的方面,只需要在方法前加注解@Subscribe即可

@Subscribe(threadMode = ThreadMode.MAIN)
    public void Demo(Demo3Bean bean) {
        //处理事件
    }

threadMode标记此方法的线程:

  • POSTING:与发送事件的线程保持一致,减少内存开销。如果不需要切换线程,可以直接使用此值。
  • MAIN:切换线程到主线程。由于是主线程操作,避免耗时操作。
  • MAIN_ORDERED:同MAIN线程切换到主线程,与MAIN不同的是,如果MAIN线程发送事件的线程也是主线程,那么不进入队列,直接调用该接受方法;而MAIN_ORDERED是先进入队列后再发送给接受方法。因为也是主线程,所以避免耗时操作。
  • BACKGROUND:后台线程。注意此线程是EventBus的唯一的后台线程,并保证所有事件按顺序交付,如果是比较耗时的后台操作,为了保证其他后台事件的正常运行,请使用ASYNC。
  • ASYNC:后台线程,并且是一个单独的后台线程。若有网络请求等一类的耗时操作,请使用此线程。

如果发送的是粘性事件的话,需要再加一个参数:

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void Demo(Demo3Bean bean) {
         //接收到粘性事件
    }

sticky = true表示这是一个粘性事件,如果不移除此事件,EventBus会一直缓存此粘性事件,但凡注册了此事件的粘性接收事件都会接收到这个事件,所以使用完要及时移除。

// 移除指定的粘性事件
EventBus.getDefault().removeStickyEvent(Object event);

// 移除指定类型的粘性事件
EventBus.getDefault().removeStickyEvent(Class<T> eventType);

// 移除所有的粘性事件
EventBus.getDefault().removeAllStickyEvents();

当然也可以指定事件的优先级:

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0)
    public void Demo(Demo3Bean bean) {
         //TODO
    }

priority = 0是默认优先级,不设置的话priority的值就是0,可以在高优先级的订阅者方法接收到事件之后取消事件的传递。此时,低优先级的订阅者方法将不会接收到该事件。注意: 订阅者方法只有在线程模式为ThreadMode.POSTING时,才可以取消一个事件的传递。或者也可以理解为相同的线程模式下,才能取消下一事件的传递。能保证相同线程的模式也只有ThreadMode.POSTING

掌握以上这些,你就可以愉快的使用了。

源码也要

源码解析

先看注册:

EventBus.getDefault().register(object);
 /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

单例创建EventBus对象

public EventBus() {
        this(DEFAULT_BUILDER);
    }

EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);//注解@Subscriber的方法寻找器
        logSubscriberExceptions = builder.logSubscriberExceptions;//是否通过log输出异常日志,默认输出
        logNoSubscriberMessages = builder.logNoSubscriberMessages;//是否通过log输出没有订阅者的消息,默认输出
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;//是否发生异常到SubscriberExceptionEvent,默认发生
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;//是否将没有订阅的消息通过NoSubscriberEvent发送出去
        throwSubscriberException = builder.throwSubscriberException;//是否抛出SubscriberException,默认抛出
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

创建过程也是大家常见的建造者模式,如果想定义其中某些属性也可以自己创建build,来修改内部属性;

EventBus.builder()
                .throwSubscriberException(false)
                .eventInheritance(false)
                ...
                .installDefaultEventBus();

最后执行installDefaultEventBus(),将修改后的EventBus对象赋值给默认的对象,使用方法依然是EventBus.getDefault().post(bean);EventBus.getDefault()获取的默认对象就是已经自定义过的EventBus对象。
继续看register

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//检索当前注册EventBus类使用@Subscriber的方法
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

简单看下findSubscriberMethods方法:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        ...
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

到这可以看到,如果当前类注册了EventBus而没有任何方法使用@Subscribe接收方法,就会抛出异常。所以EventBus要按需使用,当需要使用的时候再去注册,不能再在基类中无脑注册反注册了。

再看下subscribe方法:

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        ···
        //多次注册也会抛异常
        if (subscriptions.contains(newSubscription)) {
             throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
        

        int size = subscriptions.size();
        //根据优先级来排队
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        //粘性事件处理
        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

注册的逻辑大致如此,喜欢钻研的小伙伴们可以深入代码去了解。

接下来发送事件:

EventBus.getDefault().post(bean);

即时事件post()

/** Posts the given event to the event bus. */
    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        //事件入队
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                //只要队列不为空,就一直循环发送事件
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

再看看关键方法postSingleEvent()

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //EventBus支持继承和接口,发送事件会考虑事件的继承树
        if (eventInheritance) {
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        //如果没有找到订阅该事件的订阅者
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

从源码中可以看出,对于同一个事件,EventBus会自己检索出注册事件类的父类,并把事件发到其父类中,然后调用postSingleEventForEventType()处理事件。
接下来看下这个方法postSingleEventForEventType()

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

这个方法也比较简单,取出事件类型的集合,遍历该集合,调用postToSubscription()方法,统一处理事件,处理完在finally中初始化各种参数。
最终调用方法postToSubscription()

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

首先获取threadMode,此threadMode就是我们在注解里写的那个线程。如果是POSTING,那么直接调用invokeSubscriber();如果是MAIN,先判断当前发送事件线程是不是主线程,如果是直接调用invokeSubscriber(),否则使用mainThreadPoster处理此事件……
这里出现几个Poster:mainThreadPoster,backgroundPoster,asyncPoster,分别介绍下:

  • mainThreadPoster:顾名思义就是主线程处理器,无论哪个线程的数据通过此处理器处理后事件都在主线程执行。
  • backgroundPoster:统一后台线程处理器,无论哪个线程的数据通过此处理器处理后事件都在后台线程中执行,此线程执行先进先出的策略,保证数据的顺序性。
  • asyncPoster:单独后台处理器,作用同上,唯一的区别就是每一个事件都是单独的后台线程,没有先后顺序。

无论哪种线程,最终都调用invokeSubscriber()方法处理数据:

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

使用反射来调用订阅方法,这样就实现了将事件发送给订阅者,订阅者调用了订阅方法的过程。因为最终是使用了反射调用方法,所以肯定是影响执行效率,官方也给出了提高效率的办法,稍后会介绍。


其实发送粘性事件的流程也大致如此:

EventBus.getDefault().postSticky(bean);

看下postSticky()

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }

将粘性事件添加到stickyEvents集合中,之后调用post()方法,后续调用流程跟普通事件一样,只不过是找不到订阅该方法的订阅者罢了。那为什么当有订阅者订阅了该方法后会立马收到该粘性事件呢?


subscribe方法中有专门处理粘性事件的代码:

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        ···
        //粘性事件处理
        if (subscriberMethod.sticky) {
            //检索父类,默认为true
            if (eventInheritance) {
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    //如果订阅者订阅的事件类型与当前的粘性事件类型相同,那么把该事件分发给这个订阅者
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

EventBus并不知道当前的订阅者对应了哪个粘性事件,因此需要全部遍历一次,找到匹配的粘性事件后,最终都调用了checkPostStickyEventToSubscription()

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

又回到postToSubscription()方法,无论是粘性事件还是普通事件,最终都会通过调用postToSubscription()方法,区分threadMode,使用不同的调度处理器来处理不同线程的事件。
最后看下反注册反复unregister()

public synchronized void unregister(Object subscriber) {
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                //解除订阅
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

在for循环中调用unsubscribeByEventType()方法,将订阅者及事件作为参数传递过去,解除两者的订阅关系。
看下unsubscribeByEventType()

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

这就比较容易理解了,在subscriptions和typesBySubscriber中移除相应的订阅信息。
以上就是EventBus 3.X的源码简单解析,EventBus的体量小,功能大,解读也容易,推荐大家看看源码了解运转原理。


简单封装

前面也说过,如果使用AS提供的插件eventbus3-intellij-plugin,在订阅者和被订阅者直接可以提供绿色通道


装上插件后,左边箭头指示的android小图标,点击下就能跳转到被订阅者处,如果有多个被订阅者也会弹出弹窗让你去选目标,同理在被订阅者处也有对应小图标,点击效果同上。

言归正传,因为EventBus可以跨Module进行数据传递,所以我们先建一个lib,封装代码都写里面,不同的Module去依赖我们的这个lib,就可以实现不同Module互发消息了。


app依赖lib,module依赖lib,这样app和module1之间可以相互发送接收事件,当然你再多建几个Module也是一样的,比如建module2库,module2依赖lib,那么module1和module2之间也能互相发送接收事件了,很方便。
封装EventBus就比较简单了,毕竟就那几个方法,创建一个EventBusUtils类:

public class EventBusUtils {

    public static void init(SubscriberInfoIndex subscriberInfoIndex) {
        EventBus.builder().addIndex(subscriberInfoIndex).installDefaultEventBus();
    }

    /**
     * 注册方法
     * 若注册的act没有@Subscribe注解,会抛异常
     */
    public static void register(Object subscriber) {
        if (!EventBus.getDefault().isRegistered(subscriber)) {
            try {
                EventBus.getDefault().register(subscriber);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 接触注册
     */
    public static void unregister(Object subscriber) {
        if (EventBus.getDefault().isRegistered(subscriber)) {
            try {
                EventBus.getDefault().unregister(subscriber);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发送即时事件
     */
    public static void event(BaseEventBusBean bean) {
        EventBus.getDefault().post(bean);
    }

    /**
     * 发送粘性事件
     */
    public static void eventSticky(BaseEventBusBean bean) {
        EventBus.getDefault().postSticky(bean);
    }

    /**
     * 移除所以粘性事件
     */
    public static void removeAllStickyEvent() {
        EventBus.getDefault().removeAllStickyEvents();
    }

    /**
     * 移除一个粘性事件
     */
    public static boolean removeStickyEvent(BaseEventBusBean bean) {
        return EventBus.getDefault().removeStickyEvent(bean);
    }

    /**
     * 移除一个指定类型的粘性事件
     */
    public static <T> T removeClassStickyEvent(Class<T> clazz) {
        return EventBus.getDefault().removeStickyEvent(clazz);
    }

    /**
     * 优先级高的订阅者可以终止事件往下传递
     */
    public static void cancelEventDelivery(BaseEventBusBean bean) {
        try {
            EventBus.getDefault().cancelEventDelivery(bean);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清空缓存
     */
    public static void clearCache() {
        EventBus.clearCaches();
        
    }
}

每个方法我都做了注释,简单一目了然。
EventBus发送接收的事件是一个对象,如果要做到在不同Module下传递数据,就要保证被订阅者和订阅者发送/接收的对象是同一个对象。那么我们就建一个基类BaseEventBusBean

public class BaseEventBusBean {

}

不需要实现什么方法。然后再建一个bean包,里面存储全部的事件类,其父类就是BaseEventBusBean

每个事件单独自己去实现想要实现的方法,便于管理。
鉴于EventBus没有@Subscribe接收方法却注册会崩溃的话,建议在各位的Activity和Fragment的基类中添加类似的方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (isRegisterEventBus()) {
        EventBusUtils.register(this);
    }
}

protected boolean isRegisterEventBus() {
    return false;
}


@Override
public void onDestroy() {
    super.onDestroy();
    if (isRegisterEventBus()) {
        EventBusUtils.unregister(this);
    }
}

子类需要注册EventBus的话,重写isRegisterEventBus()方法,返回true就好了。
以上就是EventBus的简单封装,真的是很简单哦。

索引

最后再来说说索引的问题,由于EventBus 3.0以后使用运行时注解的形式接收方法,在执行效率上肯定会有影响。那么对于一个严格的程序来说,这是不能容忍的。所幸官方给出了注解解析器,将运行时注解变成了编译时注解,大大提高了执行的效率。官方文档:Subscriber Index
首先区分AS的版本,3.0以上和3.0以下的集成方式不同,我的AS版本是3.1.4,所以按照3.0以上的方式简单的介绍下:
在lib库下的build.gradle添加以下代码:

android {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'com.zdu.evenbusdemo.MyEventBusIndex' ]
            }
        }
    }

dependencies {
    api 'org.greenrobot:eventbus:3.1.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

com.zdu.evenbusdemo.MyEventBusIndex:这个路径自定义,设置完重新编译后会在build/generated/source/apt/debug/com.zdu.evenbusdemo包中生成MyEventBusIndex类,这个类就是索引类。之后在EventBusUtilsinit()初始化方法中将这个类传递过去就行了,建议在Application中初始化。

EventBusUtils.init(new MyEventBusIndex());

AS3.0以下需要以apt的形式加载,这里就不多做介绍了,大家去官方文档上看吧。

结语

EventBus作为一个事件总线,有很多优秀的地方,可以在任意位置注册接收发送事件,不受Activity的影响,使用起来非常方便。但也正是这样的优点,使得EventBus容易被滥用。你可以使用EventBus在某些特定的场景使用,比如跨Module,跨了N个Activity等,这将大量的减少代码量,但如果你用EventBus来控制整个app的流程,那么导致碎片化更严重,不易维护。所以工具谁都可以有,怎么让工具的作用最大化,就看各位使用者了。
最后附上封装Github地址:EventBusDemo

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

推荐阅读更多精彩内容

  • 昨天跑男鹿晗踢完球后的飞吻以及近几天来《欢乐颂》谢童关关一对的持续发糖都让我这个被工作考研毕业回家几座大山轮翻虐得...
    木叶子z阅读 454评论 0 0
  • 路灯灰了 衬着沉重的月光 轻轻的 悄悄地 夜幕已然降临 天边的最后一缕微光 随着列车呼啸着消散 时间停了 车窗里疲...
    末秋i阅读 180评论 1 0
  • 《开学第一课》观后感 陈思妤 预备2班 新学期的...
    风卷云舒www阅读 399评论 0 1
  • 元木木阅读 203评论 0 2