EventBus (四) Sticky事件

什么是Sticky事件?

关于Sticky事件有的同学可能不是很熟悉,Sticky的意思是粘性的。在Android开 发中,Sticky事件只指事件消费者在事件发布之后才注册的也能接收到该事件的特殊类型。Android中就有这样的实例,也就是Sticky Broadcast,即粘性广播。正常情况下如果发送者发送了某个广播,而接收者在这个广播发送后才注册自己的Receiver,这时接收者便无法接收到 刚才的广播,为此Android引入了StickyBroadcast,在广播发送结束后会保存刚刚发送的广播(Intent),这样当接收者注册完 Receiver后就可以接收到刚才已经发布的广播。这就使得我们可以预先处理一些事件,让有消费者时再把这些事件投递给消费者。

AndroidEventBus也提供了这样的功能,有所不同是AndroidEventBus会存储所有的Sticky事件,如果某个事件在不需 要再存储则需要手动进行移除。用户通过Sticky的形式发布事件,而消费者也需要通过Sticky的形式进行注册,当然这种注册除了可以接收 Sticky事件之外和常规的注册功能是一样的,其他类型的事件也会被正常处理。发布、接收Sticky事件的步骤有如下几步 :

1、发布Sticky事件;

EventBus.getDefault().postSticky("hello");

 

2、 某个时刻订阅者以Sticky的形式注册


public class MyReceiver {
    public MyReceiver() {
        EventBus.getDefault().registerSticky(this);
    }

    @Subscriber
    private void onStickyEvent(String info) {
        System.out.println("接收到事件 : " + info);
    }

}

当在某个时刻构造MyReceiver时就会将MyReceiver对象以Sticky的形式注册到EventBus中,此时先前发布的”hello”事件就会被MyReceiver对象接收到,因此就会执行onStickyEvent函数,在该函数中实现具体的逻辑即可。当然,不要忘了在某个时刻将MyReceiver注销,以弱引用的形式持有订阅者的功能还没有完成呐!整个过程就这样结束了~

Sticky事件的运用场景

上文中我们简单讲述了Sticky事件的基本使用步骤,这里我们以一个具体的示例来看看Sticky事件在开发中的使用场景。

在开发过程中,我们经常需要在Activity之间传值,我们的做法就是将数据塞到Intent中,并且为每个数据设置一个key。当我们传递的数 据是一个实体类时,我们的这个类还需要实现序列化接口,比如Parcelable或者Serializable。例如我们需要将一个用户对象传递到用户个 人信息展示页面。我们的常规做法是这样的:

User.java类 :

// 实体类实现序列化
public class User implements Parcelable {
        String name ;
        String phoneNum;
        // 其他字段省略

        public User(String aName) {
            name = aName ;
        }

        public User(Parcel in) {
            super(in);
            name = in.readString();
            phoneNum = in.readString();
        }
       // 代码省略

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeString(phoneNum);
        }

        public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
 }

 

然后我们要在某个Activity中将这个用户数据传递给个人信息界面ProfileActivity。代码如下 :

public class MainActivity extends Activity {

    // 某个点击事件
    @Override 
    public void onClick(View v) {
        User aUser = new User("Mr.Simple");
        aUser.phoneNum = "123456";
        // 其他数据

        Intent intent = new Intent(this, ProfileActivity.class);
        intent.putParcelable("user", aUser);
        startActivity(intent);
    }
}

 

在某个点击事件的处理函数中我们通过Intent将数据传递给ProfileActivity。我们再看看ProfileActivity从Intent中取出数据的代码。

public class ProfileActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);
        // 从Bundle中获取数据
        Bundle extraBundle = getIntent().getExtras();
        if (extraBundle != null) {
            User user = extraBundle.getParcelable("user");
        }
    }
}

 

OK,至此整个过程才算结束了。

大哥,我只是需要传个数据啊!何苦啊!
这种方式产生了很多的样板代码,也让逻辑变得更复杂,容易出错。我们再看看使用Sticky事件的实现方式。

User.java类 :

// 实体类实现序列化
public class User  {
        String name ;
        String phoneNum;
        // 其他字段省略

        public User(String aName) {
            name = aName ;
        }

        // 代码省略
 }

 

首先User类不需要实现序列化接口,避免了那些样板代码。然后在MainActivity中直接将User对象作为Sticky事件发布即可。

public class MainActivity extends Activity {

    // 某个点击事件
    @Override 
    public void onClick(View v) {
        User aUser = new User("Mr.Simple");
        aUser.phoneNum = "123456";
        // 其他数据
        // 发布Sticky事件
        EventBus.getDefault().postSticky(aUser);
        // 跳转到ProfileActivity页面
        Intent intent = new Intent(this, ProfileActivity.class);
        startActivity(intent);
    }
}

*后我们看看ProfileActivity如何接收数据。

public class ProfileActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);

        // 以Sticky的形式注册
        EventBus.getDefault().registerSticky(this);
    }

    @Subscriber
    private void onStickyEvent(User info){ // 这里实现你的逻辑即可, info即为传递过来的User对象 } }

在ProfileActivity中我们将ProfileActivity自身作为订阅者注册到总线当中,此时ProfileActivity就会 接收到上面发布的Sticky事件,这个事件对象就是User对象。此时就会触发ProfileActivity 中的receiveUser函数,info参数就是Sticky事件的那个用户信息对象,在receiveUser中实现自己的逻辑即可。

是的!我们并没有在onDestory中对订阅者进行注销,也就是没有调用EventBus的unregister()函数,这就是*新版的特性之一,也是目前唯一不需要手动注销的事件总线库。

EventBus (三) 源码解析 带你深入理解EventBus

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:【张鸿洋的博客】

上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢?

1、概述

一般使用EventBus的组件类,类似下面这种方式:

  1. public class SampleComponent extends Fragment  
  2. {
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState)  
  5.     {
  6.         super.onCreate(savedInstanceState);  
  7.         EventBus.getDefault().register(this);  
  8.     }
  9.     public void onEventMainThread(param)  
  10.     {
  11.     }
  12.     public void onEventPostThread(param)  
  13.     {
  14.     }
  15.     public void onEventBackgroundThread(param)  
  16.     {
  17.     }
  18.     public void onEventAsync(param)  
  19.     {
  20.     }
  21.     @Override  
  22.     public void onDestroy()  
  23.     {
  24.         super.onDestroy();  
  25.         EventBus.getDefault().unregister(this);  
  26.     }
  27. }

大多情况下,都会在onCreate中进行register,在onDestory中进行unregister ;

看完代码大家或许会有一些疑问:

1、代码中还有一些以onEvent开头的方法,这些方法是干嘛的呢?

在回答这个问题之前,我有一个问题,你咋不问register(this)是干嘛的呢?其实register(this)就是去当前类,遍历所有的方法,找到onEvent开头的然后进行存储。现在知道onEvent开头的方法是干嘛的了吧。

2、那onEvent后面的那些MainThread应该是什么标志吧?

嗯,是的,onEvent后面可以写四种,也就是上面出现的四个方法,决定了当前的方法*终在什么线程运行,怎么运行,可以参考上一篇博客或者细细往下看。

 

既然register了,那么肯定得说怎么调用是吧。

  1. EventBus.getDefault().post(param);

调用很简单,一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。

现在有没有觉得,撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。

那么,我告诉你,它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?

 

下面我们就去看看EventBus的register和post真面目。

2、register

EventBus.getDefault().register(this);

首先:

EventBus.getDefault()其实就是个单例,和我们传统的getInstance一个意思:

  1. /** Convenience singleton for apps using a process-wide EventBus instance. */  
  2.    public static EventBus getDefault() {  
  3.        if (defaultInstance == null) {  
  4.            synchronized (EventBus.class) {  
  5.                if (defaultInstance == null) {  
  6.                    defaultInstance = new EventBus();  
  7.                }
  8.            }
  9.        }
  10.        return defaultInstance;  
  11.    }

使用了双重判断的方式,防止并发的问题,还能*大的提高效率。

然后register应该是一个普通的方法,我们去看看:

register公布给我们使用的有4个:

  1.  public void register(Object subscriber) {  
  2.         register(subscriber, DEFAULT_METHOD_NAME, false, 0);  
  3.     }
  4.  public void register(Object subscriber, int priority) {  
  5.         register(subscriber, DEFAULT_METHOD_NAME, false, priority);  
  6.     }
  7. public void registerSticky(Object subscriber) {  
  8.         register(subscriber, DEFAULT_METHOD_NAME, true, 0);  
  9.     }
  10. public void registerSticky(Object subscriber, int priority) {  
  11.         register(subscriber, DEFAULT_METHOD_NAME, true, priority);  
  12.     }

本质上就调用了同一个:

  1. private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {  
  2.         List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
  3.                 methodName);
  4.         for (SubscriberMethod subscriberMethod : subscriberMethods) {  
  5.             subscribe(subscriber, subscriberMethod, sticky, priority);
  6.         }
  7.     }

四个参数

subscriber 是我们扫描类的对象,也就是我们代码中常见的this;

methodName 这个是写死的:“onEvent”,用于确定扫描什么开头的方法,可见我们的类中都是以这个开头。

sticky 这个参数,解释源码的时候解释,暂时不用管

priority 优先级,优先级越高,在调用的时候会越先调用。

下面开始看代码:

  1. List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
  2.                 methodName);

调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber 的class,以及methodName,返回一个List<SubscriberMethod>。

那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,*后返回一个List。下面看代码:

  1. List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {
  2.         String key = subscriberClass.getName() + ‘.’ + eventMethodName;  
  3.         List<SubscriberMethod> subscriberMethods;
  4.         synchronized (methodCache) {  
  5.             subscriberMethods = methodCache.get(key);
  6.         }
  7.         if (subscriberMethods != null) {  
  8.             return subscriberMethods;  
  9.         }
  10.         subscriberMethods = new ArrayList<SubscriberMethod>();  
  11.         Class<?> clazz = subscriberClass;
  12.         HashSet<String> eventTypesFound = new HashSet<String>();  
  13.         StringBuilder methodKeyBuilder = new StringBuilder();  
  14.         while (clazz != null) {  
  15.             String name = clazz.getName();
  16.             if (name.startsWith(“<a href=”http://lib.csdn.net /base/17″ class=”replace_word” title=”Java EE知识 库” target=”_blank” style=”color:#df3434; font-weight:bold;”>java</a>.”) || name.startsWith(“javax.”) || name.startsWith(“android.”)) {  
  17.                 // Skip system classes, this just degrades performance  
  18.                 break;  
  19.             }
  20.             // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
  21.             Method[] methods = clazz.getMethods();
  22.             for (Method method : methods) {  
  23.                 String methodName = method.getName();
  24.                 if (methodName.startsWith(eventMethodName)) {  
  25.                     int modifiers = method.getModifiers();  
  26.                     if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {  
  27.                         Class<?>[] parameterTypes = method.getParameterTypes();
  28.                         if (parameterTypes.length == 1) {  
  29.                             String modifierString = methodName.substring(eventMethodName.length());
  30.                             ThreadMode threadMode;
  31.                             if (modifierString.length() == 0) {  
  32.                                 threadMode = ThreadMode.PostThread;
  33.                             } else if (modifierString.equals(“MainThread”)) {  
  34.                                 threadMode = ThreadMode.MainThread;
  35.                             } else if (modifierString.equals(“BackgroundThread”)) {  
  36.                                 threadMode = ThreadMode.BackgroundThread;
  37.                             } else if (modifierString.equals(“Async”)) {  
  38.                                 threadMode = ThreadMode.Async;
  39.                             } else {  
  40.                                 if (skipMethodVerificationForClasses.containsKey(clazz)) {  
  41.                                     continue;  
  42.                                 } else {  
  43.                                     throw new EventBusException(“Illegal onEvent method, check for typos: ” + method);  
  44.                                 }
  45.                             }
  46.                             Class<?> eventType = parameterTypes[0];  
  47.                             methodKeyBuilder.setLength(0);  
  48.                             methodKeyBuilder.append(methodName);
  49.                             methodKeyBuilder.append(‘>’).append(eventType.getName());  
  50.                             String methodKey = methodKeyBuilder.toString();
  51.                             if (eventTypesFound.add(methodKey)) {  
  52.                                 // Only add if not already found in a sub class  
  53.                                 subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));  
  54.                             }
  55.                         }
  56.                     } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {  
  57.                         Log.d(EventBus.TAG, “Skipping method (not public, static or abstract): ” + clazz + “.”  
  58.                                 + methodName);
  59.                     }
  60.                 }
  61.             }
  62.             clazz = clazz.getSuperclass();
  63.         }
  64.         if (subscriberMethods.isEmpty()) {  
  65.             throw new EventBusException(“Subscriber ” + subscriberClass + ” has no public methods called ”  
  66.                     + eventMethodName);
  67.         } else {  
  68.             synchronized (methodCache) {  
  69.                 methodCache.put(key, subscriberMethods);
  70.             }
  71.             return subscriberMethods;  
  72.         }
  73.     }

呵,代码还真长;不过我们直接看核心部分:

22行:看到没,clazz.getMethods();去得到所有的方法:

23-62行:就开始遍历每一个方法了,去匹配封装了。

25-29行:分别判断了是否以onEvent开头,是否是public且非static和abstract方法,是否是一个参数。如果都复合,才进入封装的部分。

32-45行:也比较简单,根据方法的后缀,来确定threadMode,threadMode是个枚举类型:就四种情况。

*后在54行:将method, threadMode, eventType传入构造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,*终放回。

注意下63行:clazz = clazz.getSuperclass();可以看到,会扫描所有的父类,不仅仅是当前类。

继续回到register:

  1. for (SubscriberMethod subscriberMethod : subscriberMethods) {  
  2.             subscribe(subscriber, subscriberMethod, sticky, priority);
  3.         }

for循环扫描到的方法,然后去调用suscribe方法。

  1. // Must be called in synchronized block  
  2.    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {  
  3.        subscribed = true;  
  4.        Class<?> eventType = subscriberMethod.eventType;
  5.        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
  6.        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);  
  7.        if (subscriptions == null) {  
  8.            subscriptions = new CopyOnWriteArrayList<Subscription>();  
  9.            subscriptionsByEventType.put(eventType, subscriptions);
  10.        } else {  
  11.            for (Subscription subscription : subscriptions) {  
  12.                if (subscription.equals(newSubscription)) {  
  13.                    throw new EventBusException(“Subscriber ” + subscriber.getClass() + ” already registered to event ”  
  14.                            + eventType);
  15.                }
  16.            }
  17.        }
  18.        // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
  19.        // subscriberMethod.method.setAccessible(true);  
  20.        int size = subscriptions.size();  
  21.        for (int i = 0; i <= size; i++) {  
  22.            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {  
  23.                subscriptions.add(i, newSubscription);
  24.                break;  
  25.            }
  26.        }
  27.        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
  28.        if (subscribedEvents == null) {  
  29.            subscribedEvents = new ArrayList<Class<?>>();  
  30.            typesBySubscriber.put(subscriber, subscribedEvents);
  31.        }
  32.        subscribedEvents.add(eventType);
  33.        if (sticky) {  
  34.            Object stickyEvent;
  35.            synchronized (stickyEvents) {  
  36.                stickyEvent = stickyEvents.get(eventType);
  37.            }
  38.            if (stickyEvent != null) {  
  39.                // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)  
  40.                // –> Strange corner case, which we don’t take care of here.  
  41.                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
  42.            }
  43.        }
  44.    }

我们的subscriberMethod中保存了method, threadMode, eventType,上面已经说了;

4-17行:根据subscriberMethod.eventType,去subscriptionsByEventType去查找一个CopyOnWriteArrayList<Subscription> ,如果没有则创建。

顺便把我们的传入的参数封装成了一个:Subscription(subscriber, subscriberMethod, priority);

这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; 这个Map其实就是EventBus存储方法的地方,一定要记住!

22-28行:实际上,就是添加newSubscription;并且是按照优先级添加的。可以看到,优先级越高,会插到在当前List的前面。

30-35行:根据subscriber存储它所有的eventType ; 依然是map;key:subscriber ,value:List<eventType> ;知道就行,非核心代码,主要用于isRegister的判断。

37-47行:判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,如果有则立即发布去执行。stickyEvent其实就是我们post时的参数。

postToSubscription这个方法,我们在post的时候会介绍。

 

到此,我们register就介绍完了。

你只要记得一件事:扫描了所有的方法,把匹配的方法*终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;

eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切。

 

3、post

register完毕,知道了EventBus如何存储我们的方法了,下面看看post它又是如何调用我们的方法的。

再看源码之前,我们猜测下:register时,把方法存在subscriptionsByEventType;那么post肯定会去subscriptionsByEventType去取方法,然后调用。

下面看源码:

  1. /** Posts the given event to the event bus. */  
  2.    public void post(Object event) {  
  3.        PostingThreadState postingState = currentPostingThreadState.get();
  4.        List<Object> eventQueue = postingState.eventQueue;
  5.        eventQueue.add(event);
  6.        if (postingState.isPosting) {  
  7.            return;  
  8.        } else {  
  9.            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
  10.            postingState.isPosting = true;  
  11.            if (postingState.canceled) {  
  12.                throw new EventBusException(“Internal error. Abort state was not reset”);  
  13.            }
  14.            try {  
  15.                while (!eventQueue.isEmpty()) {  
  16.                    postSingleEvent(eventQueue.remove(0), postingState);  
  17.                }
  18.            } finally {  
  19.                postingState.isPosting = false;  
  20.                postingState.isMainThread = false;  
  21.            }
  22.        }
  23.    }

currentPostingThreadState是一个ThreadLocal类型的,里面存储了PostingThreadState;PostingThreadState包含了一个eventQueue和一些标志位。

  1. private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {  
  2.        @Override  
  3.        protected PostingThreadState initialValue() {  
  4.            return new PostingThreadState();  
  5.        }
  6.    }

把我们传入的event,保存到了当前线程中的一个变量PostingThreadState的eventQueue中。

10行:判断当前是否是UI线程。

16-18行:遍历队列中的所有的event,调用postSingleEvent(eventQueue.remove(0), postingState)方法。

这里大家会不会有疑问,每次post都会去调用整个队列么,那么不会造成方法多次调用么?

可以看到第7-8行,有个判断,就是防止该问题的,isPosting=true了,就不会往下走了。

 

下面看postSingleEvent

  1. private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {  
  2.         Class<? extends Object> eventClass = event.getClass();  
  3.         List<Class<?>> eventTypes = findEventTypes(eventClass);
  4.         boolean subscriptionFound = false;  
  5.         int countTypes = eventTypes.size();  
  6.         for (int h = 0; h < countTypes; h++) {  
  7.             Class<?> clazz = eventTypes.get(h);
  8.             CopyOnWriteArrayList<Subscription> subscriptions;
  9.             synchronized (this) {  
  10.                 subscriptions = subscriptionsByEventType.get(clazz);
  11.             }
  12.             if (subscriptions != null && !subscriptions.isEmpty()) {  
  13.                 for (Subscription subscription : subscriptions) {  
  14.                     postingState.event = event;
  15.                     postingState.subscription = subscription;
  16.                     boolean aborted = false;  
  17.                     try {  
  18.                         postToSubscription(subscription, event, postingState.isMainThread);
  19.                         aborted = postingState.canceled;
  20.                     } finally {  
  21.                         postingState.event = null;  
  22.                         postingState.subscription = null;  
  23.                         postingState.canceled = false;  
  24.                     }
  25.                     if (aborted) {  
  26.                         break;  
  27.                     }
  28.                 }
  29.                 subscriptionFound = true;  
  30.             }
  31.         }
  32.         if (!subscriptionFound) {  
  33.             Log.d(TAG, “No subscribers registered for event ” + eventClass);  
  34.             if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {  
  35.                 post(new NoSubscriberEvent(this, event));  
  36.             }
  37.         }
  38.     }

将我们的event,即post传入的实参;以及postingState传入到postSingleEvent中。

2-3 行:根据event的Class,去得到一个List<Class<?>>;其实就是得到event当前对象的Class,以及 父类和接口的Class类型;主要用于匹配,比如你传入Dog extends Dog,他会把Animal也装到该List中。

6-31行:遍历所有的Class,到subscriptionsByEventType去查找subscriptions;哈哈,熟不熟悉,还记得我们register里面把方法存哪了不?

是不是就是这个Map;

12-30行:遍历每个subscription,依次去调用postToSubscription(subscription, event, postingState.isMainThread);
这个方法就是去反射执行方法了,大家还记得在register,if(sticky)时,也会去执行这个方法。

下面看它如何反射执行:

  1. private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {  
  2.         switch (subscription.subscriberMethod.threadMode) {  
  3.         case PostThread:  
  4.             invokeSubscriber(subscription, event);
  5.             break;  
  6.         case MainThread:  
  7.             if (isMainThread) {  
  8.                 invokeSubscriber(subscription, event);
  9.             } else {  
  10.                 mainThreadPoster.enqueue(subscription, event);
  11.             }
  12.             break;  
  13.         case BackgroundThread:  
  14.             if (isMainThread) {  
  15.                 backgroundPoster.enqueue(subscription, event);
  16.             } else {  
  17.                 invokeSubscriber(subscription, event);
  18.             }
  19.             break;  
  20.         case Async:  
  21.             asyncPoster.enqueue(subscription, event);
  22.             break;  
  23.         default:  
  24.             throw new IllegalStateException(“Unknown thread mode: ” + subscription.subscriberMethod.threadMode);  
  25.         }
  26.     }

前面已经说过subscription包含了所有执行需要的东西,大致有:subscriber, subscriberMethod(method, threadMode, eventType), priority;

那么这个方法:*步根据threadMode去判断应该在哪个线程去执行该方法;
case PostThread:

  1. void invokeSubscriber(Subscription subscription, Object event) throws Error {  
  2.           subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

直接反射调用;也就是说在当前的线程直接调用该方法;

case MainThread:

首 先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription, event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方 法。说白了就是通过Handler去发送消息,然后执行的。

case BackgroundThread:

如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,*终由Eventbus中的一个线程池去调用

executorService = Executors.newCachedThreadPool();。

case Async:将任务加入到后台的一个队列,*终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。

这么说BackgroundThread和Async有什么区别呢?

BackgroundThread中的任务,一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行的控制。

Async则会动态控制并发。

 

到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~

其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。

 

4、其余方法

介绍了register和post;大家获取还能想到一个词sticky,在register中,如何sticky为true,会去stickyEvents去查找事件,然后立即去post;

那么这个stickyEvents何时进行保存事件呢?

其实evevntbus中,除了post发布事件,还有一个方法也可以:

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

和post功能类似,但是会把方法存储到stickyEvents中去;

大家再去看看EventBus中所有的public方法,无非都是一些状态判断,获取事件,移除事件的方法;没什么好介绍的,基本见名知意。

 

好了,到此我们的源码解析就结束了,希望大家不仅能够了解这些优秀框架的内部机理,更能够体会到这些框架的很多细节之处,并发的处理,很多地方,为什么它这么做等等。

EventBus (二) 使用详解——EventBus使用进阶

相关文章:

1、《EventBus使用详解(一)——初步使用EventBus》

2、《EventBus使用详解(二)——EventBus使用进阶》

 

一、概述

前一篇给大家装简单演示了EventBus的onEventMainThread()函数的接收,其实EventBus还有另外有个不同的函数,他们分别是:

1、onEvent
2、onEventMainThread
3、onEventBackgroundThread
4、onEventAsync

这四种订阅函数都是使用onEvent开头的,它们的功能稍有不同,在介绍不同之前先介绍两个概念:
告知观察者事件发生时通过EventBus.post函数实现,这个过程叫做事件的发布,观察者被告知事件发生叫做事件的接收,是通过下面的订阅函数实现的。

onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.

二、实战

1、解析

上面列出的这四个函数,关键问题在于,我们怎么指定调用哪个函数呢?

我们先研究一下,上一篇中是怎么调用的onEventMainThread函数,除了在接收端注册与反注册以后,关键问题在于新建的一个类:

新建一个类:

  1. package com.harvic.other;  
  2. public class FirstEvent {  
  3.     private String mMsg;  
  4.     public FirstEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }

发送时:

  1. EventBus.getDefault().post(new FirstEvent(“FirstEvent btn clicked”));    

接收时:

  1. public void onEventMainThread(FirstEvent event) {    
  2.     ……
  3. }

发现什么问题了没?

没错,发送时发送的是这个类的实例,接收时参数就是这个类实例。

所以!!!!!!当发过来一个消息的时候,EventBus怎么知道要调哪个函数呢,就看哪个函数传进去的参数是这个类的实例,哪个是就调哪个。那如果有两个是呢,那两个都会被调用!!!!

为了证明这个问题,下面写个例子,先看下效果

2、实例

先看看我们要实现的效果:

这次我们在上一篇的基础上,新建三个类:FirstEvent、SecondEvent、ThirdEvent,在第二个Activity中发送请求,在MainActivity中接收这三个类的实例,接收时的代码为:

  1. public void onEventMainThread(FirstEvent event) {  
  2.     Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  3. }
  4. public void onEventMainThread(SecondEvent event) {  
  5.     Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  6. }
  7. public void onEvent(ThirdEvent event) {  
  8.     Log.d(“harvic”, “OnEvent收到了消息:” + event.getMsg());  
  9. }

使用两个onEventMainThread分别接收FirstEvent实例的消息和SecondEvent实例的消息,使用onEvent接收ThirdEvent实例的消息。界面操作及结果如下:

%title插图%num

Log输出结果:

%title插图%num

可 以看到,在发送FirstEvent时,在MainActiviy中虽然有三个函数,但只有*个onEventMainThread函数的接收参数是 FirstEvent,所以会传到它这来接收。所以这里识别调用EventBus中四个函数中哪个函数,是通过参数中的实例来决定的。

因为我们是在上一篇例子的基础上完成的,所以这里的代码就不详细写了,只写改动的部分。

1、三个类

  1. package com.harvic.other;  
  2. public class FirstEvent {  
  3.     private String mMsg;  
  4.     public FirstEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }
  1. package com.harvic.other;  
  2. public class SecondEvent{  
  3.     private String mMsg;  
  4.     public SecondEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = “MainEvent:”+msg;  
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }
  1. package com.harvic.other;  
  2. public class ThirdEvent {  
  3.     private String mMsg;  
  4.     public ThirdEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }

2、发送

然后在SecondActivity中新建三个按钮,分别发送不同的类的实例,代码如下:

  1. package com.harvic.tryeventbus2;  
  2. import com.harvic.other.FirstEvent;  
  3. import com.harvic.other.SecondEvent;  
  4. import com.harvic.other.ThirdEvent;  
  5. import de.greenrobot.event.EventBus;  
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. public class SecondActivity extends Activity {  
  11.     private Button btn_FirstEvent, btn_SecondEvent, btn_ThirdEvent;  
  12.     @Override  
  13.     protected void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         setContentView(R.layout.activity_second);
  16.         btn_FirstEvent = (Button) findViewById(R.id.btn_first_event);
  17.         btn_SecondEvent = (Button) findViewById(R.id.btn_second_event);
  18.         btn_ThirdEvent = (Button) findViewById(R.id.btn_third_event);
  19.         btn_FirstEvent.setOnClickListener(new View.OnClickListener() {  
  20.             @Override  
  21.             public void onClick(View v) {  
  22.                 // TODO Auto-generated method stub  
  23.                 EventBus.getDefault().post(
  24.                         new FirstEvent(“FirstEvent btn clicked”));  
  25.             }
  26.         });
  27.         btn_SecondEvent.setOnClickListener(new View.OnClickListener() {  
  28.             @Override  
  29.             public void onClick(View v) {  
  30.                 // TODO Auto-generated method stub  
  31.                 EventBus.getDefault().post(
  32.                         new SecondEvent(“SecondEvent btn clicked”));  
  33.             }
  34.         });
  35.         btn_ThirdEvent.setOnClickListener(new View.OnClickListener() {  
  36.             @Override  
  37.             public void onClick(View v) {  
  38.                 // TODO Auto-generated method stub  
  39.                 EventBus.getDefault().post(
  40.                         new ThirdEvent(“ThirdEvent btn clicked”));  
  41.             }
  42.         });
  43.     }
  44. }

3、接收

在 MainActivity中,除了注册与注册,我们利用onEventMainThread(FirstEvent event)来接收来自FirstEvent的消息,使用onEventMainThread(SecondEvent event)接收来自SecondEvent 实例的消息,使用onEvent(ThirdEvent event) 来接收ThirdEvent 实例的消息。

  1. package com.harvic.tryeventbus2;  
  2. import com.harvic.other.FirstEvent;  
  3. import com.harvic.other.SecondEvent;  
  4. import com.harvic.other.ThirdEvent;  
  5. import de.greenrobot.event.EventBus;  
  6. import android.app.Activity;  
  7. import android.content.Intent;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10. import android.view.Menu;  
  11. import android.view.MenuItem;  
  12. import android.view.View;  
  13. import android.widget.Button;  
  14. import android.widget.TextView;  
  15. public class MainActivity extends Activity {  
  16.     Button btn;
  17.     TextView tv;
  18.     EventBus eventBus;
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);
  23.         EventBus.getDefault().register(this);  
  24.         btn = (Button) findViewById(R.id.btn_try);
  25.         btn.setOnClickListener(new View.OnClickListener() {  
  26.             @Override  
  27.             public void onClick(View v) {  
  28.                 // TODO Auto-generated method stub  
  29.                 Intent intent = new Intent(getApplicationContext(),  
  30.                         SecondActivity.class);  
  31.                 startActivity(intent);
  32.             }
  33.         });
  34.     }
  35.     public void onEventMainThread(FirstEvent event) {  
  36.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  37.     }
  38.     public void onEventMainThread(SecondEvent event) {  
  39.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  40.     }
  41.     public void onEvent(ThirdEvent event) {  
  42.         Log.d(“harvic”, “OnEvent收到了消息:” + event.getMsg());  
  43.     }
  44.     @Override  
  45.     protected void onDestroy() {  
  46.         // TODO Auto-generated method stub  
  47.         super.onDestroy();  
  48.         EventBus.getDefault().unregister(this);  
  49.     }
  50. }

到这里,代码就结束 了,从上面的代码,我们可以看到,EventBus是怎么接收消息的,是根据参数中类的实例的类型的判定的,所以当如果我们在接收时,同一个类的实例参数有两个函数来接收会怎样?答案是,这两个函数都会执行,下面实验一下:

在MainActivity中接收时,我们在接收SecondEvent时,在上面onEventMainThread基础上另加一个onEventBackgroundThread和onEventAsync,即下面的代码:

  1. //SecondEvent接收函数一  
  2. public void onEventMainThread(SecondEvent event) {  
  3.     Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  4. }
  5. //SecondEvent接收函数二  
  6. public void onEventBackgroundThread(SecondEvent event){  
  7.     Log.d(“harvic”, “onEventBackground收到了消息:” + event.getMsg());  
  8. }
  9. //SecondEvent接收函数三  
  10. public void onEventAsync(SecondEvent event){  
  11.     Log.d(“harvic”, “onEventAsync收到了消息:” + event.getMsg());  
  12. }

完整的代码在这里:

  1. package com.harvic.tryeventbus2;  
  2. import com.harvic.other.FirstEvent;  
  3. import com.harvic.other.SecondEvent;  
  4. import com.harvic.other.ThirdEvent;  
  5. import de.greenrobot.event.EventBus;  
  6. import android.app.Activity;  
  7. import android.content.Intent;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10. import android.view.Menu;  
  11. import android.view.MenuItem;  
  12. import android.view.View;  
  13. import android.widget.Button;  
  14. import android.widget.TextView;  
  15. public class MainActivity extends Activity {  
  16.     Button btn;
  17.     TextView tv;
  18.     EventBus eventBus;
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);
  23.         EventBus.getDefault().register(this);  
  24.         btn = (Button) findViewById(R.id.btn_try);
  25.         btn.setOnClickListener(new View.OnClickListener() {  
  26.             @Override  
  27.             public void onClick(View v) {  
  28.                 // TODO Auto-generated method stub  
  29.                 Intent intent = new Intent(getApplicationContext(),  
  30.                         SecondActivity.class);  
  31.                 startActivity(intent);
  32.             }
  33.         });
  34.     }
  35.     public void onEventMainThread(FirstEvent event) {  
  36.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  37.     }
  38.     //SecondEvent接收函数一  
  39.     public void onEventMainThread(SecondEvent event) {  
  40.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  41.     }
  42.     //SecondEvent接收函数二  
  43.     public void onEventBackgroundThread(SecondEvent event){  
  44.         Log.d(“harvic”, “onEventBackground收到了消息:” + event.getMsg());  
  45.     }
  46.     //SecondEvent接收函数三  
  47.     public void onEventAsync(SecondEvent event){  
  48.         Log.d(“harvic”, “onEventAsync收到了消息:” + event.getMsg());  
  49.     }
  50.     public void onEvent(ThirdEvent event) {  
  51.         Log.d(“harvic”, “OnEvent收到了消息:” + event.getMsg());  
  52.     }
  53.     @Override  
  54.     protected void onDestroy() {  
  55.         // TODO Auto-generated method stub  
  56.         super.onDestroy();  
  57.         EventBus.getDefault().unregister(this);  
  58.     }
  59. }

经过上面的分析,当发送SecondEvent实例的消息过来的时候,这三个函数会同时接收到并各自执行,所以当点击Second Event这个button的时候,会出现下面的结果:

%title插图%num

好啦,这篇就到了,讲来讲去就是说一个问题:消息的接收是根据参数中的类名来决定执行哪一个的;

EventBus (一) 使用详解——初步使用EventBus

目录(?)[+]

前言:EventBus是上周项目中用到的,网上的文章大都一样,或者过时,有用的没几篇,经过琢磨,请教他人,也终于弄清楚点眉目,记录下来分享给大家。

 

相关文章:

1、《EventBus使用详解(一)——初步使用EventBus》

2、《EventBus使用详解(二)——EventBus使用进阶》

 

一、概述

EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
1、下载EventBus的类库
源码:https://github.com/greenrobot/EventBus

2、基本使用

(1)自定义一个类,可以是空类,比如:

  1. public class AnyEventType {  
  2.      public AnyEventType(){}  
  3.  }

(2)在要接收消息的页面注册:

  1. eventBus.register(this);  

(3)发送消息

  1. eventBus.post(new AnyEventType event);  

(4)接受消息的页面实现(共有四个函数,各功能不同,这是其中之一,可以选择性的实现,这里先实现一个):

  1. public void onEvent(AnyEventType event) {}  

(5)解除注册

  1. eventBus.unregister(this);  

顺序就是这么个顺序,可真正让自己写,估计还是云里雾里的,下面举个例子来说明下。

首先,在EventBus中,获取实例的方法一般是采用EventBus.getInstance()来获取默认的EventBus实例,当然你也可以new一个又一个,个人感觉还是用默认的比较好,以防出错。

二、实战

先给大家看个例子:

当击btn_try按钮的时候,跳到第二个Activity,当点击第二个activity上面的First Event按钮的时候向*个Activity发送消息,当*个Activity收到消息后,一方面将消息Toast显示,一方面放入textView中显示。

%title插图%num

按照下面的步骤,下面来建这个工程:

1、基本框架搭建

想必大家从一个Activity跳转到第二个Activity的程序应该都会写,这里先稍稍把两个Activity跳转的代码建起来。后面再添加EventBus相关的玩意。

MainActivity布局(activity_main.xml)

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     android:orientation=“vertical”>  
  6.     <Button   
  7.         android:id=“@+id/btn_try”  
  8.         android:layout_width=“match_parent”  
  9.         android:layout_height=“wrap_content”  
  10.         android:text=“btn_bty”/>  
  11.     <TextView   
  12.         android:id=“@+id/tv”  
  13.         android:layout_width=“wrap_content”  
  14.         android:layout_height=“match_parent”/>  
  15. </LinearLayout>  

新建一个Activity,SecondActivity布局(activity_second.xml)

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     android:orientation=“vertical”  
  6.     tools:context=“com.harvic.try_eventbus_1.SecondActivity” >  
  7.     <Button   
  8.         android:id=“@+id/btn_first_event”  
  9.         android:layout_width=“match_parent”  
  10.         android:layout_height=“wrap_content”  
  11.         android:text=“First Event”/>  
  12. </LinearLayout>  

MainActivity.java (点击btn跳转到第二个Activity)

  1. public class MainActivity extends Activity {  
  2.     Button btn;
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);
  7.         btn = (Button) findViewById(R.id.btn_try);
  8.         btn.setOnClickListener(new View.OnClickListener() {  
  9.             @Override  
  10.             public void onClick(View v) {  
  11.                 // TODO Auto-generated method stub  
  12.                 Intent intent = new Intent(getApplicationContext(),  
  13.                         SecondActivity.class);  
  14.                 startActivity(intent);
  15.             }
  16.         });
  17.     }
  18. }

到这,基本框架就搭完了,下面开始按步骤使用EventBus了。

2、新建一个类FirstEvent

  1. package com.harvic.other;  
  2. public class FirstEvent {  
  3.     private String mMsg;  
  4.     public FirstEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }

这个类很简单,构造时传进去一个字符串,然后可以通过getMsg()获取出来。

3、在要接收消息的页面注册EventBus:

在上面的GIF图片的演示中,大家也可以看到,我们是要在MainActivity中接收发过来的消息的,所以我们在MainActivity中注册消息。

通过我们会在OnCreate()函数中注册EventBus,在OnDestroy()函数中反注册。所以整体的注册与反注册的代码如下:

  1. package com.example.tryeventbus_simple;  
  2. import com.harvic.other.FirstEvent;  
  3. import de.greenrobot.event.EventBus;  
  4. import android.app.Activity;  
  5. import android.content.Intent;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.TextView;  
  11. import android.widget.Toast;  
  12. public class MainActivity extends Activity {  
  13.     Button btn;
  14.     TextView tv;
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);
  19.                 //注册EventBus  
  20.         EventBus.getDefault().register(this);  
  21.         btn = (Button) findViewById(R.id.btn_try);
  22.         tv = (TextView)findViewById(R.id.tv);
  23.         btn.setOnClickListener(new View.OnClickListener() {  
  24.             @Override  
  25.             public void onClick(View v) {  
  26.                 // TODO Auto-generated method stub  
  27.                 Intent intent = new Intent(getApplicationContext(),  
  28.                         SecondActivity.class);  
  29.                 startActivity(intent);
  30.             }
  31.         });
  32.     }
  33.     @Override  
  34.     protected void onDestroy(){  
  35.         super.onDestroy();  
  36.         EventBus.getDefault().unregister(this);//反注册EventBus  
  37.     }
  38. }

4、发送消息

发送消息是使用EventBus中的Post方法来实现发送的,发送过去的是我们新建的类的实例!

  1. EventBus.getDefault().post(new FirstEvent(“FirstEvent btn clicked”));  

完整的SecondActivity.java的代码如下:

  1. package com.example.tryeventbus_simple;  
  2. import com.harvic.other.FirstEvent;  
  3. import de.greenrobot.event.EventBus;  
  4. import android.app.Activity;  
  5. import android.os.Bundle;  
  6. import android.view.View;  
  7. import android.widget.Button;  
  8. public class SecondActivity extends Activity {  
  9.     private Button btn_FirstEvent;  
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_second);
  14.         btn_FirstEvent = (Button) findViewById(R.id.btn_first_event);
  15.         btn_FirstEvent.setOnClickListener(new View.OnClickListener() {  
  16.             @Override  
  17.             public void onClick(View v) {  
  18.                 // TODO Auto-generated method stub  
  19.                 EventBus.getDefault().post(
  20.                         new FirstEvent(“FirstEvent btn clicked”));  
  21.             }
  22.         });
  23.     }
  24. }

5、接收消息

接收消息时,我们使用EventBus中*常用的onEventMainThread()函数来接收消息,具体为什么用这个,我们下篇再讲,这里先给大家一个初步认识,要先能把EventBus用起来先。

在MainActivity中重写onEventMainThread(FirstEvent event),参数就是我们自己定义的类:

在收到Event实例后,我们将其中携带的消息取出,一方面Toast出去,一方面传到TextView中;

  1. public void onEventMainThread(FirstEvent event) {  
  2.     String msg = “onEventMainThread收到了消息:” + event.getMsg();  
  3.     Log.d(“harvic”, msg);  
  4.     tv.setText(msg);
  5.     Toast.makeText(this, msg, Toast.LENGTH_LONG).show();  
  6. }

完整的MainActiviy代码如下:

  1. package com.example.tryeventbus_simple;  
  2. import com.harvic.other.FirstEvent;  
  3. import de.greenrobot.event.EventBus;  
  4. import android.app.Activity;  
  5. import android.content.Intent;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.TextView;  
  11. import android.widget.Toast;  
  12. public class MainActivity extends Activity {  
  13.     Button btn;
  14.     TextView tv;
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);
  19.         EventBus.getDefault().register(this);  
  20.         btn = (Button) findViewById(R.id.btn_try);
  21.         tv = (TextView)findViewById(R.id.tv);
  22.         btn.setOnClickListener(new View.OnClickListener() {  
  23.             @Override  
  24.             public void onClick(View v) {  
  25.                 // TODO Auto-generated method stub  
  26.                 Intent intent = new Intent(getApplicationContext(),  
  27.                         SecondActivity.class);  
  28.                 startActivity(intent);
  29.             }
  30.         });
  31.     }
  32.     public void onEventMainThread(FirstEvent event) {  
  33.         String msg = “onEventMainThread收到了消息:” + event.getMsg();  
  34.         Log.d(“harvic”, msg);  
  35.         tv.setText(msg);
  36.         Toast.makeText(this, msg, Toast.LENGTH_LONG).show();  
  37.     }
  38.     @Override  
  39.     protected void onDestroy(){  
  40.         super.onDestroy();  
  41.         EventBus.getDefault().unregister(this);  
  42.     }
  43. }

好了,到这,基本上算初步把EventBus用起来了,下篇再讲讲EventBus的几个函数,及各个函数间是如何识别当前如何调用哪个函数的。

EventBus使用详解

前言:EventBus出来已经有一段时间了,github上面也有很多开源项目中使用了EventBus。所以抽空学习顺便整理了一下。目前EventBus*新版本是3.0,所以本文是基于EventBus3.0的。

相关文章
EventBus使用详解
EventBus源码解析

概述

EventBus是针一款对Android的发布/订阅事件总线。它可以让我们很轻松的实现在Android各个组件之间传递消息,并且代码的可读性更好,耦合度更低。

如何使用

(1)首先需要定义一个消息类,该类可以不继承任何基类也不需要实现任何接口。如:

public class MessageEvent {
    ......
}

(2)在需要订阅事件的地方注册事件

EventBus.getDefault().register(this);

(3)产生事件,即发送消息

EventBus.getDefault().post(messageEvent);

(4)处理消息

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

在3.0之前,EventBus还没有使用注解方式。消息处理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分别代表四种线程模型。而在3.0之后,消息处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为PostThread),四种线程模型,下面会讲到。
注意,事件处理函数的访问权限必须为public,否则会报异常。

(5)取消消息订阅

EventBus.getDefault().unregister(this);

有何优点

采用消息发布/订阅的一个很大的优点就是代码的简洁性,并且能够有效地降低消息发布者和订阅者之间的耦合度。
举个例子,比如有两个界面,ActivityA和ActivityB,从ActivityA界面跳转到ActivityB界面后,ActivityB要给ActivityA发送一个消息,ActivityA收到消息后在界面上显示出来。我们*先想到的方法就是使用广播,使用广播实现此需求的代码如下:
首先需要在ActivityA中定义一个广播接收器:

public class MessageBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        mMessageView.setText("Message from SecondActivity:" + intent.getStringExtra("message"));
    }
}

还需要在onCreate()方法中注册广播接收器:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //注册事件
    EventBus.getDefault().register(this);
    //注册广播
    IntentFilter intentFilter = new IntentFilter("message_broadcast");
    mBroadcastReceiver = new MessageBroadcastReceiver();
    registerReceiver(mBroadcastReceiver, intentFilter);
    ......
}

然后在onDestory()方法中取消注册广播接收器:

@Override
protected void onDestroy() {
    super.onDestroy();
    ......
    //取消广播注册
    unregisterReceiver(mBroadcastReceiver);
}

*后我们需要在ActivityB界面中发送广播消息:

findViewById(R.id.send_broadcast).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String message = mMessageET.getText().toString();
        if(TextUtils.isEmpty(message)) {
            message = "defaule message";
        }
        Intent intent = new Intent();
        intent.setAction("message_broadcast");
        intent.putExtra("message", message);
        sendBroadcast(intent);
    }
});

看着上面的实现代码,感觉也没什么不妥,挺好的!下面对比看下使用EventBus如何实现。
根据文章*前面所讲的EventBus使用步骤,首先我们需要定义一个消息事件类:

public class MessageEvent {

    private String message;

    public MessageEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

在ActivityA界面中我们首先需要注册订阅事件:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //注册事件
    EventBus.getDefault().register(this);
    ......
}

然后在onDestory()方法中取消订阅:

@Override
protected void onDestroy() {
    super.onDestroy();
    //取消事件注册
    EventBus.getDefault().unregister(this);
}

当然还要定义一个消息处理的方法:

@Subscribe(threadMode = ThreadMode.MainThread)
public void onShowMessageEvent(MessageEvent messageEvent) {
    mMessageView.setText("Message from SecondActivity:" + messageEvent.getMessage());
}

至此,消息订阅者我们已经定义好了,我们还需要在ActivityB中发布消息:

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String message = mMessageET.getText().toString();
        if(TextUtils.isEmpty(message)) {
            message = "defaule message";
        }
        EventBus.getDefault().post(new MessageEvent(message));
    }
});

对比代码一看,有人会说了,这尼玛有什么区别嘛!说好的简洁呢?哥们,别着急嘛!我这里只是举了个简单的例子,仅仅从该例子来看,EventBus的优势没有体现出来。现在我将需求稍微改一下,ActivityA收到消息后,需要从网络服务器获取数据并将数据展示出来。如果使用广播,ActivityA中广播接收器代码应该这么写:

public class MessageBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //从服务器上获取数据
                ......
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //将获取的数据展示在界面上
                        ......
                    }
                });
            }
        }).start();
    }
}

看到这段代码,不知道你何感想,反正我是看着很不爽,嵌套层次太多,完全违反了Clean Code的原则。那使用EventBus来实现又是什么样呢?我们看一下。

@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onGetDataEvent(MessageEvent messageEvent) {
    //从服务器上获取数据
    ......
    EventBus.getDefault().post(new ShowMessageEvent());
}

@Subscribe(threadMode = ThreadMode.MainThread)
public void onShowDataEvent(ShowMessageEvent showMessageEvent) {
    //将获取的数据展示在界面上
    ......
}

对比一下以上两段代码就能很明显的感觉到EventBus的优势,代码简洁、层次清晰,大大提高了代码的可读性和可维护性。我这只是简单的加了一个小需求而已,随着业务越来越复杂,使用EventBus的优势愈加明显。

常用API介绍

线程模型

在EventBus的事件处理函数中需要指定线程模型,即指定事件处理函数运行所在的想线程。在上面我们已经接触到了EventBus的四种线程模型。那他们有什么区别呢?
在EventBus中的观察者通常有四种线程模型,分别是PostThread(默认)、MainThread、BackgroundThread与Async。

  • PostThread:如果使用事件处理函数指定了线程模型为PostThread,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为PostThread的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
  • MainThread:如果使用事件处理函数指定了线程模型为MainThread,那么不论事件是在哪个线程中发布出来的,该事件处理函数都会在UI线程中执行。该方法可以用来更新UI,但是不能处理耗时操作。
  • BackgroundThread:如果使用事件处理函数指定了线程模型为BackgroundThread,那么如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
  • Async:如果使用事件处理函数指定了线程模型为Async,那么无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行。同样,此事件处理函数中禁止进行UI更新操作。

为了验证以上四个方法,我写了个小例子。

@Subscribe(threadMode = ThreadMode.PostThread)
public void onMessageEventPostThread(MessageEvent messageEvent) {
    Log.e("PostThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.MainThread)
public void onMessageEventMainThread(MessageEvent messageEvent) {
    Log.e("MainThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
    Log.e("BackgroundThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.Async)
public void onMessageEventAsync(MessageEvent messageEvent) {
    Log.e("Async", Thread.currentThread().getName());
}

分别使用上面四个方法订阅同一事件,打印他们运行所在的线程。首先我们在UI线程中发布一条MessageEvent的消息,看下日志打印结果是什么。

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e("postEvent", Thread.currentThread().getName());
            EventBus.getDefault().post(new MessageEvent());
        }
    });

打印结果如下:

2689-2689/com.lling.eventbusdemo E/postEvent﹕ main
2689-2689/com.lling.eventbusdemo E/PostThread﹕ main
2689-3064/com.lling.eventbusdemo E/Async﹕ pool-1-thread-1
2689-2689/com.lling.eventbusdemo E/MainThread﹕ main
2689-3065/com.lling.eventbusdemo E/BackgroundThread﹕ pool-1-thread-2

从日志打印结果可以看出,如果在UI线程中发布事件,则线程模型为PostThread的事件处理函数也执行在UI线程,与发布事件的线程一致。线程模型为Async的事件处理函数执行在名字叫做pool-1-thread-1的新的线程中。而MainThread的事件处理函数执行在UI线程,BackgroundThread的时间处理函数执行在名字叫做pool-1-thread-2的新的线程中。

我们再看看在子线程中发布一条MessageEvent的消息时,会有什么样的结果。

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.e("postEvent", Thread.currentThread().getName());
                    EventBus.getDefault().post(new MessageEvent());
                }
            }).start();
        }
    });

打印结果如下:

3468-3945/com.lling.eventbusdemo E/postEvent﹕ Thread-125
3468-3945/com.lling.eventbusdemo E/PostThread﹕ Thread-125
3468-3945/com.lling.eventbusdemo E/BackgroundThread﹕ Thread-125
3468-3946/com.lling.eventbusdemo E/Async﹕ pool-1-thread-1
3468-3468/com.lling.eventbusdemo E/MainThread﹕ main

从日志打印结果可以看出,如果在子线程中发布事件,则线程模型为PostThread的事件处理函数也执行在子线程,与发布事件的线程一致(都是Thread-125)。BackgroundThread事件模型也与发布事件在同一线程执行。Async则在一个名叫pool-1-thread-1的新线程中执行。MainThread还是在UI线程中执行。

上面一个例子充分验证了指定不同线程模型的事件处理方法执行所在的线程。

黏性事件

除了上面讲的普通事件外,EventBus还支持发送黏性事件。何为黏性事件呢?简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似。具体用法如下:

订阅黏性事件:

EventBus.getDefault().register(StickyModeActivity.this);

黏性事件处理函数:

@Subscribe(sticky = true)
public void XXX(MessageEvent messageEvent) {
    ......
}

发送黏性事件:

EventBus.getDefault().postSticky(new MessageEvent("test"));

处理消息事件以及取消订阅和上面方式相同。

看个简单的黏性事件的例子,为了简单起见我这里就在一个Activity里演示了。

Activity代码:

public class StickyModeActivity extends AppCompatActivity {

    int index = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sticky_mode);
        findViewById(R.id.post).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().postSticky(new MessageEvent("test" + index++));
            }
        });
        findViewById(R.id.regist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().registerSticky(StickyModeActivity.this);
            }
        });

        findViewById(R.id.unregist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().unregister(StickyModeActivity.this);
            }
        });

    }

    @Subscribe(threadMode = ThreadMode.PostThread, sticky = true)
    public void onMessageEventPostThread(MessageEvent messageEvent) {
        Log.e("PostThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.MainThread, sticky = true)
    public void onMessageEventMainThread(MessageEvent messageEvent) {
        Log.e("MainThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.BackgroundThread, sticky = true)
    public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
        Log.e("BackgroundThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.Async, sticky = true)
    public void onMessageEventAsync(MessageEvent messageEvent) {
        Log.e("Async", messageEvent.getMessage());
    }

}

布局代码activity_sticky_mode.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.lling.eventbusdemo.StickyModeActivity">

    <Button
        android:id="@+id/post"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Post"/>

    <Button
        android:id="@+id/regist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Regist"/>

    <Button
        android:id="@+id/unregist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UnRegist"/>

</LinearLayout>

代码很简单,界面上三个按钮,一个用来发送黏性事件,一个用来订阅事件,还有一个用来取消订阅的。首先在未订阅的情况下点击发送按钮发送一个黏性事件,然后点击订阅,会看到日志打印结果如下:

15246-15246/com.lling.eventbusdemo E/PostThread﹕ test0
15246-15391/com.lling.eventbusdemo E/Async﹕ test0
15246-15246/com.lling.eventbusdemo E/MainThread﹕ test0
15246-15393/com.lling.eventbusdemo E/BackgroundThread﹕ test0

这就是粘性事件,能够收到订阅之前发送的消息。但是它只能收到*新的一次消息,比如说在未订阅之前已经发送了多条黏性消息了,然后再订阅只能收到*近的一条消息。这个我们可以验证一下,我们连续点击5次POST按钮发送5条黏性事件,然后再点击REGIST按钮订阅,打印结果如下:

6980-6980/com.lling.eventbusdemo E/PostThread﹕ test4
6980-6980/com.lling.eventbusdemo E/MainThread﹕ test4
6980-7049/com.lling.eventbusdemo E/Async﹕ test4
6980-7048/com.lling.eventbusdemo E/BackgroundThread﹕ test4

由打印结果可以看出,确实是只收到*近的一条黏性事件。

Android 线程间通信全部用广播,有什么坏处吗?

感觉广播比 Handler 方便多了,直接就 send,不用先传个实例才能回话

8 条回复    2021-01-08 10:38:47 +08:00
MaL
    1

MaL   92 天前 via Android

如果是一般广播的话,那就太重了,可以用本地广播或 eventbus
Helsing
    2

Helsing   92 天前 via iPhone

尽量少用吧,要不*后到处都是满天飞的广播,看的头大
towry
    3

towry   92 天前

不要只考虑写的时候有多爽,要考虑以后容不容易修改 /调试
wolegequ
    4

wolegequ   92 天前

能用就行,产品有人用再说. 某宝的 app 卡成翔也没见他们优化
kop1989
    5

kop1989   92 天前

软件开发层面就是不好维护。
广播相当于是一个低耦合链接。
假设你有需求要修改某个广播的信息格式或者传参数量。
这时候编译器帮不了你,你很难确认你发送端和接收端都改对了 /改全了。只能不断的测试。

而且细节处理不好还会有安全性问题。

k10ndike
    6

k10ndike   92 天前   ❤️ 1

可以用 LiveData
jinhan13789991
    7

jinhan13789991   92 天前

我目前是维护很多 LiveData,说白了就是另一种广播。
只不过发送和订阅都抽成了独立的接口。优点就是不会混乱,缺点就是每次有新的需求就要再写一个专门的方法。
后面在考虑抽成一个通用的方法,通过枚举保证唯一。
BrokenVns
    8

BrokenVns   92 天前   ❤️ 2

广播是基于异步 binder 的,异步 binder 存在以下小问题:
1.能够传递的数据量是同步 binder 的一半( 0.5M-4K ).
2.异步 binder 的消息传递优先级不高,可能会出现广播接收延迟问题,这种现象在 binder 流量大的老机器更明显。
3.你不知道异步 binder 传递是否成功了,碰到过通信失败而引发的 ANR 问题。
广播自身也有些小问题,比如广播要先发送给 AMS 再由 AMS 进行分发,跨了两次进程,广播是透明的等。

少量使用动态广播,可以减少开发量。
大量使用异步通信的话,还是建议使用 Looper-Handler 、开源库或者造轮子