EventBus BackgroundPoster原理解析

EventBus相信大家都不陌生,所以这里偷个懒就不说具体用法了,本篇讲解以下原理性的东西,不过在阅读本博客之前建议大家阅读博主的以下两篇博客,因为本篇是在这两篇的基础上写的:
Otto源码解读
EventBus源码解析开篇
通过上面两篇博客,可以了解EventBus的工作原理,简单来说最核心就是:
1、收集应用中所有标注了@Subscribe的Method方法(当然这些方法都是属于具体的注册者对象(Subscriber)的)
2、收到事件后执行method.invoke(subscriber,event);

上述两步就是EventBus的根本的核心所在。然后围绕着这两个核心不断的扩展功能过而已,当然具体的可参考文章开头的说的两篇博客。

结合上面两条在EventBus 中设计了Subscription这个类:

class Subscription {
    //EventBus.register的对象
    final Object subscriber;
    //对@Subscribe注解方法 Method对象的封装
    final SubscriberMethod subscriberMethod;
  }  

在EventBus发送事件到注册者执行Subscribe方法的时候就有了如下方法:


 void invokeSubscriber(Subscription subscription, Object event) {
           subscription
           .subscriberMethod
           .method.//Method 对象
           invoke(subscription.subscriber, event);//反射调用
        
    }

EventBus是事件总线模式,何谓事件,在面向对象的世界中就是个new 出来的对象而已。这个事件是要用EventBus 的post发送出去的,发送给谁?当然是发送给某个对象(面向对象的世界除了对象还有啥),且这个对象的某个方法必须标注@Subsrcribe注解,就像发送邮件的,总得门牌号,而@Subsrcibe就是这个门牌号。找到了事件接收方,怎么处理“邮件”就是接收者自己的事儿了,或者在被窝里看,或者坐客厅里看,反正爱怎么干怎么干。但是凡事总有个约束,EventBus允许事件接收者用如下几个模式来对事件进行处理,即:POSTING、MAIN、MAIN_ORDER、BACKGROUND、ASYNC。

在本篇中之对BACKGROUND模式进行简单的讲解,用法如下:

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onEventBackgroundThread(Integer event) {
           
}

BACKGROUND即后台模式,这个后台是相对于Android主线程而言的,也是就是在非UI线程中处理这个事件,在源码中看可以很好的理解:

  //EventBus 对BACKGROUND 模式的处理
  case BACKGROUND:
     if (isMainThread) {//如果是UI线程,则开启新线程执行之。
        backgroundPoster.enqueue(subscription, event);
     } else {//非UI线程则在直接在本线程中执行
         invokeSubscriber(subscription, event);
     }
      break;

从源码上也能看出BACKGROUND和ASYNC的简单区别(当然这只是区别之一):

case ASYNC:
    //该模式下不论是UI还是非UI线程下,都另起线程执行
    asyncPoster.enqueue(subscription, event);

发送事件是一个具体的行为,为此EventBus抽象出来一个Poster接口出来;

interface Poster {
  //subscription:事件接收者
  //event:具体的事件
   void enqueue(Subscription subscription, Object event);
}

在这里说一个题外话,通常我们都说面向抽象编程,什么是面向抽象呢?简单的来说就是面向接口(interface),而不是面向实现(class)去思考。优先考虑做什么,而不是如何做。比如你去打印文件,你要做的就是打印文件这件事。抽象一个PrintFile接口,而不是先考虑如何打印。

 interface PritFile{
    void print();
 }

至于是用公司(CompanyPrinter)的打印机去打印,还是去打印店(ShopPrinter),这个具体实现就是后期具体设计的了。所以先设计一个接口来统筹行为方案,怎么去实现就有很大的扩展性。都是打印文件,只不过采取的方式不同,从这方面来看接口的不同实现有殊途同归的性质:都是为了实现一个具体的行为。就比如不同的打印实现都是为了打印文件来服务的。

这在里Poster接口也是做了类似于PrintFile接口的事儿,在EventBus里面Poster的实现类:


poster.png

其中AsyncPoster处理的就是ThreadModel.ASYNC,HandlerPoster对应ThreadModel.MAIN和ThreadModel.MAIN_ORDER,至于BackgroundPoster则是处理的ThreadModel.BACKGROUND的了,也是本篇博文的主角。


对象用来封装数据和行为,而对象之间往往是需要不断传递的。下面讲一下EventBus 中一个重要的类PendingPost,该类的结构也很简单:

class PendingPost {
   
    private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
    //具体的事件对象
    Object event;
    Subscription subscription;
    
    //next指针
    PendingPost next;
 }   

可以看出PendingPost除了封装了事件对象event,事件接收者对象Subscription之外,还弄了个next引用,这明摆着就是构建一个链表结构,注意该类还有个静态的pendingPostPool集合,该集合包含了PendingPost对象,至于这个集合的作用后面再讲。首先所以我们来看看EventBus是怎么构建这个PendingPost链表的,具体的构建过程在PendingPostQueue类里面,先瞧瞧这个类有啥:

 class PendingPostQueue {
    //链表头部引用
    private PendingPost head;
    //链表尾部指针
    private PendingPost tail;
  } 

注意该类算是EventBus的核心类之一,在AsyncPoster,BackgroundPost,HandlerPoster里都有用到,该类的强大作用就是构建具有head和tail引用的PendingPost链表结构。具体的构建行为在PendingPostQueue对象的enqueue方法里面:


synchronized void enqueue(PendingPost pendingPost) {
         
        if (tail != null) {
            tail.next = pendingPost;
            tail = pendingPost;
        } else if (head == null) {//第一次执行是走这个分支
            head = tail = pendingPost;
        } 
        
        //唤醒等待的线程
        notifyAll();
    }

上面的方法第一次执行的时候形成了下面的结果:


e1.png

多次调用的话,很明显形成的链表结构如下:


e2.png

其实也就是一个简单的链表结构,也就是说enqueue方法的作用就是将EventBus post的事件形成一个如上图所示的链表。那么我们怎么获取链表中的对象呢,对应的PendingPostQueue有了poll方法:

   synchronized PendingPost poll() {
        //返回head节点,并将head指针下移
        PendingPost pendingPost = head;
        if (head != null) {
            head = head.next;
            if (head == null) {
                tail = null;
            }
        }
        return pendingPost;
    }

    synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
       //列表为空,等待并释放锁
        if (head == null) {
            wait(maxMillisToWait);
        }
        return poll();
    }

所以经过poll方法处理一次后,上图中的head引用会指向PendingPost2节点。一句话总结PendingPostQueue的作用就是构建上图链表和从列表中获取PendingPost的过程,且在构建的过程中如果构建完毕则调用notifyAll,取PendingPost的时候如果链表为null则wait ,可以说是一个简单的生产者消费者模式


BackgroundPoster简单分析
上面提到PendingPostQueue在EventBus Poster接口的三个实现类中都有使用,所以看看先BackgroundPoster这个类的具体实现:

//注意BackgroundPoster是一个Runnable
class BackgroundPoster implements Runnable, Poster {
    //事件链表
    private final PendingPostQueue queue;
    private final EventBus eventBus;
    //volatile确保可见性,但是不能保证原子性
    //判断事件是否得到执行
    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }
  }  

BackgroundPoster是一个实现了Poster的Runnable, 且看poster的enqueue在BackgroundPoster的具体实现:

 @Override
    public void enqueue(Subscription subscription, Object event) {
        //1、获取一个pendingPost
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            //2、构建事件链表
            queue.enqueue(pendingPost);
            //3、执行事件
            if (!executorRunning) {
                executorRunning = true;
                eventBus.getExecutorService().execute(this);
            }
        }
    }

正如代码注释所示,上述方法所了三件事:
1、根据事件event和事件接收者subscription获取一个PendingPost对象,
2、讲获取到PendingPost对象交给queue.enqueue构成上图的链表结构
3、调用eventBus.getExecutorService来处理事件,会调用BackgroundPoster的fun方法。

下面详细说一下事件1:具体的是PendingPost的静态方法obtainPendingPost来构建一个PendingPost:

 //PendingPost集合
 final static List<PendingPost> pendingPostPool=new ArrayList<PendingPost>();
 
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        //从pendingPostPool获取一个PendingPost
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            if (size > 0) {
                //从集合末尾获取一个
                PendingPost pendingPost = pendingPostPool.remove(size-1);
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                return pendingPost;
            }
        }
        //如果pendingPostPool没有,则创建一个
        return new PendingPost(event, subscription);
    }

逻辑很清晰,就是先从List集合的尾部中调用remove获取一个PendingPost对象,注意List集合中的PendingPost对象所封装的数据都为空,也就是:

        pendingPost.event == null;
        pendingPost.subscription == null;
        pendingPost.next == null;

然后将obtainPendingPost方法里的参数赋值给该pendingPost对象并返回。如果list集合为空,则new 一个PendingPost返回,注意此时新创建的PendingPost的数据不为空,并没有调用list.add方法将对象放入进去。那么什么时候将PendingPost对象add进来呢?当然是事件处理完毕后,此时调用PendingPost的releasePendingPost静态方法:

   //处理完事件后将pendingPost数据指控,可比喻为空瓶回收
    static void releasePendingPost(PendingPost pendingPost) {
        //数据置空
        pendingPost.event = null;
        pendingPost.subscription = null;
        pendingPost.next = null;
        
        //数据指控后list回收,感觉跟空瓶回收一样
        //水喝完了,瓶子单独回收
        synchronized (pendingPostPool) {
            // Don't let the pool grow indefinitely
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }

所以list集合的作用就是有点类似空瓶回收的作用,在这里PendingPost就是瓶子,瓶子里的水(事件)喝完后,将瓶子回收过来等下次再装水(事件),好处PendingPost这个对象得到了复用,必须了对象的频繁创建

e3.png

说完了PendingPost的创建和回收,以及PendingPost链表的构建是时候看看事件的消费了,当然是在BackgroundPoster的run方法中对事件进行分发处理,所以看看run方法都怎么处理的:

 public void run() {
        try {
            try {
                //循环获取链表,并处理直到链表为null
                while (true) {
                    //1、从连表中获取一个PendingPost,链表为null则等待一秒
                    PendingPost pendingPost = queue.poll(1000);

                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            //继续获取pendingPost
                            pendingPost = queue.poll();
                            //如果还为null,认定此时没有事件产生
                            if (pendingPost == null) {
                                executorRunning = false;
                                //退出run方法
                                return;
                            }
                        }
                    }

                    //在这里会调用PendingPost.releasePendingPost
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }

可以看出run总体上来说就是循环PendingPost链表,然后调用eventBus.invokeSubscriber(pendingPost);该方法主要是将event交给@subsribe方法进行invoke调用。需要注意的是invokeSubscriber方法里面调用了PendingPost.releasePendingPost,该方法上面说过就是对pendingPost进行回收再利用(见上图)。

    void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        //回收pendingPost对象,以后复用
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
            //真正执行method.invoke的地方
            invokeSubscriber(subscription, event);
        }
    }
    
    //真正执行method.invoke的地方
    void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

需要额外注意的是,在while循环中,先调用poll(1000)方法,如果事件链表为null,则wait(1000),等待事件产生;而后再次在用poll()获取,如果此时仍然为null,则EventBus认为现阶段没有事件,则run方法执行结束,直到下次事件来临后继续调用eventBus.getExecutorService().execute(this)执行。

还需要注意的是因为在fun方法中循环便利事件链表,所以为了防止事件阻塞,在使用ThreadModel.BACKGROUND的时候,要求此@Subricebe 方法不要执行耗时的逻辑。比如网络请求等。就像官方注释所说:

 EventBus uses a single background thread, that will deliver all its events sequentially. Subscribers using this mode should try to  return quickly to avoid blocking the background thread

那么耗时的逻辑在哪儿执行呢?耗时的逻辑用ThreadModel.ASYNC 模式,因为该模式很简单就是获取链表中的一个事件,执行之;而不像BackGround那样循环整个事件链表挨个执行之。

  //AsyncPoster的run方法
  public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        eventBus.invokeSubscriber(pendingPost);
    }

到此为止BackgrounPoster讲解完毕,如有不当之处欢迎批评指正,共同学习

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

推荐阅读更多精彩内容