preface

I’m sure you’ve all seen annotations a lot, whether it’s @Override in Android or Java or some of the open source libraries we use a lot, for the simple reason that annotations make code simpler, Libraries like Retrofit, PermissionsDispatcher, etc., but very few of them define annotations themselves.

So in this article we will dig into the use of custom annotations below, and take an open source library to parse the source code, and analyze the principle.

The body of the

Let’s start with some basics about annotations.

Yuan notes

Meta annotations are used to define custom annotations, that is, annotations, such as where we need to use our custom annotations, the duration of the annotation and other attributes are explained through meta annotations.

Let’s go straight to the summary

At first glance, this is a bit too much to remember, so you don’t have to remember all of them. First of all, the two annotations @target and @Retention are mainly used.

Where you want the annotation to be used, use @target. RUNTIME annotations and compile-time annotations are used for RUNTIME annotations and compile-time annotations for CLASS annotations. These two types of annotations are very different, and their implementation and emphasis are different as follows:

Well, as you can see from this, some open source libraries sometimes need to build after adding annotations to use some generation methods, such as compile-time annotations, which are done by generating new files.

So in considering what the custom type annotations, is also from the difference between the above consideration, such as I need to get what properties or resources at runtime will use the runtime annotations, I need a faster performance, for example, the compiler generate file, using generated by runtime annotations are used to complete the requirements.

Let’s take a look at the use of these two annotations, again using the source code of the parsing library.

Runtime annotations

There is a well-known runtime annotation library implementation called EventBus, which can be used at github.com/greenrobot/…

It’s easy to use. In Android, it’s used to communicate between components,

The post method is called in one place and registered subscribers receive messages using annotations, such as the following code:

@Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0) public void onEventMainThread(TestFinishedEvent event) { Test test = event.test; String text = "<b>" + test.getDisplayName() + "</b><br/>" + // test.getPrimaryResultMicros() + " micro seconds<br/>" + // ((int) test.getPrimaryResultRate()) + "/s<br/>"; if (test.getOtherTestResults() ! = null) { text += test.getOtherTestResults(); } text += "<br/>----------------<br/>"; textViewResult.append(Html.fromHtml(text)); }Copy the code

The onEventMainThread method here receives an event of type POST, so instead of talking about how eventBus publishes and subscribs, we’ll focus on the @subscribe annotation.

According to the annotation type described in the previous section, runtime annotations are usually done by reflection, so there are two basic steps:

The statement notes

Take a look at the @SUBSCRIBE annotation declaration in EventBus:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    boolean sticky() default false;

    int priority() default 0;
}
Copy the code

From the meta-annotation above we can see that this annotation must be used on the method and retained at runtime.

Note that annotations are usually used for convenience and to keep the code simple. Therefore, there are usually not too many parameters. When using annotations, they can be separated by commas.

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

Now that I’ve defined a subscriber in a class, when should I add that subscriber to the EventBus system? See the official usage documentation:

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

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

Copy the code

Register directly at the beginning and end of the component’s life cycle, and subscribers in this component are added to the system, so the annotation parsing step here should be done in the Register method.

Parsing the annotation

Look directly at the register() code:

public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code

I’m sure a lot of people are a little scared when they see the Class object in the getClass() method, because they don’t have enough access to it and feel like this part of the code is hard to understand.

Following the previous thought, runtime annotations are finding annotations, finding their parameters, and then doing things. The key here is to find annotations and parameters, and one of them is this Class type, so let’s talk about that first.

The Class type

Looking directly at the code, such as the Activity here, the code is:

Public class TestRunnerActivity extends Activity {private TestRunner TestRunner; private EventBus controlBus; private TextView textViewResult; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_runtests); textViewResult = findViewById(R.id.textViewResult); controlBus = new EventBus(); controlBus.register(this); } @Override protected void onResume() { super.onResume(); if (testRunner == null) { TestParams testParams = (TestParams) getIntent().getSerializableExtra("params"); testRunner = new TestRunner(getApplicationContext(), testParams, controlBus); if (testParams.getTestNumber() == 1) { textViewResult.append("Events: " + testParams.getEventCount() + "\n"); } textViewResult.append("Subscribers: " + testParams.getSubscriberCount() + "\n\n"); testRunner.start(); } } @Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0) public void onEventMainThread(TestFinishedEvent  event) { Test test = event.test; String text = "<b>" + test.getDisplayName() + "</b><br/>" + // test.getPrimaryResultMicros() + " micro seconds<br/>" + // ((int) test.getPrimaryResultRate()) + "/s<br/>"; if (test.getOtherTestResults() ! = null) { text += test.getOtherTestResults(); } text += "<br/>----------------<br/>"; textViewResult.append(Html.fromHtml(text)); if (event.isLastEvent) { findViewById(R.id.buttonCancel).setVisibility(View.GONE); findViewById(R.id.textViewTestRunning).setVisibility(View.GONE); findViewById(R.id.buttonKillProcess).setVisibility(View.VISIBLE); } } public void onClickCancel(View view) { // Cancel asap if (testRunner ! = null) { testRunner.cancel(); testRunner = null; } finish(); } public void onClickKillProcess(View view) { Process.killProcess(Process.myPid()); } public void onDestroy() { if (testRunner ! = null) { testRunner.cancel(); } controlBus.unregister(this); super.onDestroy(); }}Copy the code

The Class type of this Class is returned by a common method. The Class type of this Class is returned by a common method.

The code is very simple,

// Package name String getName = subscriberClass.getName(); String getSimpleName = subscriberClass.getSimpleName(); // Constructor<? > getCon = subscriberClass.getConstructor(); Constructor<? >[] getDeclCon = subscriberClass.getDeclaredConstructors(); / / Field Field [] the getFields = subscriberClass. The getFields (); Field[] getDeclFields = subscriberClass.getDeclaredFields(); / / Method [] getM = subscriberClass. GetMethods (); Method[] getDeclM = subscriberClass.getDeclaredMethods(); / / direct superclass type type getGenSuperClas = subscriberClass. GetGenericSuperclass (); // The interface implemented by the current Class<? >[] getInter = subscriberClass.getInterfaces(); . / / modifier int getMod = subscriberClass getModifiers ();Copy the code

The corresponding methods and results are:

You can click the image to enlarge and view the result. Through these methods, we can easily get the information of a class, such as fields, methods, etc., and then determine whether the method or field has added annotations, the idea is very clear.

GetDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX getDeclaredXXX

We can get a method based on its Class type, and then we can get the annotation and processing of the method.

Find the annotation information

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

/ / by reflecting private void findUsingReflectionInSingleClass (FindState FindState) {Method [] the methods; / / get the current class declares all methods of the methods. = findState clazz. GetDeclaredMethods (); For (Method Method: methods) {int modiFIERS = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) ! = 0 && (modifiers & MODIFIERS_IGNORE) == 0) {// Get method parameter type Class<? >[] parameterTypes = method.getParameterTypes(); If (parametertypes. length == 1) {Subscribe subscribeAnnotation = method.getannotation (Subscribe. if (subscribeAnnotation ! = null) {// Get the first argument Class<? > eventType = parameterTypes[0]; If (findState. CheckAdd (method, eventType)) {/ / annotation information ThreadMode ThreadMode = subscribeAnnotation. ThreadMode (); / / to the annotation information processing 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.

The processing logic is not at the heart of this article, but rather how runtime annotations retrieve annotation information through reflection.

conclusion

In this article, we will use the EventBus library to explain how runtime annotations work. The principle is very simple, mainly using reflection. Although the reflection API is not often used, it is easy to read and understand. In the next article, we will take a look at compile-time annotations using Android’s dynamic permissions library PermissionsDispatcher, including usage, generation files, etc. Don’t miss it, give it a like or follow.