SoFunction
Updated on 2025-03-11

Android uses annotations and reflection to implement Butterknife function

Custom annotations

1) Define layout file injection first

//The scope of the annotation is on the class@Target()
//Let the retention policy be run-time, encode the annotation into the class file, and let the virtual machine read it@Retention()
public @interface ContentView {
    int value();// When using it, the layout file is specified directly by @ContentView(), which will automatically inject the layout file.}

2) Inject files into the control in the layout

@Target()
@Retention()
public @interface ViewInject {
    int value();
}

3) The click response of the control is injected into the file

@Target()
@Retention()
@interface OnClick {
    int[] value();
}

Use custom annotations

@ContentView(.activity_test)
public class TestActivity extends AppCompatActivity implements {
    @ViewInject(.edit_text)
    EditText mEditText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        (savedInstanceState);
        injectContentView(this);
        injectView(this);
        injectEvent(this);
    }
    @OnClick()
    @Override
    public void onClick(View v) {
        (,"Click Successfully"+().toString(),Toast.LENGTH_SHORT).show();
    }
}

Obtain annotation parameters through reflection mechanism

Introduction to Method-related methods:

(Object obj,Object args[]) is to call the method represented by the method class, where obj is the object name and args is the parameter passed to the method method

  • getMethods(): Methods to obtain public type of class
  • getMethod(String name, Class[] params): Get the specific method of the class, name parameter specifies the name of the method, params parameter specifies the parameter type of the method
  • getDeclaredMethods(): Get all methods in the class (public, protected, default, private)
  • getDeclaredMethod(String name, Class[] params): Get the specific method of the class, name parameter specifies the name of the method, params parameter specifies the parameter type of the method

1. Layout file acquisition

    public static void injectContentView(Activity activity){
        //Get the class instance of activity        Class<? extends Activity> clazz = ();
        //Get the ContentView annotation of the activity        ContentView contentView = ();
        if(contentView!=null){
            //If this annotation exists on the activity, take out the value corresponding to the annotation, which is the layout set before            int layoutId=();
            try {
                //Use reflection to call the setContentView method to complete the injection                Method setViewMethod = ("setContentView", );
                (activity,layoutId);
            } catch (Exception e) {
                ();
            }
        }
    }

2. Control acquisition implementation

    private static void injectView(Activity activity){
        //Get the class instance of activity        Class<? extends Activity> clazz = ();
        //Get all member variables of activity        Field[] fields = ();
        //Transfuse the member variables and get the ViewInject annotation on the member variables        for(Field field:fields){
            //Get the annotation object on the field, and if there is one, continue to the next field            ViewInject viewInject = ();
            if(viewInject!=null){
                //Get the id of the View of ViewInject annotation                int viewId = ();
                //Get the control                View view = (viewId);
                try {
                    //Set field to be accessible, even private ones can be accessed, which can improve efficiency                    (true);
                    //Set this control to the field object                    (activity,view);//Set the view control of the activity object to the property corresponding to the above example is mEditText=findViewById(viewId)                }catch (IllegalAccessException e){
                    ();
                }
            }
        }
    }

3. Control click response

    private static void injectEvent(final Activity activity) {
        Class<? extends Activity> clazz = ();
        //Get all methods (private methods can also be obtained)        Method[] methods = ();
        for (Method method : methods) {
            //Get the OnClick annotation above the method            OnClick click = ();
            //If you have, continue with the following code            if (click != null) {
                //Get data in the annotation. Since multiple buttons can be bound to click events, the defined annotation class uses the int[] data type                int[] viewId = ();
                // When launching is required, the method's setAccessible can be set to true to increase the reflection speed. The reason is that after setting to true, the access check is skipped, and it can be accessed even if private modified.                (true);
                //Set a proxy object. When the setOnClickListener is called, the proxy object is passed in. When the click occurs, the method will be invoke. You can call the method with the onClick annotation.               //At this time, the listener is the object that implements the interface of the actvivty and implements the onClick method in its interface, which is actually the returned invoke object.                Object listener = ((),
                        new Class[]{}, new InvocationHandler() {
                    //This invoke actually means calling. The first parameter is the object being proxied, the second parameter is the method being called, and the third parameter is the parameter array of the method being called.                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                (TAG, "method"+method);
                                (TAG, "args "+args);
                                //Execute the activity object, parameter args method, the activity object is the current activity, and has been implemented                                //In other words, if the control is clicked, the onClick method of the implemented Activity will be executed                                Object invoke = (activity, args);
                                return invoke;
                            }
                        });
                 //listener is an object of the OnClickListener class. Because the method passes the activity, it is the OnClickListener object of the current class.                // method is onClick method                try {
                    for (int id : viewId) {
                        //Get the corresponding control                        View v = (id);
                        //"setOnClickListener" is the method name and the type of method parameter                        Method setClickListener = ().getMethod("setOnClickListener", );
                        (TAG, "injectEvent: "+listener+"()"+v);
                        //The setOnClickListener method in the View class requires a parameter of the OnClickListener type, which is our proxy object                        //We need to set the corresponding control                        //At this time, when we click v (that is, the control we found), the onClick method of the proxy object will be called.  The onClick method of the proxy object is the onClick method that implements the interface in the Activity.  It is equivalent to executing the method setOnClickListener(listener).  And this listener is the object we have above proxy (the current activity implements the OnClicker interface).                        (v, listener);
                    }
                } catch (Exception e) {
                    ();
                }
            }
        }
    }

The above implementations are all injections through reflection at runtime, but it will have some performance losses, while Butterknife uses APT (Annotation Processing Tool) compile-time parsing technology. It already processes the annotations @Bind, @OnClick when the Java code is compiled into Java bytecode. Its principle is to read the Java source code, parse the annotations, generate new Java code, and the newly generated Java code is finally compiled into Java bytecode.

This is the article about Android using annotations and reflection to implement Butterknife functions. For more related Android Butterknife content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!