【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(); } } } } }
上面代码可能大家唯一迷惑的就是动态代理那一块,请看我的另一篇博客:
- 【Android】注解机制详解
- java注解机制详解
- Android 最火框架XUtils之注解机制详解
- Android 最火框架XUtils之注解机制详解
- Android 最火框架XUtils之注解机制详解
- Android中最火框架XUtils之注解机制详解
- Android 最火框架XUtils之注解机制详解
- Android 最火框架XUtils之注解机制详解
- Android 最火框架XUtils之注解机制详解
- Android 最火框架XUtils之注解机制详解
- XUtils之注解机制详解
- XUtils之注解机制详解
- android&java注解详解
- Android-注解详解
- Android 自定义注解详解
- 详解Android注解 Annotation
- Android注解与反射机制
- Android androidannotations注解框架详解
- CRC校验详解及其在网络编程中的应用
- 新手学编程的常见困惑
- Linux 3.2中回写机制的变革
- Objective-c的内存管理MRC与ARC
- (4.1.23.12)自定义控件三部曲之动画篇(十)——联合动画的XML实现与使用示例
- 【Android】注解机制详解
- python基础31[常用模块介绍]
- Unity3D研究院之提取游戏资源的三个工具支持Unity5
- Python numpy函数hstack() vstack() stack() dstack() vsplit() concatenate()
- 树——recover-binary-search-tree
- 本地推送实例
- 跨服务器Session共享的四种方法
- 64. Minimum Path Sum
- [容器]STL之list容器详解