【Android】注解机制详解

来源:互联网 发布:漫威宇宙 知乎 编辑:程序博客网 时间:2024/06/08 06:23

对于接触过一些android开发的人来说@Override一定会很常用,它表明接下来子类要复写父类的某个方法,但是你有了解过这个单词的实现原理吗?我们从这里入手来想借一下Android的注解机制。
首先贴一下@Override的源码:

/** * Annotation type used to mark methods that override a method declaration in a * superclass. Compilers produce an error if a method annotated with @Override * does not actually override a method in a superclass. * * @since 1.5 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}

上面的那段英文意思是:这是一个重写父类某个方法的标志,如果没有重写那么就会报错。
从这个例子可以看出,我们可以使用@interface来定义一个注解,主要有四个参数来修饰一个注解:
(1)@Documented:是否会保存在JavaDoc文件中
(2)@Retention(参数):表示注解的保留时间,有SOURCE(源码时),CLASS(编译时),RUNTIME(运行时)三个类型参数可供选择。
(3)@Target:表示注解修饰的类型,有METHOD,FIELD等。
(4)Inherited:表示是否可以被继承,默认为false。
现在我们可以尝试着自己来定义一个注解,比如我们想要定义一个引入布局的注解:
(1)新建一个setLayoutView.java

//既然要加载布局,那么肯定要保留到运行时@Retention(RetentionPolicy.RUNTIME)//布局文件由id唯一控制,所以是TYPE@Target(ElementType.TYPE)//加载布局文件的注解public @interface setLayoutView {    int value();}

有了上面的文件之后我们就可以用它来修饰了。例如:

@setLayoutView(R.layout.activity_main)public class MainActivity extends Activity {}

但是聪明的你肯定想到了,仅仅是这样是无法完成功能的,你定义的这个系统是无法识别的,所以我们需要手动的识别

    private static void injectContentView(Activity activity) {        //得到Activity编译时产生的类        Class<? extends Activity> clazz = activity.getClass();        //查询是否存在ContentView的注解(自己定义的ContentView)        setLayoutView layoutView = clazz.getAnnotation(setLayoutView.class);        if (layoutView != null) {            int contentViewLayoutId = layoutView.value();            try {                //Method顾名思义就是代表的一种方法,可以用方法名来取得                Method method = clazz.getMethod(METHOD_SET_CONTENTVIEW, int.class);                method.setAccessible(true);                //invoke表示执行这种方法                method.invoke(activity, contentViewLayoutId);            } catch (Exception e) {                e.printStackTrace();            }        }    }

上述代码就是一个简单的找到注解并执行的过程,注释也比较详细 了,里面用到了java反射机制,不懂的同学可以看我的另一篇博客:

其实注解只不过是方便了我们代码的编写,到最后调用的还是activity本身的方法。
只有一个布局文件肯定还是不够的,接下来我们迫切的需要一个注入控件的方法,接下来我们实现它!

@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)//加载控件的注解public @interface viewIn{    int value();}

具体的就不解释了,如果你看懂了上面的,这个定义就没有什么问题了。
接下来是重头戏,找到修饰的每一个view

    private static void injectViews(Activity activity){//得到Activity编译时产生的类      Class<? extends Activity> clazz = activity.getClass();//得到定义的所有字段      Field[] fields = clazz.getDeclaredFields();      for (Field field:fields){            viewIn viewInjectAnnotation = field.getAnnotation(viewIn.class);            if(viewInjectAnnotation != null){                int viewId = viewInjectAnnotation.value();                try{                    //同样的道理得到activity的设置变量的方法                    Method method = clazz.getMethod("findViewById",                            int.class);                    Object resView = method.invoke(activity, viewId);                    field.setAccessible(true);                    field.set(activity, resView);                }catch (Exception e)                {                    e.printStackTrace();                }            }        }    }

具体道理跟上面是一样的,这样的话我们就可以用注解写出一个界面了,但是只有界面显然无法满足我们的需求,我们来定义一个高级一些的注解:点击事件。

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@EventBase(listenerType = View.OnClickListener.class,        listenerSetter = "setOnClickListener",        methodName = "onClick")public @interface onClick {    int[] value();//考虑到许多个view的点击事件定义在一起    String[] resName();//每个事件的名字}
    private static void injectEvenet(Activity activity) {        Class<? extends Activity> clazz = activity.getClass();        Method[] methods = clazz.getMethods();        for (Method method : methods) {            //拿到某个方法上的全部注解            Annotation[] annotation = method.getAnnotations();            for (Annotation a : annotation) {                Class<? extends Annotation> annotationType = a.annotationType();                //查看一下是否有EventBase的注解                EventBase ebAnno = annotationType.getAnnotation(EventBase.class);                if (ebAnno != null) {                    Class<?> listenerType = ebAnno.listenerType();                    String listenerSetter = ebAnno.listenerSetter();                    String methodName = ebAnno.methodName();                    try {                        //拿到onClick注解中的value方法                        Method method1 = annotationType.getDeclaredMethod("value");                        //取出所有的viewId                        int viewIds[] = (int[]) method1.invoke(annotation, null);                        DynamicHandler handler = new DynamicHandler(activity);                        //把这个method先put进map里面                        handler.addMethod(methodName, method);                        //设置一个代理,执行listenerType的的时候就会去调用handler的invoke方法                        Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),                                new Class<?>[]{listenerType}, handler);                        for (int viewId : viewIds) {                            View view = activity.findViewById(viewId);                            Method setEvenetListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);                            setEvenetListenerMethod.invoke(view, listener);                        }                    } catch (Exception e) {                        e.printStackTrace();                    }                }            }        }    }

上面代码可能大家唯一迷惑的就是动态代理那一块,请看我的另一篇博客:

0 0
原创粉丝点击