android EventBus从应用到解剖

HzhiBo · · 335 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

        从开始写博客到现在也有一年多了,慢慢的发觉写博客的过程中让我对知识点的梳理更有条理性和深入性,就博客而言面向的是读者,而一篇能吸引读者感兴趣,不仅仅是内容的本身,对于排版,内容的层次性、条理性都要去考虑。

应用场景

        在app中经常有这么一个模块:个人中心 这个模块一般使用fragment,而这个页面有个用户头像, 当用户修改头像关闭修改页面的时候,一般我们怎么去更新用户头像呢?
        方案:
                1、在fragment中的onResume中更新用户头像
                2、利用广播更新用户头像
                3、采用EventBus更新用户头像

        以上的三种方法都能实现我们的需求,第一种方案:我们知道onResume在页面每次出现在屏幕都会执行该方法,但是并不是每次在显示个人中心用户头像都有修改,造成了不必要的执行。因为个人中心有帮助、设置等跳转页面,在返回个人中心是不需要更新用户头像的。第二、三种方案思路是一样的,采用订阅发布、一对多关系。EventBus的使用更加的方便以及代码的耦合低。

EventBus使用

        添加依赖库:compile 'de.greenrobot:eventbus:3.0.0-beta1'

        使用三步骤:在使用的地方(activity)注册eventBus->实现事件处理函数(方法名自取,可指定线程)-> 发送消息

package lixiaoqian.com.eventbus;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;

public class MainActivity extends AppCompatActivity {

    private TextView tv1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);  //注册
        tv1 =(TextView)findViewById(R.id.info);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getApplicationContext(),
                        LoginActivity.class);
                startActivity(intent);
            }
        });
    }

    public void registerUser(View v){
        Intent intent = new Intent(getApplicationContext(),
                RegistActivity.class);
        startActivity(intent);
    }

    @Subscribe(threadMode = ThreadMode.MainThread)  //指定ui线程
    public void onEventMainThread(LoginSuccessdEvent event){ //接受处理事件
        String msg = event.getMsg();
        tv1.setText("已登录,当前账号"+msg);//获取事件中传递的参数
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}

实体消息类:

public class LoginSuccessdEvent { //承载消息的实体类
    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    private String msg;
    public LoginSuccessdEvent(String msg){
        this.msg=msg;
    }

}

      在activity中,onCreate进行注册,同时在onEventMainThread(形参)方法中接受eventbus的消息,进行执行,而指定该事件处理函数在什么线程执行,eventBus也给我们提供了对应的操作---> threadMode

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

    发送消息:

  EventBus.getDefault().post(new LoginSuccessdEvent("开始执行了"));

    就这么简单一行代码,到这里我们的eventBus的使用就已经结束了,那么可能有些人会问?

  • eventBus的消息是如何过滤的?不可能消息发送所有的接收事件都执行吧!
  • eventBus是怎么找到我定义好的接收方法,并执行呢?

    消息的过滤是通过事件处理中传递的信息进行匹配的,只有相同类型参数的接收事件才会被执行。那对于eventBus是如何执行我们的方法,其原理是采用反射机制,得到所有的接收事件,然后遍历匹配执行。

EventBus源码解析

    1、register方法中是怎么执行的?

    /**
     * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
     * are no longer interested in receiving events.
     * <p/>
     * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
     * The {@link Subscribe} annotation also allows configuration like {@link
     * ThreadMode} and priority.
     */
    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//获取所有注解的接收事件
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {//遍历接收事件
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

    这个register的注释说:只有注册这个才能正常的接收消息事件,在不使用的时候要调用unregister,接收事件的方法必须要用@Subscribe同时也可进行参数配置ThreadMode和priority。方法中有两个要点 获取所有的消息接收事件,遍历消息接收事件。接着往下看findSubscriberMethods是如何获取事件?

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        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.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

    很明显,事件的获取分三步,从缓存获取,利用反射机制得到方法,还有另一种暂不去深究。接着往下看findUsingReflection

    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

    代码很短,实质调用了findUsingReflectionInsingleClass,也是具体的逻辑处理。往下看

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) {//遍历方法并进行筛选。通过注解@Subscribe进行赛选
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);//获取注解
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) { //避免重复添加方法
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            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");
            }
        }
    }

    可以看出,最后的接收事件都会被添加到一个FindState的List中,那么eventBus。getDefault。post也不难猜出它是如何执行事件的,通过遍历list中的所有方法,比对形参是否一致而去执行。

    2、post方法解析

    /** Posts the given event to the event bus. */
    public void post(Object event) { 
        //将event添加到postingState中的eventQueue队列中
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);
        
        if (!postingState.isPosting) {
            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;
            }
        }
    }

    看注释,接着往下看核心部分:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //该eventInheritance上面有提到,默认为true,即EventBus会考虑事件的继承树
    //如果事件继承自父类,那么父类也会作为事件被发送
    if (eventInheritance) {
        //查找该事件的所有父类
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        //遍历所有事件
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    //如果没找到订阅该事件的订阅者
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

       对于一个事件,默认地会搜索出它的父类,并且把父类也作为事件之一发送给订阅者,接着调用了EventBus#postSingleEventForEventType,把事件、postingState、事件的类传递进去,那么我们来看看这个方法。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //从subscriptionsByEventType获取响应的subscriptions
        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;
            } 
            //...
        }
        return true;
    }
    return false;
}

进一步调用了EventBus#postToSubscription,可以发现,这里把订阅列表作为参数传递了进去,显然,订阅列表内部保存了订阅者以及订阅方法,那么可以猜测,这里应该是通过反射的方式来调用订阅方法。具体怎样的话,我们看源码。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

首先获取threadMode,即订阅方法运行的线程,如果是POSTING,那么直接调用invokeSubscriber()方法即可,如果是MAIN,则要判断当前线程是否是MAIN线程,如果是也是直接调用invokeSubscriber()方法,否则会交给mainThreadPoster来处理,其他情况相类似。这里会用到三个Poster,由于粘性事件也会用到这三个Poster,因此我把它放到下面来专门讲述。而EventBus#invokeSubscriber的实现也很简单,主要实现了利用反射的方式来调用订阅方法,这样就实现了事件发送给订阅者,订阅者调用订阅方法这一过程。送上一张从网上找到的流程图:

本文来自:开源中国博客

感谢作者:HzhiBo

查看原文:android EventBus从应用到解剖

335 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet