EventBus 이해 및 소스 분석

원문 연결, 전재는 작성자와 원문 연결을 설명해 주십시오(@woaitqs,woaitqs.github.io)
EventBus에서 해결해야 할 문제
일상적인 인코딩에서 우리는 많은 네트워크 요청, 데이터베이스 조작 등을 만날 수 있다. 일반적인 상황에서 이런 조작은 를 통해 이루어진다.예를 들면 Volley입니다.
ImageRequest request = new ImageRequest(url,
    new Response.Listener<Bitmap>() {
        @Override
        public void onResponse(Bitmap bitmap) {
            mImageView.setImageBitmap(bitmap);
        }
    }, 0, 0, null,
    new Response.ErrorListener() {
        public void onErrorResponse(VolleyError error) {
            mImageView.setImageResource(R.drawable.image_load_error);
        }
    });

이때 당신은 많은 를 사용해야 한다면 한 가지 문제를 발견하고 생각하기 시작할 것이다.예를 들어 인터넷 상태 변화, 앱의 설치 상황, 내용의 다운로드 상황을 감청해야 하는 물건을 개발하고 있다.많은 가 존재하면'이 사건들을 감청자에게 어떻게 통지하는가'는 복용할 수 있는 모듈이다. 이것이 바로 EventBus 존재의 의미이다.여기서 여러분이 한 가지 문제를 알아야 합니다. 자체가 복용할 수 있는 모듈입니다.
  • 콘텐츠 다운로드 모듈
  • 전력 감청 모듈
  • App 알림
  • 그들은 모두 EventBus를 통해 자신의 사건을 발표할 수 있다. 사용자는 이 모듈에서 자신에게 관심이 있는 내용을 등록하면 된다.
    EventBus의 이점 및 도입 문제
    장점이 비교적 뚜렷하다. 바로 하나 를 독립한 것이다. 호출자는 이 모듈을 사용하여 일부 라인 전환 문제를 차단하고 게시 구독 기능을 간단하게 실현할 수 있다.
    나쁜 점은 비교적 은연중에 있을 수 있지만, 이러한 수요는 우리의 중시를 불러일으키기에 충분하다
  • 대량의 남용은 논리의 분산을 초래하고 문제가 발생하면 포지셔닝하기 어렵다.
  • 강한 유형을 실현할 수 없고 번역할 때 문제가 발견된다(Otto는 이것을 실현했지만 성능에 문제가 있다).실현에 있어서 매우 약한 프로토콜, 예를 들어onEvent {XX}, {XXX}는 ThreadModel을 표시하여 라인의 전환을 실현한다.뒤에 코드를 해석할 때 이 문제를 설명할 것이다.
  • 코드의 가독성에 문제가 있어서 IDE가 이러한 프로토콜을 식별할 수 없기 때문에 IDE에 우호적이지 않다.

  • 어쨌든 프로젝트 안에 대량의 이벤트 상호작용이 있다면 EventBus를 통해 실현할 수 있다. 그렇지 않으면 모듈 내부에서 실현하는 것을 추천한다 EventBus 소스 분석
    EventBus.java
    원본 코드 읽기는 외관 클래스부터 시작하는데 여기EventBus.java는 핵심 인터페이스가 모두 이 클래스에서 이루어진다. 내용에 관심이 있는 호출자는register 방법을 사용하고 사건이 발생할 때onEvent에서 해당하는 리셋을 받는다.
    register(Object object);
    
    registerSticky(Object object);
    
    unRegister(Object object);
    
    post(Object object);

    먼저 초기화 부분을 보고, 어떻게 하나의 예를 실현할 수 있는지 봅시다.
    // volatile         ,        defaultInstance          ,           ,            。
    static volatile EventBus defaultInstance;
    /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        //           ,volatile   。    defaultInstance     ,
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                //         ,    defaultInstance        ,      double-check
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
    
    //        ,    defaultInstance              ,       lazy     
    //          
    static {
        defaultInstance = new EventBus();
    }
    EventBus Event Bus Builder를 구현하여 Builder 방식으로 구축할 때 더욱 쉽게
    public static EventBusBuilder builder() {
        return new EventBusBuilder();
    }

    다음 중점 보기register(Object subscriber, boolean sticky, int priority) 방법
    private synchronized void register(Object subscriber, boolean sticky, int priority) {
        //   subscriberMethodFinder      ,    subscriber      ,     。
        List<SubscriberMethod> subscriberMethods
            = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //       ,subscribe     
            subscribe(subscriber, subscriberMethod, sticky, priority);
        }
    }

    findSubscriberMethods라는 방법은 이벤트Bus를 실현하는 핵심 코드로 이벤트Bus가 은밀하게 정의한 상호작용 프로토콜을 포함한다.이 방법에서 이벤트버스의 사용을 어떻게 쟁취하는지 볼 수 있다.
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        String key = subscriberClass.getName();
        List<SubscriberMethod> subscriberMethods;
        //      Class         ,    。
        synchronized (methodCache) {
            subscriberMethods = methodCache.get(key);
        }
        //                synchronized     
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        subscriberMethods = new ArrayList<SubscriberMethod>();
        Class<?> clazz = subscriberClass;
        HashSet<String> eventTypesFound = new HashSet<String>();
        StringBuilder methodKeyBuilder = new StringBuilder();
        while (clazz != null) {
            String name = clazz.getName();
            //   JDK    
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                // Skip system classes, this just degrades performance
                break;
            }
    
            // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
            //          
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                String methodName = method.getName();
                if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
                    int modifiers = method.getModifiers();
                    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                        //     Public  
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length == 1) {
                            String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
                            ThreadMode threadMode;
    
                            //          ‘OnEvent’,    ‘OnEvent’,        ,      4      
                            // ThreadModel       
                            if (modifierString.length() == 0) {
                                threadMode = ThreadMode.PostThread;
                            } else if (modifierString.equals("MainThread")) {
                                threadMode = ThreadMode.MainThread;
                            } else if (modifierString.equals("BackgroundThread")) {
                                threadMode = ThreadMode.BackgroundThread;
                            } else if (modifierString.equals("Async")) {
                                threadMode = ThreadMode.Async;
                            } else {
                                if (skipMethodVerificationForClasses.containsKey(clazz)) {
                                    continue;
                                } else {
                                    throw new EventBusException("Illegal onEvent method, check for typos: " + method);
                                }
                            }
    
                            //       
                            Class<?> eventType = parameterTypes[0];
                            methodKeyBuilder.setLength(0);
                            methodKeyBuilder.append(methodName);
                            methodKeyBuilder.append('>').append(eventType.getName());
                            //             ,   onEventMainThread>DownloadInfo
                            String methodKey = methodKeyBuilder.toString();
                            if (eventTypesFound.add(methodKey)) {
                                // Only add if not already found in a sub class
                                subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
                            }
                        }
                    } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
                        Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
                                + methodName);
                    }
                }
            }
            //      EventBus     ,EventBus       
            clazz = clazz.getSuperclass();
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
                    + ON_EVENT_METHOD_NAME);
        } else {
            synchronized (methodCache) {
                methodCache.put(key, subscriberMethods);
            }
            return subscriberMethods;
        }
    }

    이제 subscriber Class의 내용을 이벤트버스에 구독하는 방법을 봅시다.
    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
        Class<?> eventType = subscriberMethod.eventType;
        //              Subscription 。     CopyOnWriteArrayList ,        ,
        // CopyOnWriteArrayList        ,       copy,         
        // copy,             。
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<Subscription>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }
    
        // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
        // subscriberMethod.method.setAccessible(true);
    
        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            //          ,               
            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
    
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<Class<?>>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
    
        if (sticky) {
            //       ,      Builder      ,     ,     20%       
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                //       sticky  event,          
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }
    //        
    public synchronized void unregister(Object subscriber) {
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                unubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
    //          
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        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 ThreadModel
    이벤트버스는 PostThread,MainThread,BackgroundThread,Async 등 총 4종의 Thread 모델을 제공했다.
  • PostThread 기본값, 실행이 동일한 스레드
  • MainThread가 UI 스레드에서 실행
  • BackgroundThread 콜백은 UI가 아닌 스레드에서 발생
  • Async는 다른 라인에서 영원히 실행됩니다
  • 상기 네 가지 유형은 에서 진행해야 할 비동기 처리를 충분히 지원할 수 있다.
    EventBus의 스레드 변환 방식
    무릇 실제 프로젝트를 겪으면'생산'과'소비'가 충돌하는 상황이 자주 존재하기 때문에 여기서'생산자와 소비자'모델을 사용해야 한다.
    이벤트버스에서 생산자와 소비자 모델의 실현은 주로 PendingPostQueue 안에 있다.
    PendingPostQueue의 실현은 비교적 간단하다. 주로 enqueue와poll에서 synchronized 동기화를 통해 이루어진다.
    synchronized void enqueue(PendingPost pendingPost) {
        if (pendingPost == null) {
            throw new NullPointerException("null cannot be enqueued");
        }
        //   Post        
        if (tail != null) {
            tail.next = pendingPost;
            tail = pendingPost;
        } else if (head == null) {
            //        ,          
            head = tail = pendingPost;
        } else {
            throw new IllegalStateException("Head present, but no tail");
        }
        notifyAll();
    }
    
    synchronized PendingPost poll() {
        PendingPost pendingPost = head;
        //      
        if (head != null) {
            head = head.next;
            if (head == null) {
                tail = null;
            }
        }
        return pendingPost;
    }
    //            PendingPost,         pendingPostPool    ,  PendingPost        ,         ,          。
    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);
            }
        }
    }

    EventBus 릴리즈 시점의
    //   ThreadModel (  PostThread)        Poster,   Post         ``        ``,         。
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case PostThread:
                invokeSubscriber(subscription, event);
                break;
            case MainThread:
                //     poster
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BackgroundThread:
                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);
        }
    }

    좋은 웹페이지 즐겨찾기