Annotation + reflection + dynamic proxy to achieve View click event binding

1. Some thoughts

In fact, in the process of working, I always feel that my java foundation is still very weak, so I have to revisit the java foundation. In fact, annotations are very extensive in Android applications. It allows code introduction and decoupling, which improves a lot of development. effectiveness. In order to consolidate the understanding of the basic knowledge, so simply use annotation + reflection + dynamic proxy to realize the click event binding function of View, and deepen the impression. There are still a lot of knowledge points required in the entire implementation process. First, you must be familiar with the Android View click event. Of course, if you don’t understand the annotations, then you will not be able to understand the annotations. In the end, it is a dynamic proxy. Use it freely.

Second, the concrete realization

1. Define the annotation type of the View event. In Android, the click event of the View is divided into two types: click and long press. Defining the event type is also a good preparation for the follow-up work:


/**
 * Description:点击事件类型定义 事件分为长按和短按(即点击) 
 * ElementType.ANNOTATION_TYPE对注解进行注解<br>
 * Author:Frank<br>
 * Date:2020/5/6<br>
 * Version:V1.0.0<br>
 * Update: <br>
 */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
    Class listenerType();
    String listenerSetter();
}

2. Define the click event

/**
 * Description:短按事件类型(点击)<br>
 * Author:Frank<br>
 * Date:2020/5/6<br>
 * Version:V1.0.0<br>
 * Update: <br>
 */
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    @IdRes int[] myValue();
}

3. Define the long press event

/**
 * Description:长按事件类型<br>
 * Author:Frank<br>
 * Date:2020/5/6<br>
 * Version:V1.0.0<br>
 * Update: <br>
 */
@EventType(listenerType = View.OnLongClickListener.class, listenerSetter = "setOnLongClickListener")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
    @IdRes int[] myValue();
}

4. Get annotations, use reflection and dynamic proxy to complete event binding

/**
 * Description:使用反射和动态代理实现View点击事件绑定<br>
 * Author:Frank<br>
 * Date:2020/5/6<br>
 * Version:V1.0.0<br>
 * Update: <br>
 */
public class InjectViewClickUtil {
    private static final String TAG = InjectViewClickUtil.class.getSimpleName();

    public static void inject(Activity activity) {
        if (activity == null) {
            return;
        }
        //得到类对象
        Class<? extends Activity> clz = activity.getClass();
        //获取类对象中的所有方法
        Method[] methods = clz.getDeclaredMethods();
        //判断是否存在成员方法
        if (methods == null) {
            return;
        }
        //遍历方法
        for (Method m : methods) {
            //获取方法上的所有注解
            Annotation[] annotations = m.getAnnotations();
            //判断是否存在注解
            if (annotations == null) {
                continue;
            }
            //遍历所有注解
            for (Annotation a : annotations) {
                //得到注解类型对象
                Class<? extends Annotation> annotationType = a.annotationType();
                if (annotationType.isAnnotationPresent(EventType.class)) {
                    //得到具体注解对象
                    EventType eventType = annotationType.getAnnotation(EventType.class);
                    //取值
                    Class listenerType = eventType.listenerType();
                    Log.d(TAG, "inject: " + listenerType);
                    String listenerSetter = eventType.listenerSetter();
                    try {
                        //得到标记有OnClick 和 OnLongClick 注解的方法
                        Method valueMethod = annotationType.getDeclaredMethod("myValue");
                        //得到所有需要点击控件的id 也就是注解value
                        int[] ids = (int[]) valueMethod.invoke(a);
                        //设置权限
                        m.setAccessible(true);
                        //InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,
                        // 每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法
                        ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(m, activity);
                        //创建代理对象 最终会回调我们定义注解的方法
                        Object proxyInstance = Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{listenerType}, invocationHandler);
                        for (int id : ids) {
                            View view = activity.findViewById(id);
                            Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                            setter.invoke(view, proxyInstance);
                        }
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class ListenerInvocationHandler<T> implements InvocationHandler {
        private Method method;
        private T target;

        public ListenerInvocationHandler(Method method, T target) {
            this.target = target;
            this.method = method;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return this.method.invoke(target, args);
        }
    }
}

Three, how to use

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectViewClickUtil.inject(this);
    }

    @OnClick(myValue = {R.id.tv1, R.id.tv2})
    public void onClick(View v) {
        if (v.getId() == R.id.tv1) {
            Toast.makeText(this, ((TextView) v).getText(), Toast.LENGTH_SHORT).show();

        }
        if (v.getId() == R.id.tv2) {
            Toast.makeText(this, ((TextView) v).getText(), Toast.LENGTH_SHORT).show();
        }
    }

    @OnLongClick(myValue = {R.id.tv1, R.id.tv2})
    public boolean onLongClick(View view) {
        switch (view.getId()) {
            case R.id.tv1:
                Log.d(TAG, "onLongClick: 长安了textView1");
                break;
            case R.id.tv2:
                Log.d(TAG, "onLongClick: 长按了textView2");
                break;
        }
        return true;
    }
}

Note: The Android View long press event requires a return value, which means whether the current event is consumed or not, public boolean
onLongClick(View view) {return false}; Since this issue was not paid attention to during the implementation process, the last operation reported an error, and the check found me A void type method is defined, which results in an incorrect type when it is executed, and it crashes directly.

Four, summary

There are two important classes and interfaces InvocationHandler (interface) and Proxy (class) in java dynamic proxy mechanism. This class Proxy and interface InvocationHandler are the core of our dynamic proxy; if you don’t know much about dynamic proxy, you can take a look at this first. This article (https://blog.csdn.net/yaomingyang/article/details/80981004), because it is running to obtain attributes through reflection, so the annotation is selected at runtime, and the life cycle of the annotation can be selected according to the actual business .

Guess you like

Origin blog.csdn.net/zhuhuitao_struggle/article/details/105961331