Android运行时注解
Android的注解有编译时注解和运行时注解,本文就介绍下运行时注解。
其实非常简单,直接上代码:本文主要是替代传统的findViewById()的功能,就是在我们Activity中不需要再使用findViewById()去给View赋值了,通过注解在运行阶段自动赋值。以及setOnClickListener()也是一样的原理。使用注解和反射技术。
1. 定义自己的annotation注解。
定义findViewbyId这个功能的注解
package com.xxx.xxx.xxx; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface InjectView { int value() default (int) -1; }
@Target类型如下几种:
public enum ElementType {
/**
* Class, interface or enum declaration.
*/
TYPE,
/**
* Field declaration.
*/
FIELD,
/**
* Method declaration.
*/
METHOD,
/**
* Parameter declaration.
*/
PARAMETER,
/**
* Constructor declaration.
*/
CONSTRUCTOR,
/**
* Local variable declaration.
*/
LOCAL_VARIABLE,
/**
* Annotation type declaration.
*/
ANNOTATION_TYPE,
/**
* Package declaration.
*/
PACKAGE
}
@Retation类型为:
public enum RetentionPolicy {
/**
* Annotation is only available in the source code.
*/
SOURCE,
/**
* Annotation is available in the source code and in the class file, but not
* at runtime. This is the default policy.
*/
CLASS,
/**
* Annotation is available in the source code, the class file and is
* available at runtime.
*/
RUNTIME
}
定义setOnclickListener的注解
package com.xxx.xxx.xxx; 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) public @interface InjectClick { int[] value(); }
2. 定义自己的注解处理类:
package com.xxx.xxx.xxx; import android.app.Activity; import android.view.View; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; public class RunTimeInjector { public static void injectView(Object obj, Object root) { Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Annotation[] annotations = field.getAnnotations(); if (annotations != null) { for (Annotation annotation : annotations) { if (annotation instanceof InjectView) { InjectView injectView = (InjectView) annotation; int value = injectView.value(); if (value != -1) { try { View view = getViewByRoot(root, value); field.set(obj, view); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } break; } } } } } public static void injectClick(Object obj, Object root) { Method[] methods = obj.getClass().getDeclaredMethods(); for (Method method : methods) { Annotation[] annotations = method.getAnnotations(); if (annotations != null) { for (Annotation annotation : annotations) { if (annotation instanceof InjectClick) { InjectClick inject = (InjectClick) annotation; int[] value = inject.value(); if (value != null && value.length > 0) { View.OnClickListener listener = (View.OnClickListener) obj; try { for (int res : value) { View view = getViewByRoot(root, res); if (view == null) { throw new NullPointerException(); } view.setOnClickListener(listener); } } catch (IllegalArgumentException e) { e.printStackTrace(); } } } else if (annotation instanceof InjectLongClick) { InjectLongClick inject = (InjectLongClick) annotation; int[] value = inject.value(); if (value != null && value.length > 0) { View.OnLongClickListener listener = (View.OnLongClickListener) obj; try { for (int res : value) { View view = getViewByRoot(root, res); if (view == null) { throw new NullPointerException(); } view.setOnLongClickListener(listener); } } catch (IllegalArgumentException e) { e.printStackTrace(); } } } } } } } public static View getViewByRoot(Object root, int res) { View view = null; if (root instanceof Activity) { view = ((Activity)root).findViewById(res); } return view; } }
3. Activity中使用注解:
@InjectView(R.id.action_back) private ImageView actionBack; @InjectView(R.id.site_top_bg) private ImageView mSiteTopBg; @InjectView(R.id.site_name) private TextView mSiteName; @InjectView(R.id.site_producter) private TextView mProducter; @InjectView(R.id.site_logo) private ImageRoundView mSiteLogo; @InjectView(R.id.site_description) private TextView mSiteDescription; @InjectView(R.id.viewPager) private ViewPager mViewPager;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_site);
RunTimeInjector.injectView(this, this);
RunTimeInjector.injectClick(this, this);
}
@Override @InjectClick({R.id.action_back}) public void onClick(View v) { int id = v.getId(); switch (id){ case R.id.action_back: SiteDetailActivity.this.finish(); break; } }
===============================
注解
如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。
注解处理器类库(java.lang.reflect.AnnotatedElement):
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
Class:类定义
Constructor:构造器定义
Field:累的成员变量定义
Method:类的方法定义
Package:类的包定义
java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
一个简单的注解处理器:
/***********注解声明***************/ /** * 水果名称注解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName { String value() default ""; } /** * 水果颜色注解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor { /** * 颜色枚举 * @author peida * */ public enum Color{ BULE,RED,GREEN}; /** * 颜色属性 * @return */ Color fruitColor() default Color.GREEN; } /** * 水果供应者注解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider { /** * 供应商编号 * @return */ public int id() default -1; /** * 供应商名称 * @return */ public String name() default ""; /** * 供应商地址 * @return */ public String address() default ""; } /***********注解使用***************/ public class Apple { @FruitName("Apple") private String appleName; @FruitColor(fruitColor=Color.RED) private String appleColor; @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦") private String appleProvider; public void setAppleColor(String appleColor) { this.appleColor = appleColor; } public String getAppleColor() { return appleColor; } public void setAppleName(String appleName) { this.appleName = appleName; } public String getAppleName() { return appleName; } public void setAppleProvider(String appleProvider) { this.appleProvider = appleProvider; } public String getAppleProvider() { return appleProvider; } public void displayName(){ System.out.println("水果的名字是:苹果"); } } /***********注解处理器***************/ public class FruitInfoUtil { public static void getFruitInfo(Class<?> clazz){ String strFruitName=" 水果名称:"; String strFruitColor=" 水果颜色:"; String strFruitProvicer="供应商信息:"; Field[] fields = clazz.getDeclaredFields(); for(Field field :fields){ if(field.isAnnotationPresent(FruitName.class)){ FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class); strFruitName=strFruitName+fruitName.value(); System.out.println(strFruitName); } else if(field.isAnnotationPresent(FruitColor.class)){ FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class); strFruitColor=strFruitColor+fruitColor.fruitColor().toString(); System.out.println(strFruitColor); } else if(field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class); strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address(); System.out.println(strFruitProvicer); } } } } /***********输出结果***************/ public class FruitRun { /** * @param args */ public static void main(String[] args) { FruitInfoUtil.getFruitInfo(Apple.class); } } ==================================== 水果名称:Apple 水果颜色:RED 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
Java注解的基础知识点(见下面导图)基本都过了一遍,下一篇我们通过设计一个基于注解的简单的ORM框架,来综合应用和进一步加深对注解的各个知识点的理解和运用。
一、引言
Android中通过findViewById在布局文件中找到需要的View,加入一个Activity里面有许多的View需要初始化,那将是一件很繁琐的事情。当然Google一下你会发现有很多Android Annotations框架。比如比较有名的“Android Annotations”,这样的框架很复杂,用起来也比较麻烦,还有一些BUG,第一次使用也花费了不少时间研究。也许你在项目中只希望用到 Inject View这个功能,又或者你想知道这个实现的原理是怎样的。本文主要是解决这两个问题,实现一个最简单的ViewInject.
二、原理
原理是在Activity加载好后通过找到Activity中使用注解的字段,再通过Java反射的方式,动态的给这个字段设置值。
1、首先你需要了解一下Java的注解是如何工作的,如果你不了解可以先看一下相关的资料,这个比较简答。首先定义我们的注解类:
- /**
- * view inect by id
- *
- * @author Lucky
- *
- */
- @Target(ElementType.FIELD)//表示用在字段上
- @Retention(RetentionPolicy.RUNTIME)//表示在生命周期是运行时
- public @interface ViewInject {
- int value() default 0;
- }
2、我们需要定义个BaseActivity,在这个类中来解析注解
- /**
- *
- * @author Lucky
- *
- */
- public abstract class BaseActivity extends FragmentActivity {
- /**
- * get content view layout id
- *
- * @return
- */
- public abstract int getLayoutId();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(getLayoutId());
- autoInjectAllField();
- }
- /**
- * 解析注解
- */
- public void autoInjectAllField() {
- try {
- Class<?> clazz = this.getClass();
- Field[] fields = clazz.getDeclaredFields();//获得Activity中声明的字段
- for (Field field : fields) {
- // 查看这个字段是否有我们自定义的注解类标志的
- if (field.isAnnotationPresent(ViewInject.class)) {
- ViewInject inject = field.getAnnotation(ViewInject.class);
- int id = inject.value();
- if (id > 0) {
- field.setAccessible(true);
- field.set(this, this.findViewById(id));//给我们要找的字段设置值
- }
- }
- }
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- }
- }
- }
3、完成上面的步骤后就是如何去使用了,示例代码如下:
- public class TestActivity extends BaseActivity {
- @ViewInject(R.id.claim_statement)
- private WebView mWebView;
- @Override
- public int getLayoutId() {
- // TODO Auto-generated method stub
- return R.layout.activity_claim;
- }
- }
注解反射只能提高写代码的效率,但是程序的执行效率确实相反的方向,不过影响不大。