运行时注解
@Retention(RetentionPolicy.RUNTIME) //runtime表示是运行时注解
@Target(ElementType.FIELD)//表示作用与变量
public @interface FindView11 {//注解的写法@interface 此时就可以使用@FindView11 作用与某个变量
int value();
}
简单实现一下用注解去代替findViewById()
1. ViewUtils
这里是解析注解和反射注入的工具类
尝试对activity或者view解析
public static void inject(Activity activity) {
inject(new ViewFinder(activity), activity);
}
public static void inject(View view) {
inject(new ViewFinder(view), view);
}
private static void inject(ViewFinder finder, Object o) {
injectField(finder, o);//解析并注入变量
}
// findViewById执行的地方
public class ViewFinder {
private View view;
private Activity activity;
public ViewFinder(View view) {
this.view = view;
}
public ViewFinder(Activity activity) {
this.activity = activity;
}
public View findViewById(int id) {
return activity == null ? view.findViewById(id) : activity.findViewById(id);
}
public View findViewById(int id, int pid) {
View pView = null;
if (pid > 0) {
pView = this.findViewById(pid);
}
View view = null;
if (pView != null) {
view = pView.findViewById(id);
} else {
view = this.findViewById(id);
}
return view;
}
}
2. 解析注入变量
private static void injectEvent(ViewFinder finder, Object o) {
//通过Object拿到class
Class<?> aClass = o.getClass();
//获取所有变量
Field[] fields = aClass.getDeclaredFields();
//循环遍历带有特定注解的变量
for (Field field : fields) {
//这是我们自定义的注解
FindView11 annotation = field.getAnnotation(FindView11.class);
if (annotation != null) {
//注解中的value应该是R.id.xxxx
int viewId = annotation.value();
//交给finder去findViewById找到对应View
View view = finder.findViewById(viewId);
if (view != null) {
//设置所有变量可达
field.setAccessible(true);
try {
//给变量设定值
field.set(o, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
//以上完成后就可以使用注解来findViewById了
具体使用如下
@FindView11(R.id.ttt)
TextView view1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
// view.setText("sdsd");
ViewUtils.inject(this);
view1.setText("sssssss");//这里就不会报空指针了
}
3.设置setOnClickListener的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//表示作用与方法
public @interface OnClick {
int[] value();//可以接收多个viewId
}
private static void injectEvent(ViewFinder finder, Object o) {
Class<?> aClass = o.getClass();
//获取所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//找到包含该注解的方法
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//拿到所有包含的viewId
int[] views = onClick.value();
if (views.length > 0) {
for (int viewId : views) {
View view = finder.findViewById(viewId);
if (view != null){
//这里可以直接监听然后反射执行
/**view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
method.setAccessible(true);
try {
method.invoke(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});*/
//也可以创建自己的监听然后反射执行
view.setOnClickListener(new onDeclaredClick(method,o));
}
}
}
}
}
}
private static class onDeclaredClick implements View.OnClickListener{
private Method method;
private Object o;
public onDeclaredClick(Method method, Object o) {
this.method = method;
this.o = o;
}
@Override
public void onClick(View v) {
try {
method.setAccessible(true);
method.invoke(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
下面是使用
@OnClick(R.id.ttt)
private void click() {//如果click带有参数,则反射的invoke也需要传入参数,通常和原来的保持一直
view1.setText("dddddd");
}
/**
保持一直的做法
@OnClick(R.id.ttt)
private void click(View view) {
view1.setText("dddddd");
}
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
method.setAccessible(true);
try {
method.invoke(o,v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});*/
4.
其实retrofit也是这样做的,不过它不需要区分什么运行时注解和编译时注解,它是直接通过动态代理去创建一个我们传入的class,然后利用java写好的方法反射执行,当然,它做了很多处理了。
取值 | 描述 | 作用范围 | 使用场景 |
---|---|---|---|
RetentionPolicy.SOURCE | 表示注解只保留在源文件,当java文件编译成class文件,就会消失 | 源文件 | 只是做一些检查性的操作,,比如 @Override 和 @SuppressWarnings |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 | class文件(默认) | 要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife) |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 | 运行时也存在 | 需要在运行时去动态获取注解信息 |