Khám phá cơ chế hoạt động bên trong EventBus 3.0 trên Android

1. Khở tạo đối tượng EventBus

Để sử dụng EventBus, ta thường gọi EventBus.getDefault() để lấy thực thể duy nhất:

public static EventBus getInstance() {
    if (singleton == null) {
        synchronized (EventBus.class) {
            if (singleton == null) {
                singleton = new EventBus();
            }
        }
    }
    return singleton;
}

Đoạn mã trên áp dụng mẫu thiết kế Singleton với kiểm tra kép (DCL). Khi tạo mới, EventBus sẽ dùng một builder mặc định:

public EventBus() {
    this(DEFAULT_BUILDER);
}
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

Hàm khởi tạo chính thiết lập các thành phần cốt lõi:

EventBus(EventBusBuilder builder) {
    eventToSubscribers = new HashMap<>();
    subscriberToEvents = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    bgPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    methodFinder = 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;
}

2. Đăng ký người lắng nghe

Sau khi có thực thể EventBus, ta đăng ký đối tượng lắng nghe qua phương thức register:

public void register(Object listener) {
    Class<?> listenerClass = listener.getClass();
    List<SubscriberMethod> methods = methodFinder.findSubscriberMethods(listenerClass);
    synchronized (this) {
        for (SubscriberMethod method : methods) {
            addSubscription(listener, method);
        }
    }
}

Tìm kiếm phương thức đăng ký

findSubscriberMethods trả về danh sách các phương thức được đánh dấu @Subscribe trong lớp đăng ký:

List<SubscriberMethod> findSubscriberMethods(Class<?> listenerClass) {
    List<SubscriberMethod> cached = METHOD_CACHE.get(listenerClass);
    if (cached != null) {
        return cached;
    }

    if (ignoreGeneratedIndex) {
        cached = findViaReflection(listenerClass);
    } else {
        cached = findViaIndex(listenerClass);
    }

    if (cached.isEmpty()) {
        throw new EventBusException("Subscriber " + listenerClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        METHOD_CACHE.put(listenerClass, cached);
        return cached;
    }
}

Quy trình tìm kiếm ưu tiên kiểm tra bộ nhớ đệm. Nếu chưa có, dựa vào ignoreGeneratedIndex để quyết định dùng chỉ mục biên dịch (APT) hay phản chiếu runtime. Mặc định ignoreGeneratedIndexfalse, tức là ưu tiên dùng findViaIndex:

private List<SubscriberMethod> findViaIndex(Class<?> listenerClass) {
    SearchState state = prepareSearchState();
    state.initForSubscriber(listenerClass);
    while (state.clazz != null) {
        state.subscriberInfo = getSubscriberInfo(state);
        if (state.subscriberInfo != null) {
            SubscriberMethod[] array = state.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod method : array) {
                if (state.checkAdd(method.method, method.eventType)) {
                    state.foundMethods.add(method);
                }
            }
        } else {
            findViaReflectionInClass(state);
        }
        state.moveToSuperclass();
    }
    return extractAndRelease(state);
}

Nếu không có chỉ mục APT, hệ thống rơi vào findViaReflectionInClass để phân tích qua reflection:

private void findViaReflectionInClass(SearchState state) {
    Method[] methods;
    try {
        methods = state.clazz.getDeclaredMethods();
    } catch (Throwable ex) {
        methods = state.clazz.getMethods();
        state.skipSuperClasses = true;
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] params = method.getParameterTypes();
            if (params.length == 1) {
                Subscribe annotation = method.getAnnotation(Subscribe.class);
                if (annotation != null) {
                    Class<?> eventType = params[0];
                    if (state.checkAdd(method, eventType)) {
                        ThreadMode mode = annotation.threadMode();
                        state.foundMethods.add(new SubscriberMethod(method, eventType, mode,
                                annotation.priority(), annotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String name = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + name +
                        "must have exactly 1 parameter but has " + params.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String name = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(name +
                    " is an illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

Đoạn mã trên lọc các phương thức public, không phải static/abstract, có đúng một tham số và đi kèm annotation @Subscribe.

Lưu trữ thông tin đăng ký

Sau khi tìm đủ phương thức, EventBus lưu vào cấu trúc dữ liệu nội bộ:

private void addSubscription(Object listener, SubscriberMethod method) {
    Class<?> eventType = method.eventType;
    Subscription subscription = new Subscription(listener, method);
    
    CopyOnWriteArrayList<Subscription> subs = eventToSubscribers.get(eventType);
    if (subs == null) {
        subs = new CopyOnWriteArrayList<>();
        eventToSubscribers.put(eventType, subs);
    } else {
        if (subs.contains(subscription)) {
            throw new EventBusException("Subscriber " + listener.getClass() + " already registered to event " + eventType);
        }
    }

    int len = subs.size();
    for (int i = 0; i <= len; i++) {
        if (i == len || method.priority > subs.get(i).subscriberMethod.priority) {
            subs.add(i, subscription);
            break;
        }
    }

    List<Class<?>> events = subscriberToEvents.get(listener);
    if (events == null) {
        events = new ArrayList<>();
        subscriberToEvents.put(listener, events);
    }
    events.add(eventType);

    if (method.sticky) {
        if (eventInheritance) {
            for (Map.Entry<Class<?>, Object> entry : stickyEvents.entrySet()) {
                Class<?> candidate = entry.getKey();
                if (eventType.isAssignableFrom(candidate)) {
                    deliverStickyEvent(subscription, entry.getValue());
                }
            }
        } else {
            Object sticky = stickyEvents.get(eventType);
            deliverStickyEvent(subscription, sticky);
        }
    }
}

Hai bản đồ eventToSubscriberssubscriberToEvents đóng vai trò then chốt: bản đồ thứ nhất hỗ trợ phân phối sự kiện theo loại, bản đồ thứ hai hỗ trợ hủy đăng ký theo đối tượng.

3. Phát sự kiện

Để phát sự kiện, dùng phương thức post:

public void post(Object event) {
    PostingThreadState state = currentPostingThreadState.get();
    List<Object> queue = state.eventQueue;
    queue.add(event);

    if (!state.isPosting) {
        state.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        state.isPosting = true;
        if (state.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!queue.isEmpty()) {
                dispatchSingleEvent(queue.remove(0), state);
            }
        } finally {
            state.isPosting = false;
            state.isMainThread = false;
        }
    }
}

Sự kiện được đưa vào hàng đợi của luồng hiện tại, sau đó xử lý tuần tự qua dispatchSingleEvent:

private void dispatchSingleEvent(Object event, PostingThreadState state) throws Error {
    Class<?> eventClass = event.getClass();
    boolean found = false;

    if (eventInheritance) {
        List<Class<?>> types = collectEventHierarchy(eventClass);
        for (Class<?> type : types) {
            found |= postToEventType(event, state, type);
        }
    } else {
        found = postToEventType(event, state, eventClass);
    }

    if (!found) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

Nếu eventInheritance bật, EventBus sẽ tìm cả lớp cha của sự kiện để gửi đến những người lắng nghe phù hợp. Phương thức postToEventType lấy danh sách Subscription và chuyển sang xử lý:

private boolean postToEventType(Object event, PostingThreadState state, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subs;
    synchronized (this) {
        subs = eventToSubscribers.get(eventClass);
    }

    if (subs != null && !subs.isEmpty()) {
        for (Subscription sub : subs) {
            state.event = event;
            state.subscription = sub;
            boolean aborted = false;
            try {
                routeToPoster(sub, event, state.isMainThread);
                aborted = state.canceled;
            } finally {
                state.event = null;
                state.subscription = null;
                state.canceled = false;
            }
            if (aborted) break;
        }
        return true;
    }
    return false;
}

Cuối cùng, routeToPoster quyết định luồng thực thi dựa trên ThreadMode:

private void routeToPoster(Subscription sub, Object event, boolean onMainThread) {
    switch (sub.subscriberMethod.threadMode) {
        case POSTING:
            invokeMethod(sub, event);
            break;
        case MAIN:
            if (onMainThread) {
                invokeMethod(sub, event);
            } else {
                mainPoster.enqueue(sub, event);
            }
            break;
        case BACKGROUND:
            if (onMainThread) {
                bgPoster.enqueue(sub, event);
            } else {
                invokeMethod(sub, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(sub, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + sub.subscriberMethod.threadMode);
    }
}

HandlerPoster dùng Handler với Looper chính để chuyển tác vụ sang UI thread, còn BackgroundPosterAsyncPoster dùng thread pool để xử lý bất đồng bộ.

4. Hủy đăng ký người lắng nghe

public synchronized void unregister(Object listener) {
    List<Class<?>> eventTypes = subscriberToEvents.get(listener);
    if (eventTypes != null) {
        for (Class<?> type : eventTypes) {
            removeByEventType(listener, type);
        }
        subscriberToEvents.remove(listener);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + listener.getClass());
    }
}

Dựa vào subscriberToEvents, EventBus biết được đối tượng nào đã đăng ký những sự kiện gì, từ đó dọn dẹp khỏi eventToSubscribers.

5. Kiến trúc tổng quan và đánh giá

Kiến trúc cốt lõi

EventBus xây dựng trên nền tảng Observer pattern. Bộ ba Subscriber - Event - Publisher tách biệt hoàn toàn, giúp các thành phần giao tiếp mà không cần tham chiếu trực tiếp.

Ưu điểm và hạn chế

Ưu điểm nổi bật:

  • Giảm phụ thuộc giữa các module, tăng tính linh hoạt
  • Từ phiên bản 3.0, việc dùng APT để sinh chỉ mục tại thời điểm biên dịch giúp tránh reflection nặng nề ở runtime, tối ưu hiệu năng đáng kể
  • API đơn giản, dễ tích hợp

Hạn chế cần lưu ý:

  • Lạm dụng dễ khiến luồng dữ liệu rời rạc, khó theo dõi
  • Debug trở nên phức tạp khi sự kiện lan truyền qua nhiều tầng
  • Quá nhiều sự kiện ngầm làm giảm khả năng đọc hiểu codebase

Thẻ: eventbus Android Observer Pattern Handler ThreadMode

Đăng vào ngày 20 tháng 9 lúc 04:06