preface

The last article covered some of the basics of annotations, but it’s important to keep in mind that this article starts with a look at how common libraries use annotations. Let’s start by looking at the use of runtime annotations, using the EventBus library as an example.

Articles in this series:

1. Introduction to basic concepts

The body of the

Official project address for EventBus: github.com/greenrobot/…

And it’s very simple to use.

EventBus is easy to use

Here’s a quick overview of how to use EventBus.

  • Create an event
public static class MessageEvent { /* Additional fields if needed */ }
Copy the code
  • Register a subscriber, which is what you do when an event is received
// Use annotations to indicate that this method is automatically called when an event is received on the main thread
@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {
    // Do something
}
Copy the code
  • Register and unregister subscribers. In Android, if you listen for events on a page, you register before the page is visible and unregister when the page is destroyed. If Lifecycle is combined with Jetpack, manual registration and unregister may not be required
 @Override
 public void onStart() {
     super.onStart();
     // Register in onStart
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     // Unregister in onStop
     EventBus.getDefault().unregister(this);
 }
Copy the code
  • Send the event
EventBus.getDefault().post(new MessageEvent());
Copy the code

The above steps are the easiest way to use EventBus, but the focus of this article is not there. We will have a chance to analyze the principles of EventBus later, but we will only discuss annotations.

Runtime annotations

If you think about it, and combine that with the previous article, you can do something similar with compile-time annotations, but not for now.

Runtime annotations, which do functional operations while the code executes the binary, such as the EventBus library, involve finding methods that need to be executed when an event is emitted.

Run-time annotations are usually done by reflection, so using run-time annotations can be divided into two parts, declaring annotations and parsing annotations when appropriate:

The statement notes

If you think of declarative annotations, remember those meta annotations, because meta annotations are used to modify custom annotations. Let’s look at the @SUBSCRIBE annotation declaration in EventBus:

@Documented
@Retention(RetentionPolicy.RUNTIME)  // Annotations are still valid at runtime
@Target({ElementType.METHOD})        // the annotation modifies the method
public @interface Subscribe {

    // Annotated arguments can be set to default values
    ThreadMode threadMode() default ThreadMode.POSTING;

    boolean sticky() default false;

    int priority() default 0;
}
Copy the code

We declare a runtime annotation. We use annotations for convenience and brevity, so we don’t use too many arguments and separate them with commas:

@Subscribe(threadMode = ThreadMode.MAIN, sticky = false, priority = 0)
Copy the code

For example:

@Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0)
public void onEventMainThread(TestFinishedEvent event) {
      // Execute when an event is received when appropriate
}
Copy the code

Next, see how to parse annotations.

Parsing the annotation

Also, in the case of EventBus, we need to register and unregister subscribers. In this case, subscribers are usually activities or fragments:

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

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

Copy the code

So the logic here is pretty clear: When an Android application executes to the page, it registers the page class with the EventBus system, and then calls the corresponding method in the class when an event occurs.

So look directly at the register() code:

// Register a subscriber, which in Android is an Activity or Fragment class
public void register(Object subscriber) {
    // Get the subscriber's ClassClass<? > subscriberClass = subscriber.getClass();// How to find subscribers
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    // Synchronize the operation
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            / / subscribesubscribe(subscriber, subscriberMethod); }}}Copy the code

The logic here is very simple, but many developers who are not familiar with reflection will find it difficult to read the source code because of the lack of exposure to the API.

So let’s take a quick look at Class types.

The Class type

Class is the type of a Class, which is easy to understand. Now you can forget about the business logic of a Class, and just think about the parts of a Class, which are made up of, as in the following code:

// The logic is not to look at, look at the components

// The declaration part of the class has an inherited parent class
public class TestRunnerActivity extends Activity {
    
    // Class variable
    private TestRunner testRunner;
    private EventBus controlBus;
    private TextView textViewResult;

    // method with annotations
    @Override
    // A method can be thought of as consisting of a method name, parameters, annotations, etc
    public void onCreate(Bundle savedInstanceState) {
        // ...
    }

    // method with annotations
    @Override
    protected void onResume() {
        super.onResume();
        // ...
    }

    // with the annotations we want
    @Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0)
    public void onEventMainThread(TestFinishedEvent event) {
        // ...
    }

    / / method
    public void onClickCancel(View view) {
        // ...
    }

    public void onClickKillProcess(View view) {
       // ...
    }

    public void onDestroy() {
        // ...}}Copy the code

Add the component information of the above Class to the Class, so you can do the following:

/ / package name
String getName = subscriberClass.getName();
String getSimpleName = subscriberClass.getSimpleName();
// constructorConstructor<? > getCon = subscriberClass.getConstructor(); Constructor<? >[] getDeclCon = subscriberClass.getDeclaredConstructors();/ / field
Field[] getFields = subscriberClass.getFields();
Field[] getDeclFields = subscriberClass.getDeclaredFields();
/ / method
Method[] getM = subscriberClass.getMethods();
Method[] getDeclM = subscriberClass.getDeclaredMethods();
// Type of the direct superclass
Type getGenSuperClas = subscriberClass.getGenericSuperclass();
// The interface implemented by the current classClass<? >[] getInter = subscriberClass.getInterfaces();/ / modifier
int getMod = subscriberClass.getModifiers();
Copy the code

The corresponding method and result of the above code are as follows:

You can click on the image to zoom in and see the result. With these methods, you can easily get information about a class, such as fields and methods.

GetDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX

OK, the idea is that we can get the methods of a Class based on its Class type, and then find the methods we need, which have some specific annotations added, and we’ll move on.

Find the annotation information

Source code a lot, but we are very clear, according to the Class to find the method of the Class, and then processing, source code corresponding method:

// Get the method that needs to be processed by reflection
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    // Get all the methods declared by the current class
    methods = findState.clazz.getDeclaredMethods();
    // the traversal method
    for (Method method : methods) {
        // Get the modifier of the method
        int modifiers = method.getModifiers();
        // The method must be public
        if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            // Get the method parameter typeClass<? >[] parameterTypes = method.getParameterTypes();// Since the subscriber calls this method with only one argument, it filters
            if (parameterTypes.length == 1) {
                // Get the annotation of the method
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if(subscribeAnnotation ! =null) {
                    // Get the type of the first argumentClass<? > eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        // Get information about annotations
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // Process the annotated information
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } 
        } 
    }
}
Copy the code

In fact, the above code with comments is very easy to understand, but for this kind of code that is not often used, the first contact is relatively large, feel not familiar with the API, in fact, each API can be judged by its name, the most important method is:

Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
Copy the code

Use this to get the annotation information, and then get the parameter and method information in the annotation, and then process it.

At this point, we’re basically done analyzing how annotations work in EventBus.

conclusion

Runtime annotations are things that are done while the code is running, so parsing annotations is usually done while the code is running, and parsing annotations is done by reflection, which is a little unfamiliar because the reflection API is not used very often.

Again, reflection can lead to a bit of a performance degradation, so in the next article we’ll look at how annotations are used at compile time.