阅读 142

Android IOC注解—事件三要素,打造通用事件注解工具类

image

好久没有写东西了一直在忙公司项目,最近在学习Android的面向切面和APT,学到了很多有意思的东西。ICO注入事件---打造兼容性事件监听的工具类,也是自己这两天学到的一种事件监听方式。(也就是类似Butterknife中@OnClick)做个笔记帮助自己总结一下学到的技能,也方便自己以后查阅。 Android事件、不管是点击事件OnClickListener、长按事件OnLongClickListener、按键OnKeyListener等事件、都有一些同性;所有的事件都是通过view.setOnxxxListener()进行设置,然后传入OnxxxListener事件接口,最后由系统调用接口中的onXX()方法接口触发该监听事件,如下 普通事件设置

Button btn_hw;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    btn_hw = findViewById(R.id.btn_hw);
    btn_hw.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {}
    });
}
复制代码

根据上面的分析,我们可以将事件划分三个重要点,也就是事件三要素:

  1. 事件调用的方法名xxx.setOnClickListener
  2. 事件的接口:View.OnClickListener
  3. 事件调用执行的方法:onClick (这个可能用不到)

通过上面的划分后,我们可以定义一个公共的事件注解类来存放这三个要素。这个公共事件注解并不是给调用类中的方法使用,它是专门给注解使用的因此我们需要将ElementType定义为注解类型@Target(ElementType.ANNOTATION_TYPE)。里面定义三个值:view设置事件的方法名、事件的接口Class对象,系统调用执行的事件回调方法。 公共事件注解

@Target(ElementType.ANNOTATION_TYPE)//在注解上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface OnBaseListener {
    //事件三要素---方法名 setOnClickListener
    String setListener();
    //事件三要素---事件接口 OnClickListener
    Class setListenerClass();
    //事件三要素---事件执行的方法 onClick
    String setCallbackMethod();
}
复制代码

定义完公共事件数组@OnBaseListener后,就可以在具体的事件监听注解中使用该注解了。文中只定义了两个事件,其他事件注解的定义类似** 点击事件

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnBaseListener(setListener = "setOnClickListener",
        setListenerClass = View.OnClickListener.class,
        setCallbackMethod = "onClick"
)
public @interface OnClick {
    int value();//组件id
}
复制代码

长按事件

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnBaseListener(setListener = "setOnLongClickListener",
        setListenerClass = View.OnLongClickListener.class,
        setCallbackMethod = "onLongClick")
public @interface OnLongClick {
    int value();//组件id
}
复制代码

事件定义完后,下面我们就要定义注解的解析工具类。该工具类主要通过反射来获取注解信息、组件id、view,设置事件监听,如下面定义的 InjectListener

public class InjectListener { 
    ...省略无关代码
    public void init(Object activityObj) {
            Class<?> activityClass = activityObj.getClass();
            for (Method declaredMethod : activityClass.getDeclaredMethods()) {
                declaredMethod.setAccessible(true);
                findOnListener(activityObj, activityClass, declaredMethod);
            }
        }
}
复制代码

我们先定义init(Object activityObj) 将Activity具体需要注解的类实例传过来,然后反射拿到该类中的所有方法。这里需要注意一点是 我们使用的是getDeclaredMethods() 方法,该方法返回的是activityObj对象中自己定义的方法,并不会返回父类中定义的方法。当然如果你使用 getMethod() 方法也是可以的。不过没有那个必要因为我的事件注解都是在xxxActivity中自定义的方法上使用的,所以只要获取本来方法就好不需要浪费时间遍历父类中的方法。 拿到遍历的方法后将遍历得到的Method传到自己写的findOnListener()方法中进行解析置事件监听。该方法大致流程是:

  1. 拿到activityObj对象中自己定义方法上的注解
  2. 解析每个注解、判断是否包含公共事件注解@OnBaseListener,有则取出该直接中的OnBaseListener注解
  3. 解析OnBaseListener注解中的值
  4. 获取自定义方法上Onxx注解中的id
  5. 反射执行activityObj的findViewById()方法获取到view控件
  6. 动态代理创建事件监听接口的实例
  7. 在代理类中的代理方法中反射执行**

事件注解处理工具类

public class InjectListener {
    private static String TAG = InjectListener.class.getSimpleName();
    private static InjectListener mInstance;
    public static InjectListener getInstance() {
        if (mInstance == null) {
            synchronized (InjectListener.class) {
                if (mInstance == null) {
                    mInstance = new InjectListener();
                }
            }
        }
        return mInstance;
    }
    public void init(Object activityObj) {
        Class<?> activityClass = activityObj.getClass();
        for (Method declaredMethod : activityClass.getDeclaredMethods()) {
            declaredMethod.setAccessible(true);
            findOnListener(activityObj, activityClass, declaredMethod);
        }
    }
    private void findOnListener(final Object activityObj, Class<?> activityClass, final Method listenerMethod) {
        //因为我们要拿的具体事件注解中的公共事件注解@OnBaseListener,一个类中可能有多个事件,事件有可能有不相同
        //因此我们需要通过getAnnotations()获取方法上的所有注解,然后通过annotationTyp来判断是否为@OnBaseListener注解过的
        //注解,如果是则取出其中的设置的值
        for (Annotation annotation : listenerMethod.getAnnotations()) {
            Class<? extends Annotation> annotationType = annotation.annotationType();
            OnBaseListener onBaseListener = annotationType.getAnnotation(OnBaseListener.class);
            if (onBaseListener == null) {
                continue;
            }
            //拿到事件三元素注解元素中的值
            String setListener = onBaseListener.setListener();
            Class setListenerClass = onBaseListener.setListenerClass();
            String setCallbackMethod = onBaseListener.setCallbackMethod();
            try {
                //因为要兼容其他事件方法,因此要通过反射动态获取事件监听注解中的方法
                Method valueMthod = annotationType.getDeclaredMethod("value");
                //获取view中的id
                int value = (int) valueMthod.invoke(annotation);
                //根据id获取到view getMethod()不仅可以获取自己的方法,还能获取到父类中的方法
                Method findViewById = activityClass.getMethod("findViewById", int.class);
                Object view = findViewById.invoke(activityObj, value);
                Log.d(TAG, ">>>>>>>>>>>" + value);
                //设置view的事件监听 必须要用于getMethod()来获取view设置事件监听的方法,因为我们不知道当前控件的事件是不是
                //它自己定义的还是父类View以及View的事件,getMethod()不仅会把自己类中的返回返回,还会将父类中以及父类中父类
                // 的方法返回
                Method setListenerMethod = view.getClass().getMethod(setListener, setListenerClass);
                //使用动态代理技术,自动生成事件监听接口的实例
                Object proxy = Proxy.newProxyInstance(setListenerClass.getClassLoader(), new Class[]{setListenerClass},
                        new InvocationHandler() {
                            /**
                             *  在执行接口中的方法的时候,会回调这个invoke方法,内执行接口中的一个方法,就会回调一次
                             * @param o 很少用
                             * @param method 要执行接口中的方法,如果接口中有多个方法,那么此次调用invoke(),method
                             *               指向的方法就不同
                             * @param objects 执行方法的参数
                             * @return 返回执行方法的结果
                             * @throws Throwable 异常
                             */
                            @Override
                            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                                //因为 监听函数只有一个方法,因此代理的invoke之会执行一次,我们不需要执行method方法
                                //因为我们要执行自己自定义的方法,下面的返回结果也就是我们自定义的返回结果
                                return listenerMethod.invoke(activityObj, null);
                            }
                        });
                //设置监听
                setListenerMethod.invoke(view, proxy);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

复制代码

在Activity中使用

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    InjectListener.getInstance().init(this);
}
//点击事件
@OnClick(R.id.btn_hw)
private void btnHW() {
    Toast.makeText(getBaseContext(), "我是OnClick注解绑定的事件哦!",
            Toast.LENGTH_SHORT).show();
}
//长按事件
@OnLongClick(R.id.btn_long)
private boolean btnLong() {
    Toast.makeText(getBaseContext(), "我是OnLongClick注解绑定的事件哦!",
            Toast.LENGTH_SHORT).show();
    return true;
}
复制代码

** 项目地址:Anno **

总结

上面的实现IOC注解绑定View的各种事件方式,只要是通过注解叫反射的方式实现。最后的动态代理最最重要的一点是用来创建事件接口的具体实现类,代理invoke()方法的返回函数就是Activity自动的事件监听函数的返回值,如果自定义的函数没有返回值那么就是null。