EventBus 使用及源码解析

我是在EventBus 3.1.1上做得介绍
下面是EventBus github地址

EventBus github地址

EventBus

EventBus is a publish/subscribe event bus for Android and Java.


image.png

EventBus的介绍

EventBus...

  • simplifies the communication between components
    • decouples event senders and receivers
    • performs well with Activities, Fragments, and background threads
    • avoids complex and error-prone dependencies and life cycle issues
  • makes your code simpler
  • is fast
  • is tiny (~50k jar)
  • is proven in practice by apps with 100,000,000+ installs
  • has advanced features like delivery threads, subscriber priorities, etc.

以下是百度翻译而来的

*简化组件之间的通信
*分离事件发送者和接收者
*很好地处理活动、片段和后台线程
*避免复杂且易出错的依赖关系和生命周期问题
*使代码更简单
*快
*很小(约5万罐)
*通过安装超过100000000次的应用程序在实践中得到验证
*具有高级功能,如传递线程、订阅服务器优先级等。

我总结的是,EventBus可以替换Handle来进行线程间的通讯,代码简介方便,更有粘性这么一说,避免了依赖生命周期的,收不到消息的问题

EventBus 的使用

EventBus注册 ,通常在Activity或者Fragment中的onCreate 方法中注册,注册后可以监听处理事件的方法

EventBus.getDefault().register(this);

EventBus解除注册,注册之后需要在onDestory中解除注册方法,避免内存泄漏

EventBus.getDefault().unregister(this);

发送事件, messageEvent可以为任何类型,自己定义,调用此方法的地方可以是代码中的任何地方

EventBus.getDefault().post(messageEvent);

接收事件 threadMode代码接收消息所在的线程 ,需要注意的是,只能在执行register(this)的类中执行此方法

@Subscribe(threadMode = ThreadMode.MAIN)
public void XXX(MessageEvent messageEvent) {
    ...
}

threadMode的描述

/**
 * Each subscriber method has a thread mode, which determines in which thread the method is to be called by EventBus.
 * EventBus takes care of threading independently from the posting thread.
 * 
 * @see EventBus#register(Object)
 * @author Markus
 */
public enum ThreadMode {
    /**
     * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery
     * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
     * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers
     * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
     */
/*订阅者将在发布事件的同一线程中调用。这是默认值。事件传递是同步完成的,一旦发布完成,所有订阅者都将被调用。此ThreadMode意味着开销最小
,因为它完全避免了线程切换。因此,这是已知完成的简单任务的推荐模式,是一个非常短的时间而不需要主线程。使用此模式的事件处理程序应该快速返回
以避免阻止发布线程,这可能是主线程。
*/
    POSTING,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is
     * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event
     * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.
     * If not on Android, behaves the same as {@link #POSTING}.
     */
/*
订阅者将在Android的主线程(有时称为UI线程)中调用。如果发布线程是主线程,则将直接调用事件处理程序方法(与ThreadMode.POSTING所描述的同步)。
使用此模式的事件处理程序必须快速返回以避免阻塞主线程。
*/
    MAIN,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},
     * the event will always be queued for delivery. This ensures that the post call is non-blocking.
     */
/*
订阅者将在Android的主线程中调用。该事件总是排队等待以后交付给订阅者,因此对post的调用将立即返回。这为事件处理提供了更严格且更一致的顺序
(因此名称为MAIN_ORDERED)。例如,如果您在具有MAIN线程模式的事件处理程序中发布另一个事件,则第二个事件处理程序将在第一个事件处理程序之前
完成(因为它是同步调用的 - 将其与方法调用进行比较)。使用MAIN_ORDERED,第一个事件处理程序将完成,然后第二个事件处理程序将在稍后的时间点调用
(一旦主线程具有容量),使用此模式的事件处理程序必须快速返回以避免阻塞主线程。
我认为这是在主线程中有一个排队的操作,等待第一个完成了,才会接收这个
*/
    MAIN_ORDERED,

    /**
     * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods
     * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
     * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to
     * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.
     */
/*
订阅者将在后台线程中调用。如果发布线程不是主线程,则将在发布线程中直接调用事件处理程序方法。如果发布线程是主线程,则EventBus使用单个后台
线程,该线程将按顺序传递其所有事件。使用此模式的事件处理程序应尝试快速返回以避免阻塞后台线程。
*/
    BACKGROUND,

    /**
     * Subscriber will be called in a separate thread. This is always independent from the posting thread and the
     * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should
     * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
     * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus
     * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.
     */
/**
事件处理程序方法在单独的线程中调用。这始终独立于发布线程和主线程。发布事件永远不会等待使用此模式的事件处理程序方法。如果事件处理程序的执行
可能需要一些时间,例如用于网络访问,则应使用此模式。避免同时触发大量长时间运行的异步处理程序方法来限制并发线程数。EventBus使用线程池从
已完成的异步事件处理程序通知中有效地重用线程。
*/
    ASYNC
}

ThreadMode.POSTING 发送者在哪个线程中调用的发送,与接收者到的线程一直
ThreadMode.Main 会将消息发送到主线程
ThreadMode.MAIN_ORDERED 会将消息发送到主线程,并且会生成队列,等待第一个方法完成了,第二个接收的的方法才会再执行
ThreadMode.BACKGROUND 消息接收到的线程是子线程
ThreadMode.ASYNC 每次回单独启用一个子线程,用于调用耗时的程序

Eventbus 粘性事件

粘性事件代表着,如果事件发送了,接收者还没有注册上,在接收者注册上之后,也可以收到此消息
打个比方,比如广播机制,当一个广播发送出去了,广播接收者还没有注册上,这个广播就会被丢弃,当没有发送过,就算接收者后来注册上了,这个广播也不会被接收到。粘性事件就是会避免这样的问题

EventBus.getDefault().postSticky(messageEvent);
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void XXX(MessageEvent messageEvent) {
    ...
}

EventBus优先级

在@Subscribe中还有一个属性 priority这个代码接收者的优先级,这个值默认是0,优先级越高,越先被调用

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default 0;
}

当高priority的接收者接收到消息后,觉得该事件可以不被后面的接收者接收到的时候可以调用cancelEventDelivery来取消事件,从而不被后面的接收者接收到

EventBus.getDefault().cancelEventDelivery(event) ;

EventBus源码的解析

获取EventBus实例

在创建EventBus实例的时候,一种方式是按照我们上面的形式,通过EventBus的静态方法getDefault来获取一个实例。getDefault本身会调用其内部的构造方法,通过传入一个默认的EventBusBuilder来创建EventBus。此外,我们还可以直接通过EventBus的builder()方法获取一个EventBusBuilder的实例,然后通过该构建者模式来个性化地定制自己的EventBus。

public class EventBus {
...
static volatile EventBus defaultInstance;
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
...
    /** 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;
    }

    public static EventBusBuilder builder() {
        return new EventBusBuilder();
    }

    /** For unit test primarily. */
    public static void clearCaches() {
        SubscriberMethodFinder.clearCaches();
        eventTypesCache.clear();
    }

    /**
     * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
     * central bus, consider {@link #getDefault()}.
     */
    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);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }
...
    public EventBus build() {
        return new EventBus(this);
    }

register方法

一般写代码中带注释的方法,都会用到反射

EventBus->Register

    public void register(Object subscriber) {
// 首席会获取注册的对象的类型
        Class<?> subscriberClass = subscriber.getClass();
// 然后获取注册的对象的订阅方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//不出我所料在这个方法中用到了反射
// 对当前实例加锁,并不断执行监听的逻辑
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 对订阅方法进行注册
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

    EventBus->Register->findSubscriberMethods

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();METHOD_CACHE 变量用于存储带Subscriber注释的方法
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        //通过放射获取到带注释的方法
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
        // 将反射到的方法存入到Method_cache变量中
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }
EventBus->Register->findSubscriberMethods->findUsingInfo
    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
// 这里通过FindState对象来存储找到的方法信息
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
// 这里是一个循环操作,会从当前类开始遍历该类的所有父类
        while (findState.clazz != null) {
// 获取订阅者信息
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
 // 如果使用了MyEventBusIndex,将会进入到这里并获取订阅方法信息
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
  // 未使用MyEventBusIndex将会进入这里使用反射获取方法信息
                findUsingReflectionInSingleClass(findState);
            }
// 将findState.clazz设置为当前的findState.clazz的父类
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
EventBus->Register->findSubscriberMethods->findUsingInfo->findUsingReflectionInSingleClass
EventBus->Register->findSubscriberMethods->findUsingReflection->findUsingReflectionInSingleClass
  //最后都会进入此方法,在这个方法中通过反射获取带注释的方法,并将方法存入到findState.subscriberMethods
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
/ 获取该类中声明的所有方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
// 对方法进行遍历判断
        for (Method method : methods) {
            int modifiers = method.getModifiers();
// 这里会对方法的修饰符进行校验
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
              //获得方法中的@subscribe注解
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
// 获取该方法的第一个参数
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                           //将得到的方法存入到findState.subscriberMethods集合中
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

EventBus->subscribe

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
// 将所有的观察者和订阅方法封装成一个Subscription对象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 尝试从缓存中根据事件类型来获取所有的Subscription对象
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
// 指定的事件类型没有对应的观察对象的时候
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            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) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                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);
            }
        }
    }

通过看rigister方法,大体的意思是,通过放射将带有@subscribe注释的方法,反射出来,存入到final List<SubscriberMethod> subscriberMethods = new ArrayList<>();变量中
然后遍历subscriberMethods ,将subscriberMethods 变量中的方法当做value,类名当做key存入到METHOD_CACHE map集合中,然后在subscribe方法中对键值对进行注册,然后根据优先级对方法进行排序,最后在判断是否粘性,如果是粘性的,发送消息

发送方法 post

这个方法目前看是,将这个消息对象加入到队列中

 private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
//这里的currentPostingThreadState是一个ThreadLocal类型的变量,其中存储了对应于当前线程的PostingThreadState对象,该对象中存储了当前线程对应的事件列表和线程的状态信息等。
...
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); // 1
            }
        } finally {
            // 恢复当前线程的信息
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

EventBus->postSingleEvent
在这个方法中,查找到所有注册这个消息对象的 接收者

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        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->postSingleEvent->postSingleEventForEventType
在这个方法中获取了调用register方法中的缓存变量

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;
}

EventBus->postSingleEvent->postSingleEventForEventType->postToSubscription
在这个方法中,会根据带有@subscriber的方法进行对应的发送

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 {
                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(...);
    }
}

EventBus->postSingleEvent->postSingleEventForEventType->postToSubscription->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);
        }
    }

总结,Post流程大概就是根据post中传送的对象,找到执行register时的存的变量中的接收者对象的方法,然后通过反射,将post的对象发送给注册@subscription的方法。
注销的方法我就不写了,讲的应该是将接收者对象从缓存中移除

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容