面试题之---EventBus源码解析

(一)介绍

1,EvenetBus是一种发布-订阅事件总线.代码简洁,开销小,并很好的实现了发送者和接收者的解耦.(是一种观察者模式)

优点:

代码简单,速度快;

解耦;

体积小,jar大概50k;

稳定,大概有1亿+的应用中使用


2,三要素:
A,Event:事件,
B,Publisher:发布者,可以在任意线程发布事件
C,Subscrible:订阅者,

 

3,通常情况下安卓下数据的传递有下面几种方法:

3.1.通过intent传递,包括显式意图和隐式意图,广播(Broadcast)和服务都能通过Intent传递

    传递的数据类型包括8大基本数据类型    实现ParcelableSerializable接口的类型   以及集合数组类型

3.2.静态变量传递  在工具类下 声明一个Object类型的静态变量   在A中将要传递的值,在B中通过这个静态变量取出来

3.3.通过handle在不同的线程中传递Object类型的数据

3.4.通过构造方法传递Object类型的数据

3.5.通过SharedPreferences传递八大基本数据类型

3.6.通过ContentProvider在进程间共享数据

3.7.通过aidl在进程进程传递数据

3.8.通过流(本地文件)传递八大基本数据类型和实现Serializable接口的数据类型

3.9.通过基类中的属性或者方法

    属性: 基类公有属性  在某个子类中赋值   其他子类中都能使用

    方法: 子类调用父类的某个方法给父类某个属性赋值  另外一个子类通过父类的另一个公有方法获取这个值(这个方法把值返回)

 


(二)基本使用

##先订阅,后发布

1,添加依赖
    compile 'org.greenrobot:eventbus:3.1.1'
2,注册事件
    public class MessageEvent {

    private String message;

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

    public String getMessage(){
        return message;
    }
}

3,在接受消息的代码
      //注册成为订阅者
      EventBus.getDefault().register(this);

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除注册
        EventBus.getDefault().unregister(this);
    }

    //订阅方法,当接收到事件的时候,会调用该方法
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent messageEvent){
        Log.e("date","receive it");
        Toast.makeText(ViewPageStep1Activity.this, messageEvent.getMessage(), Toast.LENGTH_SHORT).show();
    }
4,在发送消息的地方  
     EventBus.getDefault().post(new MessageEvent("从fragment将数据传递到activity22222222"));

###先发布,再订阅,黏性事件
- 5,在接受消息的代码
     //注册成为订阅者
      EventBus.getDefault().register(this);

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除注册
        EventBus.getDefault().unregister(this);
    }

    //订阅方法,当接收到事件的时候,会调用该方法
    @Subscribe(threadMode = ThreadMode.MAIN,stick = true)
    public void onEvent(MessageEvent messageEvent){
        Log.e("date","receive it");
        Toast.makeText(ViewPageStep1Activity.this, messageEvent.getMessage(), Toast.LENGTH_SHORT).show();
    }
- 6,在发送消息的地方
     EventBus.getDefault().postSticky(new MessageEvent("从fragment将数据传递到activity22222222"));

 

(三)四个订阅方法

onEvent:

如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。

onEventMainThread:

如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。

onEventBackground:

如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。

onEventAsync:

使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync。

面试题之---EventBus源码解析_第1张图片

Subscribe注解

RetentionPolicy.SOUCE(编译时)

如果是RetentionPolicy.RUNTIME(运行时),那就可以通过反射拿到它的方法。

 

面试题之---EventBus源码解析_第2张图片

(四)源码解析

 

                                   面试题之---EventBus源码解析_第3张图片

          面试题之---EventBus源码解析_第4张图片      

 

         可以看到,发布者(Publisher)使用post()方法将Event发送到Event Bus,而后Event Bus自动将Event发送到多个订阅者(Subcriber)。这里需要注意两个地方:

         (1)一个发布者可以对应多个订阅者。

        (2)3.0以前订阅者的订阅方法为onEvent()onEventMainThread()onEventBackgroundThread()onEventAsync()。在Event Bus3.0之后统一采用注解@Subscribe的形式,具体实现方式见下文。

        

   // 1主线程调用
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void eventBusMain(String str){
        Log.i("TAG", "MAIN:"+str+" Thread="+Thread.currentThread().getId());
    }

    // 2.发布线程为主线程,新开线程调用
    // 2.发布线程为子线程,发布线程调用
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void eventBusBg(String str){
        Log.i("TAG", "BACKGROUND:"+str+" Thread="+Thread.currentThread().getId());
    }

    //3,哪个线程发布,就在哪个线程调用
    @Subscribe(threadMode = ThreadMode.POSTING)
    public void eventBusPosting(String str){
        Log.i("TAG", "POSTING:"+str+" Thread="+Thread.currentThread().getId());
    }

    // 4,每次都新开线程调用
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void eventBusAsync(String str){
        Log.i("TAG", "ASYNC:"+str+" Thread="+Thread.currentThread().getId());
    }

       

1,getDefault()

 里面采用单利双重锁模式创建Eventbus对象

static volatile EventBus defaultInstance;
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}


2,构造方法
2.1,粘性事件,保存到
ConCurrenHashMap集合,(在构造方法中实现),
HashMap效率高,但线程不安全,在多线程的情况下,尽量用
ConcurrentHashMap,避免多线程并发异常

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



3,register()

注册,主要是为了获取目标activity或者fragment里,接收你传递信息的方法。比如你点击B页面按钮,将信息传递到A页面,你是在B页面注册,那注册时,就是为了获取A页面接收你信息的那个方法

 

register()方法主要做了3件事(过程):

             
3.1,从缓存中获取订阅方法列表,

3.2,如果缓存中不存在,则通过反射获取到订阅者所有的函数,

3.3,遍历再通过权限修饰符.参数长度(只允许一个参数).注解(@Subscribe)来判断是否是具备成为订阅函数的前提

面试题之---EventBus源码解析_第5张图片

3.4,具备则构建一个SubscriberMethod(订阅方法,其相当于一个数据实体类,包含方法,threadmode,参数类型,优先级,是否粘性事件这些参数),循环结束订阅函数列表构建完成添加进入缓存

在RunTime期间使用反射对程序运行的性能有较大影响,EventBus3.0中增加了一个新特性:通过在编译期创建索引(SubscriberInfoIndex)就不会跑用反射获取订阅者方法,而是直接从getSubscriberInfo这个方法里面获取,提高程序运行性能,自己添加索引进行优化。

public void register(Object subscriber) {
    Class subscriberClass = subscriber.getClass();
    //找到订阅者的方法.找出传进来的订阅者的所有订阅方法,然后遍历订阅者的方法.
  List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //订阅者的注册
             subscribe(subscriber, subscriberMethod);
        }
    }
}


private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // 通过反射来获取订阅者中所有的方法
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // 
,并根据方法的类型,参数和注解找到订阅方法.
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;}

    //通过CopyOnWriteArrayList保存Subscription,Subscription是一个封装类,封装了订阅者、订阅方法这两个类。
    //Arraylist效率高,但线程不安全,在多线程的情况下,使用CopyOnWriteArrayList,避免多线程并发异常

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList 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);
        }
    }

3.5,如果是粘性事件,是先接收消息,再发送。

它就会获取接收消息,判断接收的消息是不是发送的消息。

isAssignableFrom()方法是判断是否为某个类的父类,是从类继承的角度去判断;

isAssignableFrom()方法的调用者和参数都是Class对象,调用者为父类,参数为本身或者其子类。(instanceof关键字是判断是否某个类的子类,是从实例继承的角度去判断)

 stickyEvents是发送粘性事件时,报错的粘性事件

面试题之---EventBus源码解析_第6张图片

面试题之---EventBus源码解析_第7张图片

4,post()

4.1,发送事件:从缓存中获取目标页面接收信息的方法,循环遍历,找到对应的方法,就将信息传递过去.

4.2,post调用后先使用ThreadLocal来存储事件,他可以隔离多个线程对数据的访问冲突(ThreadLocal是每个线程独享的,其数据别的线程是不能访问的,因此是线程安全的。)


4.3,,根据事件类型(也就是Post参数的类型)为key从subscriptionsByEventType获取订阅方法和订阅者
        此时,我们已经具备了订阅者.订阅方法.待发送事件.post线程.threadmode等信息

,4.4,,根据线程和threadmode来判断

public class EventBus {
   private final ThreadLocal currentPostingThreadState = new         
     ThreadLocal() {
          @Override
          protected PostingThreadState initialValue() {
             return new PostingThreadState();
          }
      };



  /** For ThreadLocal, much faster to set (and get multiple values). */
    final static class PostingThreadState {
        final List eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
} 
  
public void post(Object event) {
  //PostingThreadState 保存着事件队列和线程状态信息
   PostingThreadState postingState = currentPostingThreadState.get();
    //获取事假队列,并将当期事件插入事件队列
    List 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 {
         //处理队列中的所有事件,
        //将所有的事情交给postSingleEvent处理,并移除该事件
          while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
} 
  

 

5,unregister

取消事件订阅,移除缓存中的事件

public synchronized void unregister(Object subscriber) {
    ////根据当前的订阅者来获取它所订阅的所有事件
    //typesBySubscriber是一个map集合,
    //通过subscriber找到 subscribedTypes (事件类型集合),
     List> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        //将subscriber对应的eventType从typesBySubscriber移除
        typesBySubscriber.remove(subscriber);
 
  /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
    private void unsubscribeByEventType(Object subscriber, Class eventType) {
        //根据事件类型从subscriptionsByEventType中获取相应的 subscriptions 集合
        List subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
              //遍历所有的subscriptions,满足条件就删除
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }




借鉴:刘望舒先生的进阶之光

Android EventBus3.1.1从使用到源码解析

Android EventBus 使用详解

EventBus的使用,数据传递

EventBus 3.0进阶:源码及其设计模式 完全解析

你可能感兴趣的:(框架源码解析)