EventBus用法及源码解析

EventBus用法及源码解析
目录介绍
1.EventBus简介
1.1 EventBus的三要素
1.2 EventBus的四种ThreadMode(线程模型)
1.3 EventBus怎么调用
2.EventBus使用
2.1 最简单的使用
3.EventBus注册源码解析
3.1 EventBus.getDefault()获取对象
3.2 register(this)注册源码解析
3.2.1 首先看register(this)源码
3.2.2 接下来看findSubscriberMethods(subscriberClass)里面的源码
3.2.3 接下来看findUsingInfo(subscriberClass)源码
3.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
3.3.1 subscribe(subscriber, subscriberMethod);
3.3.2 订阅者的注册过程
3.3.3 流程图
4.EventBus事件分发解析
4.1 从post方法入手
4.2 什么是PostingThreadState?
4.3 PostingThreadState怎么获得?
4.4 来看看postSingleEvent方法里做了什么
4.5 接下来看看postSingleEventForEventType方法
4.6 接下来看看postToSubscription方法
4.7 整个流程图
4.8 总结一下整个事件分发的过程
5.EventBus取消注册解析
5.1 unregister(this)方法入手
5.2 再来看看unsubscribeByEventType(subscriber, eventType)
5.3 取消注册流程图
5.4 总结一下取消注册的过程
6.总结一下EventBus的工作原理
6.1 订阅逻辑
6.2 事件发送逻辑
6.3 取消逻辑
6.4 利与弊
7.其他介绍
7.1 参考文档
7.2 其他

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

1.EventBus简介

  • 1.1 EventBus的三要素**
    • Event:事件
      可以是任意类型的对象。
    • Subscriber:事件订阅者
      在EventBus3.0之前,消息处理的方法只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他们分别代表四种线程模型。
      在EventBus3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING),四种线程模型下面会讲到。
    • Publisher:事件发布者
      可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法。可以自己实例化EventBus对象,但一般使用EventBus.getDefault()就好了,根据post函数参数的类型,会自动调用订阅相应类型事件的函数。
  • 1.2 EventBus的四种ThreadMode(线程模型)**
    • POSTING(默认):
      如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
    • MAIN:
      事件的处理会在UI线程中执行。事件处理时间不能太长,长了会ANR的。
    • BACKGROUND:
      如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
    • ASYNC:
      无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。
  • 1.3 EventBus怎么调用**
    代码如下: EventBus.getDefault().post(param);
    • 调用原理简单理解为:
    • 一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。
    • 撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。
    • 它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?

2.EventBus使用

  • 2.1 最简单的使用
  • 2.1.1 自定义一个事件类
public class MessageEvent {

}
  • 2.1.2 在需要订阅事件的地方注册事件
EventBus.getDefault().register(this);
  • 2.1.3 发送事件
EventBus.getDefault().post(messageEvent);
  • 2.1.4 处理事件
    • 3.0之后, 消息处理的方法可以随便取名
      问题: (threadMode = ThreadMode.MAIN)是做什么用的??
      需要添加一个注解@Subscribe,并且要指定线程模型
      如果没有添加,那就是默认为POSTING
@Subscribe(threadMode = ThreadMode.MAIN)
public void Hhhh(MessageEvent messageEvent) {
...
}
  • 2.1.5 取消事件订阅
EventBus.getDefault().unregister(this);

3.EventBus注册源码解析

  • 3.1 EventBus.getDefault()获取对象
  • a.先看源码:
/** 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;
}
  • b.分析
    单例模式, 使用了双重判断的方式,防止并发的问题,还能极大的提高效率
  • 3.2 register(this)注册源码解析
  • 3.2.1.首先看register(this)源码
public void register(Object subscriber) {
    //首先获取订阅者的类对象
    Class<?> subscriberClass = subscriber.getClass();
    //用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面订阅的内容。
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        //
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
  • 3.2.2.接下来看findSubscriberMethods(subscriberClass)里面的源码
    • 该方法的作用其实就是从订阅类中获取所有的订阅方法信息
    • findSubscriberMethods找出一个SubscriberMethod的集合,也就是传进来的订阅者所有的订阅的方法,接下来遍历订阅者的订阅方法来完成订阅者的订阅操作。对于SubscriberMethod(订阅方法)类中,主要就是用保存订阅方法的Method对象、线程模式、事件类型、优先级、是否是粘性事件等属性。
    • 源码分析:首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则根据 ignoreGeneratedIndex 选择如何查找订阅方法,ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex。最后,找到订阅方法后,放入缓存,以免下次继续查找。ignoreGeneratedIndex 默认就是false,可以通过EventBusBuilder来设置它的值。我们在项目中经常通过EventBus单例模式来获取默认的EventBus对象,也就是ignoreGeneratedIndex为false的情况,这种情况调用了findUsingInfo方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //首先从缓存中读取
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //是否忽略注解器生成的MyEventBusIndex类
    if (ignoreGeneratedIndex) {
        //利用反射来获取订阅类中的订阅方法信息
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息
        subscriberMethods = findUsingInfo(subscriberClass);
    }

    //在获得subscriberMethods以后,如果订阅者中不存在@Subscribe注解并且为public的订阅方法,则会抛出异常。
    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;
    }
}
//METHOD_CACHE,是一个map集合,键是class类型
Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
  • 3.2.3 接下来看findUsingInfo(subscriberClass)源码
    • 通过getSubscriberInfo方法来获取订阅者信息。在我们开始查找订阅方法的时候并没有忽略注解器为我们生成的索引MyEventBusIndex,如果我们通过EventBusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以得到订阅方法相关的信息,这个时候就不在需要通过注解进行获取订阅方法。如果没有配置MyEventBusIndex,便会执行findUsingReflectionInSingleClass方法,将订阅方法保存到findState中。最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //获取订阅者信息,没有配置MyEventBusIndex返回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.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //通过反射来查找订阅方法
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
  • 3.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
  • 3.3.1 回到
    register(this)这个方法


    Image.png
  • 3.3.2 订阅者的注册过程
    • 订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //获取订阅方法的参数类型 
    Class<?> eventType = subscriberMethod.eventType;
    //根据订阅者和订阅方法构造一个订阅事件
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //获取当前订阅事件中Subscription的List集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
    //订阅者已经注册则抛出EventBusException
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    //遍历订阅事件,找到比subscriptions中订阅事件小的位置,然后插进去
    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中
    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);
        }
    }
}
  • 3.3.3 流程图


    Image.png

4.EventBus事件分发解析

  • 4.1 从post方法入手
    首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。
/** Posts the given event to the event bus. */
public void post(Object event) {
    //获取当前线程的postingState
    PostingThreadState postingState = currentPostingThreadState.get();
    //取得当前线程的事件队列
    List<Object> eventQueue = postingState.eventQueue;
    //将该事件添加到当前的事件队列中等待分发
    eventQueue.add(event);
    if (!postingState.isPosting) {
        //判断是否是在主线程post
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        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;
        }
    }
}
  • 4.2 什么是PostingThreadState?
    PostingThreadState中包含了当前线程的事件队列,就是当前线程所有分发的事件都保存在eventQueue事件队列中以及订阅者订阅事件等信息,有了这些信息我们就可以从事件队列中取出事件分发给对应的订阅者
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();//当前线程的事件队列
    boolean isPosting;//是否有事件正在分发
    boolean isMainThread;//post的线程是否是主线程
    Subscription subscription;//订阅者
    Object event;//订阅事件
    boolean canceled;//是否取消
}
  • 4.3 PostingThreadState怎么获得?
    • ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。
    • 可以看出currentPostingThreadState的实现是一个包含了PostingThreadState的ThreadLocal对象,这样可以保证取到的都是自己线程对应的数据。
    • 我们有了PostingThreadState获取到了当前线程的事件队列,接下来就是事件分发,我们来看postSingleEvent(eventQueue.remove(0), postingState);
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};
  • 4.4 来看看postSingleEvent方法里做了什么
    eventInheritance表示是否向上查找事件的父类,它的默认值为true,可以通过在EventBusBuilder中来进行配置。当eventInheritance为true时,则通过lookupAllEventTypes找到所有的父类事件并存在List中,然后通过postSingleEventForEventType方法对事件逐一处理,接下来看看postSingleEventForEventType方法
事件分发
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //得到事件类型
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;

    //是否触发订阅了该事件(eventClass)的父类,以及接口的类的响应方法.
    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) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}
  • 4.5 接下来看看postSingleEventForEventType方法
    同步取出该事件对应的Subscription集合并遍历该集合将事件event和对应Subscription传递给postingState并调用postToSubscription方法对事件进行处理,接下来看看postToSubscription方法:
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;
}
  • 4.6 接下来看看postToSubscription方法
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING://默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,
        //不论该线程是否为主线程(UI 线程)。
            invokeSubscriber(subscription, event);
            break;
        case MAIN://在主线程中执行响应方法。
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(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);
    }
}
  • 4.7 整个流程图


    511825-20160330182550879-762466444.png
  • 4.8 总结一下整个事件分发的过程
    • 首先获取当前线程的PostingThreadState对象从而获取到当前线程的事件队列
    • 通过事件类型获取到所有订阅者集合
    • 通过反射执行订阅者中的订阅方法

5.EventBus取消注册解析

  • 5.1 unregister(this)方法入手
/** Unregisters the given subscriber from all event classes. */
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 {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
  • 5.2 再来看看unsubscribeByEventType(subscriber, eventType)
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--;
            }
        }
    }
}
  • 5.3 取消注册流程图


    Image.png
  • 5.4 总结一下取消注册的过程
    • 1、首先获取订阅者的所有订阅事件
    • 2、遍历订阅事件
    • 3、根据订阅事件获取所有的订阅了该事件的订阅者集合
    • 4、将该订阅者移除
    • 5、将步骤1中的集合中的订阅者移除

6.总结一下EventBus的工作原理

  • 6.1 订阅逻辑
    • 1、首先用register()方法注册一个订阅者
    • 2、获取该订阅者的所有订阅的方法
    • 3、根据该订阅者的所有订阅的事件类型,将订阅者存入到每个以 事件类型为key 以所有订阅者为values的map集合中
    • 4、然后将订阅事件添加到以订阅者为key 以订阅者所有订阅事件为values的map集合中
    • 5、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。
  • 6.2 事件发送逻辑
    • 1、首先获取当前线程的事件队列
    • 2、将要发送的事件添加到事件队列中
    • 3、根据发送事件类型获取所有的订阅者
    • 4、根据响应方法的执行模式,在相应线程通过反射执行订阅者的订阅方法
  • 6.3 取消逻辑
    • 1、首先通过unregister方法拿到要取消的订阅者
    • 2、得到该订阅者的所有订阅事件类型
    • 3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者
    • 4、将订阅者从步骤2的集合中移除
  • 6.4 利与弊
    • EventBus好处比较明显,它能够解耦和,将业务和视图分离,代码实现比较容易。而且3.0后,我们可以通过apt预编译找到订阅者,避免了运行期间的反射处理解析,大大提高了效率。当然EventBus也会带来一些隐患和弊端,如果滥用的话会导致逻辑的分散并造成维护起来的困难。另外大量采用EventBus代码的可读性也会变差。

7.其他介绍

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,938评论 4 42
  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 24,813评论 23 184
  • 这是日本绘本大师安野光雅唯一的人生随笔,他用看似笨拙、幼稚的方法,生动描绘了他对绘画的喜爱、对梦想的坚持和对人生的...
    流年2016阅读 667评论 0 0
  • 流水账 上班阴天,下班中雨。媳妇放假第一天,睡得呼呼滴,好可爱!整第36周,快安全上岸了,保佑哦。风平浪静的一天:...
    狸狸的守护者阅读 177评论 0 0