学习笔记:通过反射实现类似ButterKnife功能(OnClick事件注入)

297 阅读3分钟

1.定义EventsBase注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.ANNOTATION_TYPE) //使用在注解上
@Retention(RetentionPolicy.RUNTIME)// 运行时
// 定义一个事件的基类注解,onClick onLongClick都可以基于此注解
public @interface EventsBase { 

    //方法名:setOnClickListener
    String listenerSetter();

    //监听的类型:View.OnClickListener.class
    Class<?> listenerType();

    // 回调方法名:onClick
    String listenerCallback();
    }

2.定义onClick注解

 import android.view.View;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
    
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 // 第一步定义的base注解
 // 名字千万不能打错,最好直接cv,因为是通过aop去切面拦截,名字错了就GG
 @EventsBase(listenerSetter = "setOnClickListener", // 需要设置的方法名
 listenerType = View.OnClickListener.class, // 具体监听的listener类型
 listenerCallback = "onClick") // 需要替换掉的回调名
 
 public @interface OnClick {
       int[] values(); // 因为有可能多个id,所以定义成数组
   }    

3.定义InvocationHandler实现类

动态代理传送门: blog.csdn.net/yaomingyang…

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;

public class ListenerInvocationHandler implements InvocationHandler {
    //需要拦截的目标 这里是LoginActivity
    private Object target;
    //缓存需要 String:替换的方法名字 Method:要替换的方法
    private HashMap<String, Method> map;
    
    //构造函数
    public ListenerInvocationHandler(Object target) {
        this.map = new HashMap<>();
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        try {
            if (target != null) {
                //获取到当前方法的名字,这里获取到的是onClick
                String methodName = method.getName();
                //通过这个名字去map里面取method
                //这里取出来的就是我们自定义的login()方法
                method = map.get(methodName);
                //判空
                if (method != null) {
                    //打开private权限
                    method.setAccessible(true);
                    //获取自定义的login方法有几个参数
                    int getParameterCount = method.getGenericParameterTypes().length;
                    //
                    if (getParameterCount > 0) {
                        //有参数返回args
                        return method.invoke(target, args);
                    } else {
                        //没参数不返回args
                        return method.invoke(target);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public void addMethod(String methodName, Method method) {
         //希望替换的方法名 methodName:onClick  
         //希望替换成那个方法 method:login()
        map.put(methodName, method);
    }
}       

4.在InjectManager中实现具体的逻辑

    private static void injectEvents(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        //获取class所有的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        //遍历所有的方法
        for (Method declaredMethod : declaredMethods) {
            //得到方法上面的注解集合
            Annotation[] annotations = declaredMethod.getAnnotations();
            //遍历注解集合
            for (Annotation annotation : annotations) {

                //获取到每个注解的类型 :得到OnClick注解
                Class<? extends Annotation> annotationType = annotation.annotationType();
                //再从这个注解上获取注解,得到EventsBase注解
                EventsBase eventsBase = annotationType.getAnnotation(EventsBase.class);
                //如果eventsBase不为空
                if (eventsBase != null) {
                    // 获取方法名 :setOnClickListener
                    String listenerSetter = eventsBase.listenerSetter();
                    // 获取监听类型:View.OnClickListener.class
                    Class<?> listenerType = eventsBase.listenerType();
                    // 获取回调的方法名 :onClick
                    String listenerCallBack = eventsBase.listenerCallback();
                    //实现方法替换的接口
                    ListenerInvocationHandler handle = new ListenerInvocationHandler(activity);
                    //添加要替换的方法  listenerCallBack:onClick是原本的回调方法名  declaredMethod:是在业务代码中自定义的方法login()
                    handle.addMethod(listenerCallBack, declaredMethod);
                    //设置代理监听
                    Object listenerProxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handle);
                    //
                    try {
                        //这个invoke里面的参数annotation就是onClick注解
                        int[] viewIds = (int[]) annotationType.getDeclaredMethod("values").invoke(annotation);
                        for (int viewId : viewIds) {
                            //找到findViewById方法 也可以直接用activity.findViewById(viewId);
                            Method findViewById = clazz.getMethod("findViewById", int.class);
                            //得到具体的View
                            Object view = findViewById.invoke(activity, viewId);
                            //这个方法就是setOnClick方法
                            Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                            //打开私有权限,这里不设置为True的话private修饰的方法将不能访问
                            setter.setAccessible(true);
                            //执行方法 相当于 view.setOnClickListener(listener)
                            //view参数是要监听的对象,就是这个方法要在那个目标执行
                            // listenerProxy是代理监听对象,在这个类里面会把原本的onClick()方法替换成login()
                            setter.invoke(view, listenerProxy);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        }

    }

5.onLongClick

新建一个@OnLongClick,将里面onClick对应的方法都换成onLongClick就行了

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventsBase(listenerSetter = "setOnLongClickListener",
        listenerType = View.OnLongClickListener.class,
        listenerCallback = "onClick")
public @interface onLongClick {
    int[] values();
}

6.使用和总结

在activity中调用一下inject

然后使用@OnClick注解

逻辑其实不复杂,多熟悉API就行了。