说到java注解,相信大家在平时写代码的时候见过注解,但是真的很少有人用注解写过什么功能,包括我也是,所以今天就带着大家一起学习java中注解:
下面通过一个简单的注解介绍它有那些组成部分:
从
Override
注解发现,其中有@Target
注解和@Retention
注解修饰,其中@Target
表示当前的注解是修饰在哪的,也就是作用域,@Retention
表示当前的注解发生在什么时期,ElementType.METHOD
表示修饰在方法上面,RetentionPolicy.SOURCE
表示该注解发生在代码编写阶段。下面看看ElementType
和RetentionPolicy
还有其他的那些修饰:
public enum RetentionPolicy {
//该修饰表示注解只是在代码编写阶段起作用
SOURCE,
//在编译期起作用
CLASS,
//在运行期间起作用
RUNTIME
}
SOURCE:表示该注解在代码编写阶段起作用,如果在代码编写期间有代码规范问题或错误就会直接提示
CLASS:表示该注解在编译期起作用,编译期会帮我们生成代码,有名的ButterKnife
框架就是通过在编译期生成对应的类名_ViewBinding,大家可以看看apt生成的一些类。
RUNTIME:表示该注解在运行期起作用,也就是JVM的运行期。这个和上一节讲到的反射都是在运行期使用的。
public enum ElementType {
//一般作用在枚举、类、接口上
TYPE,
//作用在属性上,BindView注解就是形式这种
FIELD,
//作用在方法上
METHOD,
//作用在方法参数上
PARAMETER,
//作用在构造器上
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
//作用在注解上面,比如上面的@Retention、@Target都是修饰在注解上的
ANNOTATION_TYPE,
//作用在包上
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
常用的也就只有下面这几种:
TYPE 作用对象类/接口/枚举
FIELD 成员变量
METHOD 成员方法
PARAMETER 方法参数
ANNOTATION_TYPE 注解的注解
修饰变量注解@Target(ElementType.FIELD)
下面通过一个例子,将注解和反射一起结合使用,该注解实现的一个功能就是通过该注解能实现view点击后跳转到下一个activity的动作:
①定义注解
//定义该注解作用在什么时期,此处定义为jvm运行时期
@Retention(RetentionPolicy.RUNTIME)
//定义该注解作用在变量上
@Target(ElementType.FIELD)
public @interface IntentAnnotation {
//定义注解要传入的值,后面因为要用到该值,因此提前申明出来
Class<?> value();
}
②为了完成activity之间的跳转,申明了一个A_Activity
public class A_Activity extends AppCompatActivity {
//将注解作用在button上面,传入我们想要跳转的activity
@IntentAnnotation(B_Activity.class)
private Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
button = findViewById(R.id.button);
AnnotationUtils.inject(this);
}
}
此处还定义了一个AnnotationUtils
的工具类,其实主要作用在它身上,咱们看看如何去写:
public class AnnotationUtils {
private static final String TAG = "AnnotationUtils";
public static void inject(final Activity activity) {
Class<? extends Activity> aClass = activity.getClass();
//获取class文件所有的属性集合
Field[] fields = aClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
//判断当前属性是不是有想要的注解
if (field.isAnnotationPresent(IntentAnnotation.class)) {
final IntentAnnotation annotation = field.getAnnotation(IntentAnnotation.class);
Log.d(TAG, "field.getGenericType().toString():" + field.getGenericType().toString());
//判断下属性的类型
if (field.getGenericType().toString().startsWith(
"class android.widget.")) {
try {
//此处就是要干的事情
View view = (View) field.get(activity);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(activity, annotation.value());
activity.startActivity(intent);
}
});
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("the field must be a view");
}
}
}
}
}
所以说会使用注解,咱们自己也就可以封装很多不用重复写的代码,通过一行注解就能搞定。
修饰方法注解@Target(ElementType. METHOD)
下面通过一个简单的类似Eventbus的代码:
提供给方法的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface onEvent {
}
package com.single.layoutinflaterdemo.bus;
import android.os.Looper;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
public class EventBus {
static volatile EventBus sInstance;
Finder mFinder;
//CopyOnWriteArrayList一般用在读和写同时存在的情况下使用
//键存的是要提供注解的类的class对象,值存的要被调用的方法,类信息
public Map<Class<?>, CopyOnWriteArrayList<Subscriber>> mSubscriberMap;
//通过handler发送消息
PostHandler mPostHandler;
private EventBus() {
mFinder = new NameBasedFinder();
mSubscriberMap = new HashMap<>();
mPostHandler = new PostHandler(Looper.getMainLooper(), this);
}
/**
* 得到一个单例的上下文对象
*
* @return
*/
public static EventBus getDefault() {
if (sInstance == null) {
synchronized (EventBus.class) {
if (sInstance == null) {
sInstance = new EventBus();
}
}
}
return sInstance;
}
public void register(Object subscriber) {
/**
* 该处是关键,找到该类下面有onEvent注解的方法
*/
List<Method> methods = mFinder.findSubscriber(subscriber.getClass());
if (methods == null || methods.size() < 1) {
return;
}
CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.get(subscriber.getClass());
if (subscribers == null) {
subscribers = new CopyOnWriteArrayList<>();
/**
* 该map用来存储onevent开头的方法的第一个参数为key和
* subscribers的集合
*/
mSubscriberMap.put(subscriber.getClass(), subscribers);
}
for (Method method : methods) {
/**
* 封装了该类和该类带有onevent的方法
*/
Subscriber newSubscriber = new Subscriber(subscriber, method);
subscribers.add(newSubscriber);
}
}
public void unregister(Object subscriber) {
CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.remove(subscriber.getClass());
if (subscribers != null) {
for (Subscriber s : subscribers) {
s.mMethod = null;
s.mSubscriber = null;
}
}
}
public void post(Object event) {
mPostHandler.enqueue(event);
}
}
可以看到上面有List<Method> methods = mFinder.findSubscriber(subscriber.getClass());
,此处很关键,找onEvent注解的方法:
public class NameBasedFinder implements Finder {
/**
* @param subscriber
* @return
*/
@Override
public List<Method> findSubscriber(Class<?> subscriber) {
List<Method> methods = new ArrayList<>();
//先找到类下面的所有方法
for (Method method : subscriber.getDeclaredMethods()) {
method.setAccessible(true);
//这个就是找方法的注解关键
if (method.isAnnotationPresent(onEvent.class)) {
methods.add(method);
}
}
return methods;
}
}
在post时候,将要被加注解的类放到handler中:
public void post(Object event) {
mPostHandler.enqueue(event);
}
接着到PostHandler
如何去处理加注解的方法:
public class PostHandler extends Handler {
final EventBus mEventBus;
public PostHandler(Looper looper, EventBus EventBus) {
super(looper);
mEventBus = EventBus;
}
@Override
public void handleMessage(Message msg) {
CopyOnWriteArrayList<Subscriber> subscribers = mEventBus.mSubscriberMap.get(msg.obj.getClass());
for (Subscriber subscriber : subscribers) {
subscriber.mMethod.setAccessible(true);
try {
/**
* 第二个参数是方法的参数,第-个参数是该类的对象
*/
subscriber.mMethod.invoke(subscriber.mSubscriber);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void enqueue(Object event) {
Message message = obtainMessage();
message.obj = event;
sendMessage(message);
}
}
其实说到底还是通过反射去调用该方法,由于我这里没写方法的参数,所以invoke的时候是没带参数值的,大家可以根据eventBus源码自己动手写写。
关于更多的注解使用,还是希望大家多练,多看相关的源码才能熟练。更多注解学习请看这里
网友评论