安卓开发之,教你使用反射+注解

浅谈Java高级特性 · 注解

Java在网上都被玩烂了,在安卓开发中最蛋疼的事就是写一大堆代码,就只是为了拿到一个值(findViewByid() ,getIntent().getStringExtra(),setOnClickListener()诸如此类的)。为了解决这个问题,网上出现好多的注解框架。但是这些注解框架背后是什么实现原理呢?现在我们来看看是怎么实现的。

这里我们自己来写一个findViewByid的注解。

1.定义一个注解

New ->Annotation
新建注解

public @interface findViewByid
{
}
这样就定义出来一个注解了。但是注解我们在平时使用中发现有的注解是写在类名(class)上面的(为类注解),有的注解是写在方法Methed上面的(为方法注解),还有的则是写在变量Field上面的(属性注解)。
很明显,我们这里定义的findViewByid 是属于变量的注解。因为我们需要为我们定义的一个Button(或者其他的)自动绑定到Layout.xml中的定义的id的一个控件上、
然后不知道大家有没有发现,在使用Eclipse开发的时候经常看见这样的代码,比如我们在重写父类一个方法,Eclipse会自动在我们重写的方法上面添加这么一句、如下:
@Override
public String toString()
{
    // TODO Auto-generated method stub
    return super.toString();
}
大家应该注意到了这个 @Override 然后我们发现,我们把这个@Override 删掉也并不影响我们程序的使用。对,这就涉及到了一个作用域(这个词是我瞎编的)的问题,说白了就是这个注解在什么时候产生作用。那我们的这个情况肯定是在运行的时候需要使用。那么我们的代码可以这么写:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface findViewByid
{
}
@Retention(RetentionPolicy.RUNTIME) 这个是定义我们的注解是在什么作用域下起作用的。我们详细应该注意的是参数里的这个类 RetentionPolicy 让我们来看看源码:
/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
我们发现这个类只是一个枚举类,其中只写了三个值在里面。其实这就代表了三个作用域。

SOURCE代表的是这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,Annotation的数据就会消失,并不会保留在编译好的.class文件里面。

ClASS的意思是这个Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,在执行的时候,并不会把这一些信息加载到虚拟机(JVM)中去.注意一下,当你没有设定一个Annotation类型的Retention值时,系统默认值是CLASS.

RUNTIME,表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的.

@Target(ElementType.FIELD) 这个是定义我们的注解是为什么注解的。我们详细应该注意的是参数里的这个类 ElementType 让我们来看看源码:
/** @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 4.1 The Kinds of Types and Values
 */
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

TYPE,表示我们写的注解是为类class而设计的 然后我们这个注解就只能在类上使用。

FIELD,表示我们写的注解是为属性变量field而设计的 然后我们这个注解就只能在我们定义的属性变量上使用。

METHOD,表示我们写的注解是为方法method而设计的 然后我们这个注解就只能在我们定义的方法上使用。

PARAMETER,表示我们写的注解是为方法的参数param而设计的 然后我们这个注解就只能修饰方法内的参数。

这里讲的主要的就是这些。还有一些就自行百度。

到这里,我们的注解就可以在Field上使用了,就是我们定义的属性变量。如:
@findViewByid
Button button;
但是我们如何才能找到对应的Button呢?所以我们应该弄一个参数进去,把对应的id传进去。我们修改代码如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface findViewByid
{
    public int id();
}

使用方法:

@findViewByid(id=R.id.button1)
Button button;
但是这样的话我们还要多写个 ‘id=’了,能不能不写‘id=’呢?JavaAPI为我们提供了一个默认值:value 当参数名为value的时候,那么则可以省略参数名,这次我们修改如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface findViewByid
{
    public int value();
}

我们再试试:

@findViewByid(R.id.button1)
Button button;

OK到这一步我们前期工作就算完成了

2.使用注解

注解已经定义好了,那么怎么使用呢?这个时候就要利用Java另外一个特性:反射。
第一步:获取当前类的Class,
Class clazz = MainActivity.this.getClass();
第二步:获取当前类的所有的属性变量
java.lang.reflect.Field fields[] = clazz.getDeclaredFields();
第三步:找到被@findViewByid 注解过的变量。
for(Field field:fields)
{
    if(field.isAnnotationPresent(findViewByid.class))//判断此方法是否被findViewByid所注解
    {

    }
}
第四步:找到注解内参数的R.id值
findViewByid fivd = field.getAnnotation(findViewByid.class);//通过方法获取到注解对象
int id = fivd.value();//通过注解对象获取参数value值 也就是R.id.button1
第五步:通过id拿到对应控件Object对象
Object obj = this.findViewById(id);
第六步:把对象注入到对应的变量中。
/**
*第一个参数是哪个对象?因为可能别的activity中也有一个名字叫button的field所以确定是哪个对象,第二个则是为这个field设置什么值进去?
*/
field.set(MainActivity.this, obj);

注意:这一步可能会报错,导致注入失败,因为当我们定义的变量为私有变量Private时,这时候就因为权限原因导致注入失败!

@findViewByid(123)
private Button button;//设置为私有变量
为了解决这个问题,JavaAPI为我们提供了一个解决方案:
field.setAccessible(true);//设置变量为可访问的
修改后完整的代码如下:
Class clazz = MainActivity.this.getClass();
java.lang.reflect.Field fields[] = clazz.getDeclaredFields();
for(Field field:fields)
{
    if(field.isAnnotationPresent(findViewByid.class))//判断此方法是否被findViewByid所注解
    {
        findViewByid fivd = field.getAnnotation(findViewByid.class);//通过方法获取到注解对象
        int id = fivd.value();//通过注解对象获取参数value值 也就是R.id.button1
        Object obj = this.findViewById(id);
        field.setAccessible(true);//设置变量为可访问的
        field.set(MainActivity.this, obj);
    }
}

OK ,到此 我们的自定义注解从设计到功能实现都完成了。有一些地方需要优化,这里就等下次再说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值