好久没有写东西了一直在忙公司项目,最近在学习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) {}
});
}
根据上面的分析,我们可以将事件划分三个重要点,也就是事件三要素:
- 事件调用的方法名xxx.setOnClickListener
- 事件的接口:View.OnClickListener
- 事件调用执行的方法: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()方法中进行解析置事件监听。该方法大致流程是:
- 拿到activityObj对象中自己定义方法上的注解
- 解析每个注解、判断是否包含公共事件注解@OnBaseListener,有则取出该直接中的OnBaseListener注解
- 解析OnBaseListener注解中的值
- 获取自定义方法上Onxx注解中的id
- 反射执行activityObj的findViewById()方法获取到view控件
- 动态代理创建事件监听接口的实例
- 在代理类中的代理方法中反射执行**
事件注解处理工具类
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。