拆解EventBus

773 阅读7分钟

一、EventBus原理概述

翻看大部分EventBus源码分析文章,都在大谈特谈EventBus的原理和使用,但是对于牛逼的你,一旦用过EventBus,从它暴露出来的api,也能对它的原理说出个大概。

public static class MessageEvent { /* Additional fields if needed */ }

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

 @Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }
 
  EventBus.getDefault().post(new MessageEvent());

在注册时传入的是订阅者对象,那EventBus作为被观察者代理人。自然有责任和义务去管理(添加和删除)这些订阅者,register和unRegister方法自然被需要,分发对应的事件,当然就需要post方法去遍历通知所有的订阅者。至此,eventBus源码原理解释完毕。

二、EventBus需要解决哪些问题,怎么解决的?

1. 怎么保证事件处理的串行和并行?

ThreadMode.BACKGROUND  对应BackgroundPoster 串行执行。
ThreadMode.POSTING  串行执行
ThreadMode.MAIN 与  ThreadMode.MAIN_ORDERED 几乎一模一样,无需刻意区分,串行执行
ThreadMode.ASYNC 对应AsyncPoster  并行执行

ThreadMode.BACKGROUND 不是java的后台线程啊,如果发送事件是在主线程,那么将是从EventBus默认的线程池中提供一个线程去执行。如果发送事件本身是在用户的子线程,那接收也是在那个子线程。

//以下这个是主线程发,子线程接收保证串行
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

final class BackgroundPoster implements Runnable, Poster {
    ....//发送事件是为保证串行 先将事件出入队列,各种同步锁
    //把任务丢给线程池,让其处理。
    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!executorRunning) {
                executorRunning = true;
                eventBus.getExecutorService().execute(this);
            }
        }
    }
}

ThreadMode.POSTING 无需任何Poster类,发送和接收均在同一个线程,同一个线程的run方法,默认顺序执行,直接就是串行执行了啊。

ThreadMode.MAIN 与 ThreadMode.MAIN_ORDERED 这两个真的没法区分,我不清楚源码里面要区分是干啥。直接考虑两种场景吧,1. 发送在主线程,接收也在主线程,安卓主线程就一个,自然无需处理就是串行了;2. 发送在子线程,接收在主线程,那你做线程切换用的是同一个Handler啊,handler.sendMessage和handler.handleMessage有MessageQueue队列加持,应该也无需做特殊处理额,源码中的HandlerPoster写的太啰嗦了吧。(多个线程同时发送同一个Event的场景除外)

//贴下我改写的代码吧,如有错误,大家轻喷!
public class HandlerPoster implements Handler.Callback {
    public EventBus eventBus;
    public Handler handler;
    public HandlerPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        //主线程的handler
       handler=new Handler(Looper.getMainLooper(),this);
    }
    public void enqueue(Subscription subscription, Object eventType) {
        PostPending postPending=PostPending.obtainPostPending(eventType,subscription);
        Message message=Message.obtain();
        message.obj=postPending;
        handler.sendMessage(message);
    }
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        if( msg.obj instanceof  PostPending){
            PostPending postPending= (PostPending) msg.obj;
            eventBus.invokeSubscriber(postPending);
        }
        return true;
    }
}


ThreadMode.ASYNC 异步处理,只要有一个event到达,就加入线程池执行,自然就是并行处理了。

public class AsyncPoster implements Runnable {
  ......
    public void enqueue(Subscription subscription, Object eventType) {
        PostPending postPending = PostPending.obtainPostPending(eventType, subscription);
        Log.e("eventBus", "AsyncPoster  obtainPostPending " +postPending);
        queue.enqueue(postPending);
        eventBus.executorService.execute(this);
    }

真正需要额外处理的场景:

  1. 指定Thread.Mode为ThreadMode.BACKGROUND,发送在主线程,接收在子线程,同时要保证串行。如果让你来实现,你会怎么做?

你是不是会想到单生产-单消费模型 啊,源码就是基于这个模型的, 生产就加入队列,消费就从队列里面取出执行。

public class BackgroundPoster implements Runnable {
    public EventBus eventBus;
    public PostPendingQueue queue;
    public BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        this.queue = new PostPendingQueue();
    }
    //使用volatile保证可见性
    private volatile boolean executorRunning;
    public void enqueue(Subscription subscription,Object eventType) {
        PostPending postPending=PostPending.obtainPostPending(eventType,subscription);
        synchronized (this) {
            queue.enqueue(postPending);
            if (!executorRunning) {
                executorRunning = true;
                eventBus.executorService.execute(this);
            }
        }
    }
    @Override
    public void run() {
        try {
            try {
                while (true) {
                    PostPending postPending = null;
                    postPending = queue.poll(1000);
                    if (postPending == null) {
                        synchronized (this) {
                            postPending = queue.poll();
                            if (postPending == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    eventBus.invokeSubscriber(postPending);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } finally {
            executorRunning = false;
        }
    }
}

忍不住贴一下EventBus里面的队列,很经典

public class PostPendingQueue {
    PostPending head;
    PostPending tail;
   public  synchronized  void enqueue(PostPending postPending){
   //注意顺序
       if(tail!=null){
           tail.next=postPending;
           tail=postPending;
       }else if (head==null){
           head=tail=postPending;
       }
       notifyAll();
   }
   public synchronized  PostPending poll(long time) throws InterruptedException {
       if(head==null){
           wait(time);
       }
        return poll();
   }
    public synchronized  PostPending poll() {
        PostPending postPending=head;
       if(postPending!=null){
           head=postPending.next;
           if(head==null){
               tail=null;
           }
       }
       return postPending;
    }
}

2. EventBus可给java后台使用,也可给android用,那它怎么判断是android的?

static {
    boolean android = false;
    try {
        android = Class.forName("android.util.Log") != null;
    } catch (ClassNotFoundException e) {
        // OK
    }
    ANDROID_LOG_AVAILABLE = android; //判断通过这个标志位
}

3. 如果方法的Event参数类型是泛型,怎么获取到这个泛型的真实类型?

VariableElement 是编译期间才用的api。 represents a field, enum constant, method or constructor parameter, local variable, resource variable, or exception parameter。(代表一个字段、枚举常量、方法参数、构造参数、本地变量、资源变量、异常参数)

private TypeMirror getParamTypeMirror(VariableElement param, Messager messager) {
    TypeMirror typeMirror = param.asType();
    // 判断是否是泛型
    if (typeMirror instanceof TypeVariable) {
        TypeMirror upperBound = ((TypeVariable) typeMirror).getUpperBound();
   //获取到实际的声明类型,以上界为准
        if (upperBound instanceof DeclaredType) {
            typeMirror = upperBound;
        }
    }
    return typeMirror;
}

4. 判断订阅方法的修饰符为Public:使用位运算,大大提高效率

private static final int BRIDGE = 0x40;
private static final int SYNTHETIC = 0x1000;
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
public static final int PUBLIC           = 0x00000001;
public static final int PRIVATE          = 0x00000002; 
public static final int PROTECTED        = 0x00000004;
public static final int STATIC           = 0x00000008;
public static final int FINAL            = 0x00000010;
public static final int ABSTRACT         = 0x00000400;
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
   .....
}

5. EventBus源码中是用ThreadLocal干啥?

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

ThreadLocal作用:相同线程数据共享,不同线程数据隔离。 其对象实例与ThreadLocal变量的映射关系由Thread维护。

EventBus使用的场景:订阅事件设置线程ThreadMode = ThreadMode.POSTING, 且设置了不同的优先级,优先级高的订阅者在接收到事件后,调用了 EventBus.getDefault().cancelEventDelivery(event);那后续优先级低的订阅者将不会收到事件。

6. 粘性事件?

当之前有相应的粘性事件发送了,但是订阅者还没注册,这时候订阅者再注册,可以在注册时收到之前的事件。

@Subscribe(threadMode =  ThreadMode.POSTING, sticky = true)
public void show5(MyMessage myMessage) {
    Log.e("eventBus1", "show5: 接收数据message");
}
EventBus.getDefault().postSticky(event);
//最后才注册:
EventBus.getDefault().register(MainActivity.this);

7. 解析注解生成新类,只能使用javapoet吗?

EventBus可没有引入这个javapoet库哦,用的Filer配合 BufferedWriter 的writer写入文件。

  private void createFile() {
        BufferedWriter bufferedWriter = null;
        try {
            JavaFileObject source = mFiler.createSourceFile(mIndex);
            bufferedWriter = new BufferedWriter(source.openWriter());
            int index = mIndex.lastIndexOf(".");
            String packageName = mIndex.substring(0, index);
            bufferedWriter.write("package " + packageName + ";\n\n");
            bufferedWriter.write("import java.util.HashMap;\n");
            bufferedWriter.write("import java.util.Map;\n");
            bufferedWriter.write("import " + ThreadMode.class.getCanonicalName() + ";\n");
            bufferedWriter.write("public class " + mIndex.substring(index + 1) + " implements ISubscriberMethodIndex{\n\n");
            bufferedWriter.write("     private final static Map<Class<?>, SubscribeMethodApt[]> SUBSCRIBER_INDEX;\n");
            bufferedWriter.write("     static {" + "\n");
            bufferedWriter.write("          SUBSCRIBER_INDEX = new HashMap<>();\n");
            bufferedWriter.write(writeKeyLines());
            bufferedWriter.write("     }" + "\n\n");
            bufferedWriter.write("     @Override" + "\n");
            bufferedWriter.write("     public SubscribeMethodApt[] getSubScriberMethod(Class<?> clazz){\n");
            bufferedWriter.write("        return SUBSCRIBER_INDEX.get(clazz);\n");
            bufferedWriter.write("      }\n");
            bufferedWriter.write("}\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

8. 在gradle中配置annotationProcessorOptions有什么用?

  javaCompileOptions {
            annotationProcessorOptions {
                arguments=[eventBusIndex:'com.docwei.eventbusnewdemo.MainEventBusIndex']
            }
        }

参数的arguments含有一个键值对,key是eventBusIndex,值是:com.docwei.eventbusnewdemo.MainEventBusIndex,value作为新生成的文件全路径。在注解处理器中可以捕获到这个key和value值。

v@SupportedOptions(value = {"eventBusIndex"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EventBusAnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //判断有没有设置options
        // arguments = [eventBusIndex: 'com.docwei.eventbusnewdemo.MainEventBusIndex']
        //这个就是新生成的类的文件全路径
        mIndex = mOptions.get("eventBusIndex");
        

9. 支持多线程操作的ArrayList和HashMap并发容器

EventBus使用了CopyOnWriteArrayList和ConcurrentHashMap.

CopyOnWriteArrayList  查询时不需要加锁,可直接访问,只有在写入/删除时,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。 可能存在内存占用问题,导致Gc。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

ConcurrentHashMap  key和value都不能为null,采用了CAS和synchronized来保证并发安全.

10. 对象池的多处使用

PendingPost 和 FindState 都创建了对应的对象池。以后可以参考这种写法:

private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
    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;
        }
    }
    return new PendingPost(event, subscription);
}

static void releasePendingPost(PendingPost pendingPost) {
    pendingPost.event = null;
    pendingPost.subscription = null;
    pendingPost.next = null;
    synchronized (pendingPostPool) {
        // Don't let the pool grow indefinitely
        if (pendingPostPool.size() < 10000) {
            pendingPostPool.add(pendingPost);
        }
    }
}
private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    findState.recycle();
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}

private FindState prepareFindState() {
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            FindState state = FIND_STATE_POOL[i];
            if (state != null) {
                FIND_STATE_POOL[i] = null;
                return state;
            }
        }
    }
    return new FindState();
}

三、基于EventBus源码,精简代码的demo奉上

看完源码后以完成EventBus的主体功能为目标进行精简重写。

EventBusNewDemo