开始这个例子之前我们先来回顾下注解
注解
从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为meta-annotation(元注解)。
声明注解的作用范围@Target
@Target也只能修饰一个注解定义,作用是指定被修饰的注解能用于修饰哪些程序单元,@Target也包含了一个value值,他的值只能是下面的:ElementType.
取值 | 注解使用范围 |
---|---|
METHOD | 可用于方法上 |
TYPE | 可用于类或者接口上 |
ANNOTATION_TYPE | 可用于注解类型上(被@interface修饰的类型) |
CONSTRUCTOR | 可用于构造方法上 |
FIELD | 可用于域上 |
LOCAL_VARIABLE | 可用于局部变量上 |
PACKAGE | 用于记录java文件的package信息 |
PARAMETER | 可用于参数上 |
声明注解的保留级别@Retention
->RetenionPolicy.SOURCE 该注解只保存在源代码中,编译器直接丢弃该注解
->RetenionPolicy.CLASS 编译器把该注解记录在class文件中。当运行java程序时,JVM不可获取注解信息。这是默认值!
->RetenionPolicy.RUNTIME编译器把该注解记录在class文件中。当运行java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息
根据注解的保留级别不同,对注解的使用自然存在不同场景。
保留级别 | 技术(使用场景) | 说明 |
---|---|---|
源码 | APT | 在编译期能够获取注解与注解声明的类包括类中所有成员信息, 一般用于生成额外的辅助类 |
字节码 | 字节码增强 | 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。 对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解 |
运行时 | 反射 | 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定 |
了解完注解的相关知识后,我们来看看这个例子,首先我们知道,在Android里面对View的事件绑定是需要根据控件的id来找到这个控件,然后调用View里面的setOnClickListener、setOnLongClickListener来完成绑定。如果用注解怎么来实现呢?首先根据需要我们可以整理出必须要的几个东西,控件的id,OnClickListener、OnLongClickListener的实现类,绑定点击、长按点击事件的方法名。
如何获取OnClickListener、OnLongClickListener实现类及对应的绑定方法,我们定义一个注解类,因为这个注解要作用于其它注解上,所以Target类型为ANNOTATION_TYPE,因为我们是在运行时解析,所以保留级别为RetentionPolicy.RUNTIME
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
Class listenerType();
String listenerSetter();
}
定义OnClick注解
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener")
public @interface OnClick {
int[] value();
}
定义OnLongClick注解
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnLongClickListener.class, listenerSetter = "setOnLongClickListener")
public @interface OnLongClick {
int[] value();
}
接下来我们来定义解析工具,用来获取类及绑定方法还有控件的id,因为这边需要处理OnClickListener及OnLongClickListener两种,而我们其实不需要关心具体是哪一种,所以我们可以用动态代理来创建对应的代理类。
import android.app.Activity;
import android.view.View;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class InjectUtil {
public static void bind(Activity activity) {
bindEvent(activity);
}
public static void bind(View view) {
bindEvent(view);
}
private static <T> void bindEvent(T t) {
Class cls = t.getClass();
//获取对应类下面的所有方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
//获取方法上的所有注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//获取注解类型
Class<? extends Annotation> annotationType = annotation.annotationType();
if (!annotationType.isAnnotationPresent(EventType.class))
continue;
EventType eventType = annotationType.getAnnotation(EventType.class);
if (eventType == null)
continue;
//获取需要代理的类
Class listenerType = eventType.listenerType();
//获取绑定方法名
String listenerMethod = eventType.listenerSetter();
try {
Method valueMethod = annotationType.getDeclaredMethod("value");
//通过反射我们不需要关心到底是OnClick还是OnLongClick 注解
int[] viewIds = (int[]) valueMethod.invoke(annotation);
method.setAccessible(true);
//动态创建对应类的代理对象
Object listenerProxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, new ListenerInvocationHandler<>(t, method));
if (viewIds != null) {
for (int viewId : viewIds) {
View view = null;
if (t instanceof Activity) {
view = ((Activity) t).findViewById(viewId);
} else if (t instanceof View) {
view = ((View) t).findViewById(viewId);
}
if (view == null)
continue;
//获取指定的方法(不需要判断是Click还是LongClick)
//如获得:setOnClickListener方法,参数为OnClickListener
//获得 setOnLongClickListener,则参数为OnLongClickListener
Method mViewMethod = view.getClass().getMethod(listenerMethod, listenerType);
//反射调用setter方法设置点击回调
mViewMethod.invoke(view, listenerProxy);
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
private static class ListenerInvocationHandler<T> implements InvocationHandler {
private T t;
private Method method;
public ListenerInvocationHandler(T t, Method method) {
this.t = t;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
return this.method.invoke(t, objects);
}
}
}
至此,我们的工具类编写完成。
测试使用
<?xml version="1.0" encoding="utf-8"?>
<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/one_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="One"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/two_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Two"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/one_bt" />
<Button
android:id="@+id/three_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Three"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/two_bt" />
<Button
android:id="@+id/four_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Four"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/three_bt" />
</androidx.constraintlayout.widget.ConstraintLayout>
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtil.bind(this);
}
@OnClick({R.id.one_bt, R.id.two_bt})
public void click(View view) {
switch (view.getId()) {
case R.id.one_bt:
Log.i("MainActivity", "One");
break;
case R.id.two_bt:
Log.i("MainActivity", "Two");
break;
}
}
@OnLongClick({R.id.three_bt, R.id.four_bt})
public boolean longClick(View view) {
switch (view.getId()) {
case R.id.three_bt:
Log.i("MainActivity", "three");
break;
case R.id.four_bt:
Log.i("MainActivity", "four");
break;
}
return true;
}
}