书山有路勤为径,学海无涯苦做舟,苦海无涯,回头是岸
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 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布局文件:
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">
java反射:java 反射原理学习_weixin_43863193的博客-CSDN博客
发表评论