一、概述
自从Java1.5后,其提供了一个非常强大的功能那就是注解。在普通的开发中可能不会自定义注解,甚至有些时候一个app开发下来完全不需要注解。但是想要在技术方面做一个纵深,自己封装框架,成为高级工程师,专家或者架构师,注解这块的知识是绕不开的。所以学习一下java中的注解对自己的内功修炼是非常有必要的。
现在主流的开源框架大多数都会用到注解,不同的框架注解的参与程度也不一样。像:EventBus、ButterKnife、Retrofit2等框架其注解的参与程度是比较深的,如果想要在注解方面有更快的提升,建议各位看官去看这些框架的源代码。
本节会先讲一下java&android中常用的元注解。然后通过两个小例子来系统的说一下注解的具体用法:自定义ButterKnife和获取类似retrofit中interfaceservice中的方法信息,来加强各位看官在注解方面上的理解。
二、元注解
网上将Java元注解的文章非常的多,本节只将在作者看来最重要的两个,@target和@retention。其他的大家自行学习。本节的重点在于实践,其理论只是相对较少。
1.@Target,其定义了注解可以在什么地方使用:如:类、属性、方法、参数、构造方法、局部变量、包声明等。
@Target注解有以下几种值可以使用,当然,多种值可以组合使用
a.TYPE:类、接口、枚举声明
b.FIELD:在属性上声明
c.METHOD:在方法上声明
d.PARAMETER:参数参数声明
e.CONSTRUCTOR:构造方法声明
f.ANNOTATION_TYPE:注解类型声明
g.LOCAL_VARIABLE:局部变量声明
h.PACKAGE:包声明
2@Retention,其定义了注解在那些阶段是可以用的,如:源码阶段、Class字节码阶段、运行时阶段(虚拟机)
@Retention有三种阶段可以选择:
a.RetentionPolicy.SOURCE,注解只存在源码中
b.RetentionPolicy.CLASS,注解可以一直存活到字节码阶段
c.RetentionPolicy.RUNTIME,注解可以一直存活,即使程序已经运行起来了
三、创建一个超级简单的ButterKnife
本例我们将创建一个非常简单的ButterKnife。利用bind方法进行绑定view并给view赋值,而且还可以选择性的设置view的点击事件。
类介绍:
1.BindView.java绑定view的注解
2.OnClick.java绑定点击事件的注解
3.ButterKnife.java注解工具
4.BindViewTestActivity.java测试类
BindView:
package com.yw.annotationlib;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 模仿ButterKnife实现View的绑定
* create by yangwei
* on 2020-02-24 13:09
*/
@Target(ElementType.FIELD)//标记该注解运行在字段上
@Retention(RetentionPolicy.RUNTIME)//标记该字段一直到程序运行时都有效
public @interface BindView {
int value() default -1;//View的Id,如:R.id.btn
}
OnClick.java
package com.yw.annotationlib;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* create by yangwei
* on 2020-02-24 13:38
*/
@Target(ElementType.METHOD)//标记注解应用在方法上
@Retention(RetentionPolicy.RUNTIME)//标记注解直到运行时都可以存活
public @interface OnClick {
int value() default -1;//比较view的id值
}
ButterKnife.java
package com.yw.annotationlib;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import androidx.annotation.RequiresApi;
/**
* create by yangwei
* on 2020-02-24 13:12
*/
public class ButterKnife {
/**
* 绑定对象用注解标注的所有方法和属性
*
* @param activity
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public static void bind(final Activity activity) {
try {
Class clazz = activity.getClass();
//获取class中的所有的注解
Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
final OnClick onClick = method.getDeclaredAnnotation(OnClick.class);
if (onClick != null) {
final View view = activity.findViewById(onClick.value());
method.setAccessible(true);
//设置view的点击事件
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
method.invoke(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
BindView bindView = field.getDeclaredAnnotation(BindView.class);
if (bindView != null) {
View view = activity.findViewById(bindView.value());
field.setAccessible(true);
field.set(activity, view);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
BindViewTestActivity.java
package com.yw.rxjava3demo;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.yw.annotationlib.BindView;
import com.yw.annotationlib.ButterKnife;
import com.yw.annotationlib.OnClick;
import com.yw.annotationlib.retrofit.FieldMap;
import com.yw.annotationlib.retrofit.POST;
import com.yw.annotationlib.retrofit.RetrofitAnnotationBind;
import java.util.Arrays;
import java.util.Map;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
/**
* create by yangwei
* on 2020-02-24 13:21
*/
public class BindViewTestActivity extends Activity {
@BindView(R.id.tv_name)
TextView tv_name;
@BindView(R.id.btn_click)
Button btn_click;
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bindviewtest_layout);
ButterKnife.bind(this);
tv_name.setText("我是杨洛峋,是一个小宝宝");
//测试参数注解和方法注解
RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind(this);
Log.e("获取注解中的请求方式:", serviceMethod.getMethod());
Log.e("获取请求方法中的值:", serviceMethod.getParams());
}
@POST("POST")
public void retrofitTest(@FieldMap Map<String,String> map){
}
@OnClick(R.id.btn_click)
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_click:
Toast.makeText(BindViewTestActivity.this, "点击事件执行了", Toast.LENGTH_LONG).show();
break;
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是杨洛峋" />
<Button
android:id="@+id/btn_click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击我试试" />
</LinearLayout>
其最终的结果是TextView和Button按钮成功绑定上了findViewById。且Button按钮可以执行其注解标注的点击事件。
四、模仿retrofit获取注解方法参数以及方法注解参数上的值
本小节的主要内容是操作activty中的一个方法retrofitTest并的到这个方法注解的值和参数注解中的值并返回
FieldMap.java参数注解
POST.java方法注解
RetrofitAnnotationBind.java注解解析器
FieldMap.java
package com.yw.annotationlib.retrofit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 模仿Retrofit的FieldMap参数注解
* create by yangwei
* on 2020-02-24 14:03
*/
@Target(ElementType.PARAMETER)//标记注解附着到参数上
@Retention(RetentionPolicy.RUNTIME)//标记注解一直到运行时都有效果
public @interface FieldMap {
boolean encoded() default false;
}
POST.java
package com.yw.annotationlib.retrofit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* create by yangwei
* on 2020-02-24 14:01
*/
@Target(ElementType.METHOD)//在方法上运行
@Retention(RetentionPolicy.RUNTIME)//标记注解一直能存活到运行时
public @interface POST {
String value() default "post_hello";
}
RetrofitAnnotationBind.java
package com.yw.annotationlib.retrofit;
import android.os.Build;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.RequiresApi;
/**
* create by yangwei
* on 2020-02-24 14:06
*/
public class RetrofitAnnotationBind {
/**
* 拿到方法注解和参数注解
*
* @param obj
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public static ServiceMethod bind(Object obj) {
ServiceMethod serviceMethod = new ServiceMethod();
Class clazz = obj.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
POST post = method.getDeclaredAnnotation(POST.class);
//说明此方法上有POST注解,拿到post上的值
if (post != null) {
String psotValue = post.value();
serviceMethod.setMethod(psotValue);
Annotation[][] annotations = method.getParameterAnnotations();
if (annotations != null) {
StringBuffer sb = new StringBuffer();
for (Annotation[] paramsAnnotations : annotations) {
for (Annotation annotation : paramsAnnotations) {
if (annotation instanceof FieldMap) {
FieldMap fieldMap = (FieldMap) annotation;
sb.append(fieldMap.encoded()).append(",");
}
}
}
serviceMethod.setParams(sb.toString());
}
}
}
return serviceMethod;
}
public static class ServiceMethod {
private String method;
private String params;
public ServiceMethod() {
}
public ServiceMethod(String method, String params) {
this.method = method;
this.params = params;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
}
}
测试类中的测试方法,解析的就是这个方法
@POST("POST")
public void retrofitTest(@FieldMap Map<String,String> map){
}
本例的最终结果会的到一个ServiceMethod类。此类中存储了POST注解的值和FieldMap注解中的值。
其打印结果为POST,false
//测试参数注解和方法注解
RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind(this);
Log.e("获取注解中的请求方式:", serviceMethod.getMethod());
Log.e("获取请求方法中的值:", serviceMethod.getParams());
总结:本节的内容讲到这里就结束了,希望大家能够对注解的使用方式有一点自己的体会。如果想要更深入的了解注解,建议大家去看下相关框架的源代码。
ps:一旦你深入了,你会发现注解其实法力还是很大的。