EventBus 소스 코드 깊이 분석

  • 이전의 사 고 를 분석
  • 4 개의 중요 한 데이터 구조
  • 등록 시의 행위
  • post 시의 행위
  • 등록 해제 시 행위
  • 캐 시 처리
  • 최적화 할 수 있 는 곳

  • 본 분석 을 바탕 으로 하 는 코드 는 여기 서 clone 에서 볼 수 있 습 니 다. 강력 한 건 의 는 코드 를 보면 서 본 고 를 조회 하 는 것 입 니 다. 본 글 은 주로 eventbus 의 핵심 사상 을 분석 합 니 다. 버 전 차이 로 인해 일부 세부 사항 이 다 를 수 있 지만 저 는 여러분 들 이 디 테 일 에 빠 져 헤 어 나 오지 못 하고 높 은 각도 에서 그의 사상 을 이해 하 는 것 이 우리 의 목적 이 라 고 생각 합 니 다. 따라서...일부 기본 개념 은 이미 많은 문장 이 이미 말 했 으 니, 다음 글 은 더 이상 군말 하지 않 겠 다.
    이전의 사 고 를 분석 하 다
  • eventbus 의 용법 은 모두 가 잘 알 고 있 습 니 다. 다음 의 가장 간단 한 상황 을 보 세 요.
  • //  RegisterActivity.java     
    // onCreate   
    Bus.getDefault().register(this);
    // onDestroy   
    Bus.getDefault().unRegister(this);
    
    //      
        @BusReceiver(mode = EventMode.Thread)
        public void onThreadEvent(final String event) {
            Log.v(TAG, "onThreadEvent=" + Thread.currentThread().getName());
            appendLog("onThreadEvent event=" + event
                    + " thread=" + Thread.currentThread().getName());
        }
     //  PostActivity.java     
     // onCreate   
     Bus.getDefault().post("Hello EventBus");
  • 우리 가 스스로 실현 한다 면 우 리 는 어떤 문 제 를 고려 해 야 하 는 지 생각해 보 자. 나 는 아래 에 우리 가 고려 해 야 할 핵심 문 제 를 열거 했다.
  • 우 리 는 등록 류 의 어떤 정 보 를 저장 해 야 합 니까? 어떻게 저장 해 야 합 니까?
  • 우 리 는 매개 변수의 어떤 정 보 를 저장 해 야 합 니까? 어떻게 저장 해 야 합 니까?
  • 우 리 는 어떤 함수 가 호출 될 지 어떻게 확정 해 야 합 니까?
  • 우 리 는 그들 이 특정한 라인 에서 운행 하 는 것 을 어떻게 통제 해 야 합 니까?
  • 우 리 는 캐 시 기능 을 고려 할 필요 가 있 습 니까? 어떻게 프레임 의 성능 을 향상 시 킵 니까?
  • 우 리 는 다음 에 상기 문 제 를 해결 하고 다시 한 번 강조 한다. 이때 너 는 반드시 완전한 소스 코드 를 가 져 야 한다. 나의 분석 은 하나의 도입부 로 서 핵심 사상 을 설명 할 수 밖 에 없다. 그 다음 에 네가 코드 에 깊이 들 어가 그것 을 관통 시 켜 야 한다
  • .
    4 개의 중요 한 데이터 구조
  • 표지 의 주해 구 조 는 매우 기본 적 이 고 코드 를 직접 볼 수 있 습 니 다. Bus. EventMode 는 매 거 진 유형 으로 세 가지 값 이 있 습 니 다. Sender, Main, Thread 는 각각 다른 스 레 드
  • 를 표시 합 니 다.
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface BusReceiver {
        //               
        Bus.EventMode mode() default Bus.EventMode.Main;
    }
  • 먼저 중요 한 문 제 를 해결 합 니 다. 우 리 는 무엇 을 저장 하고 있 습 니까? 어떻게 저장 해 야 합 니까? 우 리 는 특수 한 주해 방법 (Method 대상) 을 저장 합 니 다. 이 방법 중의 매개 변수, 등 록 된 대상 과 해당 하 는 주 해 를 저장 하기 때문에 우 리 는 첫 번 째 데이터 구조 Bus.getDefault().post("Hello EventBus");
  • 를 조립 했 습 니 다.
    public class MethodInfo {
        //     
        public final Method method;
        //      Class  
        public final Class> targetType;
        //    Class  
        public final Class> eventType;
        //          
        public final String name;
        //    mode 
        public final Bus.EventMode mode;
    
        public MethodInfo(final Method method, final Class> targetClass, final Bus.EventMode mode) {
            this.method = method;
            this.targetType = targetClass;
            this.eventType = method.getParameterTypes()[0];
            this.mode = mode;
            this.name = targetType.getName() + "." + method.getName()
                    + "(" + eventType.getName() + ")";
        }
    
        // else codes
    }
  • 다시 한 번 포장 하여 결합 을 최대 화하 고 MehodInfo 는 해당 데이터 구 조 를 저장 하 는 직책 만 맡 게 하 며 Subscriber 로 하여 금 구체 적 인 집행 기능 을 하 게 합 니 다. invoke 함수
  • 에 주의 하 십시오.
    class Subscriber {
        public final MethodInfo method;
        public final Object target;
        //      4         ,     MethodInfo   
        public final Class> targetType;
        public final Class> eventType;
        public final Bus.EventMode mode;
        public final String name;
    
        public Subscriber(final MethodInfo method, final Object target) {
            this.method = method;
            this.target = target;
            this.eventType = method.eventType;
            this.targetType = method.targetType;
            this.mode = method.mode;
            this.name = method.name;
        }
    //               ,     event post   
        public Object invoke(Object event)
                throws InvocationTargetException, IllegalAccessException {
            return this.method.method.invoke(this.target, event);
        }
        // else codes
    }
  • Subscriber 는 호출 함수 만 맡 았 을 뿐 어느 스 레 드 에서 실행 해 야 하 는 지 책임 지지 않 았 습 니 다. 그 러 니 한 층 더 포장 해 야 합 니 다. EventEmitter 는 Runnable 대상 이기 때문에 우 리 는 Handler, Executor 를 사용 하여 어느 스 레 드 에서 실행 되 는 지 제어 할 수 있 습 니 다!
  • public class EventEmitter implements Runnable {
        private static final String TAG = Bus.TAG;
    
        public final Bus bus;
        public final Object event;
        public final Subscriber subscriber;
        public final Bus.EventMode mode;
        public final boolean debug;
    
        public EventEmitter(final Bus bus, final Object event,
                            final Subscriber subscriber, final boolean debug) {
            this.bus = bus;
            this.event = event;
            this.subscriber = subscriber;
            this.mode = subscriber.mode;
            this.debug = debug;
    
        }
    
        @Override
        public void run() {
            try {
                if (debug) {
                    Log.v(TAG, "sending event:[" + event
                            + "] to subscriber:[" + subscriber
                            + "] at thread:" + Thread.currentThread().getName());
                }
                subscriber.invoke(event);
            } catch (Exception e) {
                if (debug) {
                    Log.e(TAG, "sending event:[" + event + "] to subscriber:["
                            + subscriber + "] failed, reason: " + e, e);
                }
            }
        }
    
        @Override
        public String toString() {
            return "{" +
                    "event:[" + event +
                    "] to subscriber:[" + subscriber +
                    "]}";
        }

    네 개의 데이터 구 조 를 본 후에 그의 교묘 한 디자인 에 감탄 하고 층 층 포장 을 이용 하여 결합 을 풀 며 단일 한 직책 의 원칙 에 부합 되 고 구 조 를 뚜렷하게 유지 하기 쉽다.
    등록 시 행동MethodInfo
  • 등록 할 때 우 리 는 주로 두 가지 일 을 했다
  • 첫 번 째 일 은 등록 류 의 전체 유형 을 key 라 고 하 는데 그 중의 모든 만족 조건 을 만족 시 키 는 방법 은 Value 이 고 하나의 집합 에 저장 합 니 다 Bus.getDefault().register(this)
  • 두 번 째 일 은 매개 변수 유형 을 key 로 하고 일치 하 는 방법 집합 은 Value 로 Concurrent HashMap (방법 매개 변 수 를 기준 으로 분할 함수) final static Map> sMethodCache = new ConcurrentHashMap>();
  • 로 저장 합 니 다.
  • 이 과정 에서 중요 한 방법 을 살 펴 보 자
  •     //                ,      
       private Set getMethods(Class> targetClass) {
            //           key
            String cacheKey = targetClass.getName();
            Set methods;
            //        ,                ,         
            synchronized (Cache.sMethodCache) {
                methods = Cache.sMethodCache.get(cacheKey);
            }
            if (methods == null) {
                /**
                 *                  
                 */
                methods = mMethodFinder.find(this, targetClass);
                synchronized (Cache.sMethodCache) {
                    Cache.sMethodCache.put(cacheKey, methods);
                }
            }
            return methods;
        }
  • 따라 들 어가 서 private final Map, Set> mSubscriberMap; 의 구체 적 인 실현 을 보 세 요. 코드 는 간단 합 니 다. MethodHelp. java 류 에서 보 세 요. 간단 한 정 리 를 하 세 요. 이러한 역할 은 요구 에 부 합 된 Method 조합 을 MethodInfo 집합 으로 선별 하 는 것 입 니 다. 선택 한 규칙 은 다음 과 같은 몇 가지 가 있 습 니 다 (isValid Method 함수 에서)
  • 우선 BusReceiver 주 해 를 가 져 야 합 니 다. 이것 은 큰 전제 이 고 그 다음 에 다음 과 같은 네 가지 작은 조건
  • 을 만족 시 켜 야 합 니 다.
  • mMethodFinder.find(this, targetClass); 함 수 는 반드시 Public
  • 이 어야 합 니 다.
  • !Modifier.isPublic(method.getModifiers()) 함 수 는 정적
  • 이 아니다.
  • Modifier.isStatic(method.getModifiers()) 함 수 는 하나의 매개 변수
  • 만 있 을 수 있 습 니 다.
  • method.getParameterTypes().length != 1 이 조건 은 매우 중요 합 니 다. getDeclared Methods 에 존재 하 는 bug 를 복원 하 는 것 입 니 다. 여기
  • 를 참고 하 십시오.
        public static Set findSubscriberMethodsByAnnotation(
                final Class> targetClass) {
            final MethodConverter converter = new MethodConverter() {
                @Override
                public MethodInfo convert(final Method method) {
                    // check annotation
                    final BusReceiver annotation = method.getAnnotation(BusReceiver.class);
                    if (annotation == null) {
                        return null;
                    }
                    if (!isValidMethod(method)) {
                        return null;
                    }
                    return new MethodInfo(method, targetClass, annotation.mode());
                }
            };
            return findSubscriberMethods(targetClass, converter);
        }
            /**
         *           class  ,   "         ",    ,          ,        while  ,
         *        for  
         */
        public static Set findSubscriberMethods(
                final Class> targetClass, MethodConverter converter) {
            Class> clazz = targetClass;
            final Set methods = new HashSet();
            while (!shouldSkipClass(clazz)) {
                final Method[] clsMethods = clazz.getDeclaredMethods();
                for (final Method method : clsMethods) {
                    final MethodInfo methodInfo = converter.convert(method);
                    if (methodInfo != null) {
                        methods.add(methodInfo);
                    }
                }
                // search more methods in super class
                clazz = clazz.getSuperclass();
            }
            return methods;
        }

    행동
  • 저장 소 는 매개 변수 형식 을 모두 key (예 를 들 어 자바. lang. String) 라 고 합 니 다. 이 매개 변수 유형의 부모 유형 이나 인터페이스 유형 은 value (부모 유형 과 인터페이스 유형 도 가입 한 것 은 호환성 을 고려 하여 엄격 한 일치 매개 변수 없 이 함 수 를 조절 할 수 있 기 때 문 입 니 다)
    매개 변수 형식 을 key 로 하 는 이 유 는 이벤트 버스 가 함수 이름 이 아 닌 매개 변수 형식 을 호출 하 는 지 확인 하기 때 문 입 니 다. 따라서 post 의 매개 변수 가 설명 한 리 셋 함수 의 매개 변수 와 호 환 되 기만 하면 호출 할 수 있 습 니 다!
  •  final static Map>> sEventTypeCache = new ConcurrentHashMap>>();
  • 매개 변수 유형의 모든 호 환 유형 을 얻 은 후 전달 해 야 할 값 과 해당 하 는 호 환 유형
  • 을 한 번 에 전달 합 니 다.
            for (Class> eventType : eventTypes) {
                postEventByType(event, eventType);
            }
  • post EventByType 함수 따라 가기
  •     /**
         *                  
         *
         * @param event         
         * @param eventType        
         * @param            
         */
        private synchronized  void postEventByType(final E event, final Class> eventType) {
            final Set subscribers = mSubscriberMap.get(eventType);
            if (subscribers == null || subscribers.isEmpty()) {
                return;
            }
            for (Subscriber subscriber : subscribers) {
                sendEvent(new EventEmitter(this, event, subscriber, mDebug));
            }
        }
  • 앞에서 말 했 듯 이 EventEmitter 의 유형 과 역할 은 sendEvent 방법 을 계속 따라 가 는 것 입 니 다. 이 방법 은 주석 모델 에 따라 해당 하 는 종 류 를 선택 하여 emitter 의 run 방법 을 실행 하 는 것 입 니 다. 이 방법 은 논리 가 비교적 간단 합 니 다. 코드 에서 Schedulers 류 를 보 세 요. 공장 모델 로 각각 세 개의 키 종 류 를 만 듭 니 다. Sender Scheduler (emitter 의 run 방법 을 직접 호출 하여 동기 화 방식 으로 실행 합 니 다)현재 스 레 드 에서 실 행 됩 니 다. Handler Scheduler (Handler 의존) 는 메 인 스 레 드 에서 실 행 됩 니 다. Executor Scheduler (Executor 의존) 는 비동기 스 레 드 에서 실 행 됩 니 다. 마지막 까지 Emitter run 방법
  • 에 실 행 됩 니 다.
        public void sendEvent(EventEmitter emitter) {
            if (mDebug) {
                Log.v(TAG, "send event:" + emitter);
            }
            if (EventMode.Sender.equals(emitter.mode)) {
                mSenderScheduler.post(emitter);
            } else if (EventMode.Main.equals(emitter.mode)) {
                if (Helper.isMainThread()) {
                    mSenderScheduler.post(emitter);
                } else {
                    mMainScheduler.post(emitter);
                }
            } else if (EventMode.Thread.equals(emitter.mode)) {
                mThreadScheduler.post(emitter);
            }
        }
  • emitter 의 run 방법 을 보 세 요. 아주 간단 합 니 다. 마지막 까지 실 행 됩 니 다. 다만 오 는 경로 가 다 릅 니 다. (위 에서 분석 한 세 가지 책임)아래 에서 볼 수 있 듯 이 최종 적 으로 실 행 된 것 은 Subscriber 의 invoke 방법 입 니 다. 우 리 는 위 에서 분석 한 바 와 같이 이 방법 은 해당 하 는 method 를 반사 적 으로 조정 하 는 것 입 니 다. 여기 서 분석 을 마 쳤 습 니 다. 최종 적 으로 등록 류 에 해당 하 는 방법 을 성공 적 으로 호출 할 것 입 니 다
  •     @Override
        public void run() {
            try {
                subscriber.invoke(event);
            } catch (Exception e) {}
        }
    

    등록 해제 시 행동
  • 이 문 제 를 생각해 보 셨 는 지 모 르 겠 습 니 다. post 를 사용 할 때 어떤 방법 이 매개 변 수 를 바탕 으로 판단 하 는 지 모 르 겠 습 니 다. 그러면 저희 가 등록 을 풀 때 등 록 된 모든 방법 을 어떻게 확인 하 는 지 저 희 는 이런 방법 을 mSubscriber Map 에서 제거 해 야 합 니 다. 정확성 을 확보 하고 오 류 를 제거 하지 않 아야 합 니 다. 그래서 데이터 구 조 를 하나 더 만들어 서 이 일 을 해 야 합 니 다. key바로 우리 의 클래스 대상 입 니 다. 그 value 는 우리 클래스 대상 에서 방법 에 부합 되 는 모든 매개 변수 대상 입 니 다.(왜 매개 변수 대상 을 value 로 하 는 지 생각해 보 세 요. 방법 대상 을 value 로 하 는 것 이 아 닙 니 다. mSubscriber Map 이 있 기 때문에 매개 변수 대상 에 따라 방법 대상 을 추출 하고 방법 대상 Subscriber 의 target 속성 을 통 해 현재 등록 대상 과 비교 할 수 있 습 니 다. 일치 하면 이 방법 대상 을 mSubscriber Map 에서 삭제 할 수 있 습 니 다) !Modifier.isVolatile(method.getModifiers())
  •     public  void unregister(final T target) {
            if (mDebug) {
                Log.v(TAG, "unregister() target:" + target);
                mStopWatch.start("unregister()");
            }
            //mEventMap        register   
            final Set> eventTypes = mEventMap.remove(target);
            if (eventTypes == null || eventTypes.isEmpty()) {
                Log.v(TAG, "unregister() no subscriber for target:" + target);
                return;
            }
            for (Class> eventType : eventTypes) {
                Set subscribers = mSubscriberMap.get(eventType);
                if (subscribers == null || subscribers.isEmpty()) {
                    continue;
                }
                synchronized (mSubscriberMap) {
                    Iterator it = subscribers.iterator();
                    while (it.hasNext()) {
                        final Subscriber subscriber = it.next();
                        if (subscriber.target == target) {
                            it.remove();
                            }
                        }
                    }
                }
            }
        }

    캐 시 처리
  • 이벤트 의 성능 병목 은 두 가지 측면 에서 나타난다.
  • 첫째, 우 리 는 특정한 요구 에 부합 되 는 방법 을 찾 아 저장 하 는 것 이다. 왜냐하면 우 리 는 현재 종 류 를 찾 아야 할 뿐만 아니 라 부모 종 류 를 찾 아야 하기 때문이다. 이것 은 적지 않 은 비용
  • 이다.
  • 둘째, 우 리 는 호 환 되 는 모든 매개 변수 유형 을 찾 아 저장 해 야 한다. 이것 도 적지 않 은 비용
  • 이다.
  • 상기 설명 에 따라 우 리 는 두 개의 데이터 구 조 를 이용 하여 그들 을 첫 번 째 search 때 캐 시 했다. 하 나 는 완전한 유형 과 대응 하 는 모든 요구 에 부합 되 는 방법 이 고 다른 하 나 는 완전한 매개 변수 이름과 모든 호 환 유형
  • 이다.
        static class Cache {
    
            // key=         target.getClass().getName()
            // value=         @BusReceiver      
            // targetTypeName->method set
            final static Map> sMethodCache =
                    new ConcurrentHashMap>();
            // key=         
            // value=            
            // eventTypeName-> event type set
            final static Map>> sEventTypeCache =
                    new ConcurrentHashMap>>();
        }
  • 둘 이 사용 하고 채 우 는 위 치 는 각각 register () 와 unRegister () 함수 에 있 습 니 다. AndroidStudio 에서 command + F 와 command + G 를 사용 하여 직접 확인 하 십시오.
  • 최적화 할 수 있 는 곳.
  • 우리 가 요구 에 부합 되 는 방법 을 찾 으 러 갈 때 시스템 등급 의 방법 을 찾 을 필요 가 없다. 예 를 들 어 Object 류 중의 방법 을 찾 으 면 우 리 는 작은 최적화 를 할 수 있다. 아래 코드 를 보 세 요. 코드 는 간단 하고 군말 하지 않 습 니 다
  •     public static boolean shouldSkipClass(final Class> clazz) {
            if (clazz == null || Object.class.equals(clazz)) {
                return true;
            }
            final String clsName = clazz.getName();
            return clsName.startsWith("java.")
                    || clsName.startsWith("javax.")
                    || clsName.startsWith("android.")
                    || clsName.startsWith("com.android.");
        }

    좋은 웹페이지 즐겨찾기