Android EventBus usage optimization

  In the previous article Android EventBus usage understanding Finally, I suspected that the reflection search subscription method would affect the efficiency, and I saw on the official website that it is recommended to use Subscriber Index in APP products. The reason given is to be faster and avoid crashes.
  Use Subscriber Index to avoid expensive subscription method lookups at runtime using reflection. It uses annotation processors to look up subscription methods at compile time.
  It's really amazing! The overhead at runtime is done at compile time.

fulfil requirements

  • The subscription method and subscriber class must be public
  • The event class must be public
  • The subscription method cannot be placed in an anonymous class

how to generate index

  If it is Java language, use annotation processor. If you use Kotlin, use Kapt.
  Here we mainly talk about the configuration method using Java. Since my build.gradle.kts is a Kotlin script, I will post my configuration.

android {
    
        
    defaultConfig {
    
        
        javaCompileOptions {
    
    
            annotationProcessorOptions.arguments["eventBusIndex"] = "com.example.testeventbus.MyEventBusIndex"
        }
    }    
}
dependencies {
    
    
    val eventbus_version = "3.3.1"
    implementation("org.greenrobot:eventbus:$eventbus_version")
    annotationProcessor("org.greenrobot:eventbus-annotation-processor:$eventbus_version")   
}         

how to use index

  First you need to build the project. As I configured, it will automatically generate com.example.testeventbus.MyEventBusIndex class.
  Where the code is initialized, before calling EventBus.getDefault(), you need to call EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus().
  That's it, nothing else needs to be done.

Combine the code to analyze the index

Let's analyze it on the basis of the example of Android EventBus usage and understanding   in the previous article .
  It is mainly to analyze the logic of the current query subscription method.

added class

  The class automatically generated using annotations is com.example.testeventbus.MyEventBusIndex, take a look at its implementation:

public class MyEventBusIndex implements SubscriberInfoIndex {
    
    
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
    
    
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
    
    
            new SubscriberMethodInfo("onMessageEvent", MessageEvent.class, ThreadMode.MAIN),
            new SubscriberMethodInfo("onMessage", MessageEvent.class, ThreadMode.MAIN),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
    
    
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
    
    
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
    
    
            return info;
        } else {
    
    
            return null;
        }
    }
}

  This class inherits SubscriberInfoIndex and implements the getSubscriberInfo(Class<?> subscriberClass) method. At the same time, we can see that in the static code block of the MyEventBusIndex class, the relevant information of the subscription method is put into the static member SUBSCRIBER_INDEX.
  In this example, the key of SUBSCRIBER_INDEX is MainActivity.class, and MainActivity is the subscriber object. SUBSCRIBER_INDEX actually uses the Class of the subscriber object as the key, and the value is the SubscriberInfo implementation class. Here, its value is a SimpleSubscriberInfo class object.
  The third parameter initialized by SimpleSubscriberInfo is an array of SubscriberMethodInfo, and SubscriberMethodInfo is used to describe the subscription method. In the example, we declared two methods, so there will be two arrays. In the understanding of using Android EventBus , we found these two methods through reflection. Now we have found these two methods directly. This is what the official website means to search at runtime and search at compile time.

Put the generated Index into EventBus.getDefault()

  Because we implement related operations through EventBus.getDefault() every time we call, we need to put the generated index into EventBus.getDefault() for it to work. It is done through EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus().
  First look at EventBus.builder().addIndex(new MyEventBusIndex())

    /** Adds an index generated by EventBus' annotation preprocessor. */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
    
    
        if (subscriberInfoIndexes == null) {
    
    
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

  It can be seen that the newly generated MyEventBusIndex is added to the member variable subscriberInfoIndexes. subscriberInfoIndexes is of type ArrayList.
  Look at the installDefaultEventBus() method again:

    /**
     * Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be
     * done only once before the first usage of the default EventBus.
     *
     * @throws EventBusException if there's already a default EventBus instance in place
     */
    public EventBus installDefaultEventBus() {
    
    
        synchronized (EventBus.class) {
    
    
            if (EventBus.defaultInstance != null) {
    
    
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }

    /** Builds an EventBus based on the current configuration. */
    public EventBus build() {
    
    
        return new EventBus(this);
    }    

  Generate EventBus.defaultInstance directly on build(). Call EventBus.getDefault() later to get EventBus.defaultInstance.
  EventBus initializes the code about index:

    EventBus(EventBusBuilder builder) {
    
    
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = 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;
    }

  The main thing is the middle two sentences about indexCount and subscriberMethodFinder. Set the Index collection to the SubscriberMethodFinder class object subscriberMethodFinder. We know that SubscriberMethodFinder is about finding subscription methods. When searching, it is related to subscriberInfoIndexes. When Index is not set before, it is empty and skipped. Now that the value is set, it's time to revisit the logic of finding the subscription method.

Find a way to subscribe

  We don't have to start from the beginning, just jump to the relevant code, just findUsingInfo(Class<?> subscriberClass) in the subscriberMethodFinder classmethod:

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    
    
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
    
    
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
    
    
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
    
    
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
    
    
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
    
    
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

  The key change is getSubscriberInfo(findState). The findState.subscriberInfo returned by this piece was empty before, so findUsingReflectionInSingleClass(findState) will be used. Now it returns not empty, so there is no need to use the reflection search method.
  Now take a look at getSubscriberInfo(findState):

    private SubscriberInfo getSubscriberInfo(FindState findState) {
    
    
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
    
    
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
    
    
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
    
    
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
    
    
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
    
    
                    return info;
                }
            }
        }
        return null;
    }

  At this time, findState.subscriberInfo is null, but subscriberInfoIndexes is not null anymore, because we assigned a value. For our example, there is a MyEventBusIndex object in subscriberInfoIndexes. Then call its getSubscriberInfo(Class<?> subscriberClass) method. It returns a SimpleSubscriberInfo object. Then the object is returned.
  Return to the findUsingInfo() method, and then call SimpleSubscriberInfo's getSubscriberMethods(). as follows:

    @Override
    public synchronized SubscriberMethod[] getSubscriberMethods() {
    
    
        int length = methodInfos.length;
        SubscriberMethod[] methods = new SubscriberMethod[length];
        for (int i = 0; i < length; i++) {
    
    
            SubscriberMethodInfo info = methodInfos[i];
            methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
                    info.priority, info.sticky);
        }
        return methods;
    }

  There are now 2 SubscriberMethodInfo in SimpleSubscriberInfo, and now it is necessary to convert SubscriberMethodInfo into SubscriberMethod, which is realized by calling createSubscriberMethod().

    protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode,
                                                      int priority, boolean sticky) {
    
    
        try {
    
    
            Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
            return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
        } catch (NoSuchMethodException e) {
    
    
            throw new EventBusException("Could not find subscriber method in " + subscriberClass +
                    ". Maybe a missing ProGuard rule?", e);
        }
    }

  SubscriberClass is the Class of the subscriber class object. Here, the corresponding Method is obtained through getDeclaredMethod(), so that the SubscriberMethod object can be generated.
  In this way, return to the findUsingInfo() method, and start to judge whether the subscription method can be added to the result set through findState.checkAdd() again. This judgment logic is in the previous article Android EventBus usage understanding .
  Now you understand how Index can replace the method of finding subscriptions through reflection.

Guess you like

Origin blog.csdn.net/q1165328963/article/details/132240392