Android 注解原理学习及自定义注解

书山有路勤为径,学海无涯苦做舟,苦海无涯,回头是岸

android注解,是JDK5.0引入的一种注解机制,主要是用于减少一些繁琐的工作,比如:findViewById...。现在有许多框架帮我们实现了相关的注解,方便且实用。但闲暇之余还是可以了解一下其中的原理,知其然也知其所以然。

下面我们就以简单的例子,来简单的学习一下android注解:

一、android 注解介绍:

1.1、注解解释:

JDK5.0内置了3个标准注解,4个元注解

@Override:检查方法是否是重写方法

@Desprecated:标记元素被废弃,使用该注解的元素,编译器会发出警告。

@SuppressWarnings:指示编译器忽略警告信息。

@Target:标记注解可用在什么地方(元注解)

@Retention:标记注解可用在什么地方(元注解)

@Documented:标记注解是否出现在 Javadoc 中(元注解)

@Inherited:标记标注的Annotation是否具有继承性(元注解)

在JDK7.0之后,额外增加了3个注解:

@SafeVarargs:忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告

@FunctionalInterface:标识一个匿名函数或函数式接口

@Repeatable:标识某注解可以在同一个声明上使用多次

注解有多个,详细的这儿不做过多介绍,自定义注解时,主要用到@Target和@Retention两个元注解,下面介绍一下这两个注解

1.2、@Target使用介绍

@Target:标记用于什么地方,接收参数为枚举ElementType中的元素,下面来看一下ElementType这个枚举:

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
}

通过ElementType这个枚举,我们可以看出,@Target可以用于类、属性、方法、参数、构造方法等等地方,参数可以设置多个,例如:

@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})

表示这个注解可用于属性、类、方法上面。

1.3、@Target使用介绍使用

@Retention:标记注解的策略,接收参数为枚举RetentionPolicy中的元素,我们来看一下RetentionPolicy:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,//源文件,编译时丢弃,class字节码文件中不包含

    /**
     * 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,//默认策略,编译时保留,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//编译时保留,运行时保留,可通过反射获得
}

具体含义看代码注释,此处不再重复解释

二、自定义注解: 

2.1、定义注解

定义注解时,需要使用到@interface关键字,类似下面的定义;

@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Myannotation {
    int viewId() default 0;
    int layoutId() default 0;
    int onclick() default 0;
}

 此处定义了一个可用于属性、类方法的注解,注解接收三个int类型的参数,但都有默认值,默认值为0 ,所以使用的时候可以只传入其中的一部分。

2.2、使用注解

2.1中定义的注解名称为Myannotation,使用注解的方式为在需要注解的属性、方法、类前面使用@Myannotation(参数)进行使用,例如以下代码:

@Myannotation(layoutId = R.layout.activity_main)//注解类
public class MainActivity extends AppCompatActivity {
    private TXCloudVideoView mVideoViewAnchor;

    @Myannotation(viewId = R.id.button_1)//注解属性
    private Button button1;

    @Myannotation(onclick = R.id.button_1)//注解方法
    public void clickButton(View v){
        Log.d("test", "被点击到了");

    }
}

2.3、注解如何生效? 

注解不能凭空生效,需要我们去对他进行解释处理,例如以下代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnnotionUtils.injectActivity(this);//处理注解
//        TestAsyncTask task = new TestAsyncTask();
        Integer[] years = new Integer[3];
        button1.setText("click me,click me");
}

 利用 AnnotionUtils.injectActivity(this);这句代码对注解进行处理

接下来,我们从injectActivity(Activity activity)这个方法开始,看一下整个的处理流程:

public static void injectActivity(Activity activity){
    if(activity.getClass().isAnnotationPresent(Myannotation.class)){
        Myannotation myannotation =  activity.getClass().getAnnotation(Myannotation.class);
        activity.setContentView(myannotation.layoutId());
        injectClick(activity);//处理点击方法
        injectView(activity);//处理属性
    }

}

 代码比较简单,主要的流程是通过isAnnotationPresent这个方法,判断指定类型的注释是否存在于此元素上,如果存在,则返回TRUE。,然后通过getAnnotation(Class<A> annotationClass)方法,去获取到对应的注解对象,通过activity的setContentView绑定对应的布局文件layoutId.

接下来,我们再来看一下处理点击的方法,代码如下:

public static void injectClick(Activity activity){
    Class<?> activityClass = activity.getClass();
    Method[] methods = activityClass.getMethods();//获取方法

    for (int i = 0;i < methods.length;i++) {

        if (methods[i].isAnnotationPresent(Myannotation.class)){
            Myannotation myannotation = methods[i].getAnnotation(Myannotation.class);
            Log.d("test", "  |   " + myannotation.onclick());
            int finalI = i;
            activity.findViewById(myannotation.onclick()).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        methods[finalI].invoke(activity,v);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

}

 首先通过activityClass.getMethods()方法获取到class中所有的方法(注:此处涉及到反射的知识点,我们下期一起学习一下反射)

然后遍历所有的方法,通过isAnnotationPresent方法判断,方法中是否含有Myannotation注解,如果含有,则做以下两步操作:

1、对传入的控件id,设置OnClickListener监听器

2、在onclick方法中调用被注解的方法。这儿要使用到反射中的invoke方法,先在此处简单的介绍一下这个方法,方法生命如下:

第一个参数,是调用此方法的对象,比如我们此处是要调用activity中的clickButton方法,所以第一个参数传入activity对象,Object... args是一个可变参数,clickButton接收哪些参数,此处就传入哪些参数。

methods[finalI].invoke(activity,v);在示例中,这样就达到了在点击id为button_1的button时,调用了activity中的clickButton方法。

最后我们来看一下处理属性的方法,代码如下:

这儿流程比较简单,也涉及到了反射的相关知识,简单来说分为两步:

1、 调用activityClass.getDeclaredFields()获取所有属性

2、通过isAnnotationPresent判断属性是否含有Myannotation,如果有,调用属性的set方法对属性进行赋值。

这里流程比较简单,但有两处需要注意(注释中也说明了):

1、获取属性,注意getDeclaredFields和getFields的区别,getFields只能获取到public的属性

2、获取到的私有属性赋值前,一定不能忘记设置setAccessible为true,否则不能修改成功

完整代码这边如下:

Myannotation:
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Myannotation {
    int viewId() default 0;
    int layoutId() default 0;
    int onclick() default 0;
}

 MainActivity:

@Myannotation(layoutId = R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    private TXCloudVideoView mVideoViewAnchor;

    @Myannotation(viewId = R.id.button_1)
    private Button button1;

    @Myannotation(onclick = R.id.button_1)
    public void clickButton(View v){
        Log.d("test", "被点击到了");

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnnotionUtils.injectActivity(this);//处理注解
        Integer[] years = new Integer[3];
        button1.setText("click me,click me");
    }
}
AnnotionUtils:
public class AnnotionUtils {
    public static void injectActivity(Activity activity){
        if(activity.getClass().isAnnotationPresent(Myannotation.class)){
            Myannotation myannotation = activity.getClass().getAnnotation(Myannotation.class);
            activity.setContentView(myannotation.layoutId());
            injectClick(activity);//处理点击方法
            injectView(activity);//处理属性
        }

    }
    public static void injectView(Activity activity){

        Class<?> activityClass = activity.getClass();
        Field[] fields = activityClass.getDeclaredFields();//获取属性,注意getDeclaredFields和getFields的区别,getFields只能获取到public的属性
        for (int i = 0;i < fields.length;i++) {
            if (fields[i].isAnnotationPresent(Myannotation.class)){
                try {
                    Log.d("test", fields[i].getName() + "  |   " + fields[i].getAnnotation(Myannotation.class).viewId());
                    fields[i].setAccessible(true);//私有属性一定不能忘记设置access
                    fields[i].set(activity,activity.findViewById(fields[i].getAnnotation(Myannotation.class).viewId()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    public static void injectClick(Activity activity){
        Class<?> activityClass = activity.getClass();
        Method[] methods = activityClass.getMethods();//获取方法

        for (int i = 0;i < methods.length;i++) {

            if (methods[i].isAnnotationPresent(Myannotation.class)){
                Myannotation myannotation = methods[i].getAnnotation(Myannotation.class);
                Log.d("test", "  |   " + myannotation.onclick());
                int finalI = i;
                activity.findViewById(myannotation.onclick()).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            methods[finalI].invoke(activity,v);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }

    }
}

 xml布局文件:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.tencent.rtmp.ui.TXCloudVideoView
        android:id="@+id/video_view_anchor"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

java反射:java 反射原理学习_weixin_43863193的博客-CSDN博客 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值