本文的注解主要基于运行时的反射机制
一.注解
1.关键字Annotation,新建一个注解类也很简单

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)//注解作用在类上面
//@Target(ElementType.FIELD)//注解作用在字段上面
//@Target(ElementType.METHOD)//注解作用在方法上面
//@Target(ElementType.ANNOTATION_TYPE)//注解作用在注解上面
@Retention(RetentionPolicy.SOURCE)//注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃,对应于:Java源文件(.java文件)
//@Retention(RetentionPolicy.CLASS)//注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期,对应于 .class文件
//@Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,对应于内存中的字节码
//生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用
//一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解
//如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
//如果只是做一些检查性的操作,比如@Override 和@SuppressWarnings,使用SOURCE 注解。
//注解@Override用在方法上,当我们想重写一个方法时,在方法上加@Override,当我们方法的名字出错时,编译器就会报错
//注解@Deprecated,用来表示某个类或属性或方法已经过时,不想别人再用时,在属性和方法上用@Deprecated修饰
//注解@SuppressWarnings用来压制程序中出来的警告,比如在没有用泛型或是方法已经过时的时候
public @interface TestAnnotation {
int value();//方法的返回值就对应于注解的类型
}
二.布局的注解
2.1.新建注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*/
@Target(ElementType.TYPE)//作用在类上面
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();//由于注解的是布局id,所以返回值是个int
}
2.2布局注解的使用
@ContentView(R.layout.activity_ioc)
public class IOCActivity extends AppCompatActivity {
@InjectView(R.id.tv)
TextView tv;
@InjectView(R.id.btn)
Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectManager.inject(this);
}
}
2.3布局注解的注入
public class InjectManager {
public static void inject(Activity activity) {
injectLayout(activity);
}
private static void injectLayout(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
int value = contentView.value();
//直接通过activity调用setcontentView()
activity.setContentView(value);
//通过反射调用
try {
// Method setContentView = clazz.getMethod("setContentView", int.class);
// setContentView.invoke(activity, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
三.属性的注解
3.1新建注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
3.2注解的使用
@ContentView(R.layout.activity_ioc)
public class IOCActivity extends AppCompatActivity {
@InjectView(R.id.tv)
TextView tv;
@InjectView(R.id.btn)
Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectManager.inject(this);
}
}
3.3属性注解的注入
private static void injectView(Activity activity) {
Class<? extends Activity> aClass = activity.getClass();
try {
Method findViewById = aClass.getMethod("findViewById", int.class);
//1.拿到所有属性
Field[] declaredFields = aClass.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
Field declaredField = declaredFields[i];
//2.拿到具有InjectView注解的属性
InjectView injectView = declaredField.getAnnotation(InjectView.class);
if (injectView != null) {
//3.拿到对应注解的值
int viewResId = injectView.value();
//4.将注解的值使用findViewById()的返回值设置给对应属性
Object invoke = findViewById.invoke(activity, viewResId);
declaredField.setAccessible(true);
declaredField.set(activity, invoke);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
四.单一事件的注入(以onClick为例)
4.1新建注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectClick {
int[] value();
}
4.2使用注解
@InjectClick(R.id.btn)
public void btnClick(View view) {
Toast.makeText(IOCActivity.this, "btn click", Toast.LENGTH_SHORT).show();
}
4.3单一事件注解的注入
/**
* zhuru zhuru
* 注入onClick事件
* 该方法的局限性在于,每次只能注入单一事件,比如,onClick 或者 onLongClick
*
* @param activity
*/
private static void injectOnClickEvent(final Activity activity) {
final Class<? extends Activity> aClass = activity.getClass();
try {
Method findViewById = aClass.getMethod("findViewById", int.class);
Method[] declaredMethods = aClass.getDeclaredMethods();
for (final Method method : declaredMethods) {
InjectClick injectClick = method.getAnnotation(InjectClick.class);
if (injectClick != null) {
int[] clickResId = injectClick.value();//获取到注解click的资源id
final Object invoke = findViewById.invoke(activity, clickResId[0]);
((View) invoke).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
method.invoke(activity);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
多事件的注解(以OnClick和OnLongClick为例)
1.新建注解
(此处注解会涉及到两种,一种是直接作用域控件上的,如InjectClick或InjectOnLongClick)
另一种是,作用在上面注解上面的注解,用来后面动态代理设置对应的属性
1.1作用在控件上的注解:(此处只给出了OnClick的注解,LongClick同理)
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)
@MultiEvents(listenerSetterMethod = "setOnClickListener", listenerField = View.OnClickListener.class, listenerCallBackMethod = "onClick")
public @interface InjectClick {
int[] value();
}
1.2作用在上面注解上的注解
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 MultiEvents {
//1.设置监听的方法(setOnClickListener)
String listenerSetterMethod();
//2.设置监听的对象(onClickListener)
Class<?> listenerField();
//3.执行方法的回掉(onClick)
String listenerCallBackMethod();
}
1.3注解的使用(同单一的事件注解)
@InjectClick(R.id.btn)//如需要,此处可更换为LongClick等其他事件
public void btnClick(View view) {
Toast.makeText(IOCActivity.this, "btn click", Toast.LENGTH_SHORT).show();
}
1.4注解的注入
此处涉及到了动态代理,以保证对注解事件的低耦合,也就是,注解事件可以自定义,以免局限于onClick或者onLongClick等
private static void injectMultiEvents(Activity activity) {
Class<? extends Activity> aClass = activity.getClass();
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//1.获取到带有OnClick/OnLongCLick注解的方法,以及对应的控件Id
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
MultiEvents multiEvents = annotationType.getAnnotation(MultiEvents.class);
if (multiEvents != null) {
//获取到MultiEvents对应的三个属性
String listenerCallBackMethod = multiEvents.listenerCallBackMethod();
Class<?> listenerField = multiEvents.listenerField();
String listenerSetterMethod = multiEvents.listenerSetterMethod();
// Log.e("test", "listenerCallBackMethod--->" + listenerCallBackMethod);
// Log.e("test", "listenerField--->" + listenerField);
// Log.e("test", "listenerSetterMethod--->" + listenerSetterMethod);
try {
// //获取到带有InjeckClick注解的所有控件
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
//// Log.e("test", "--------invoke---->" + viewIds.getClass().getSimpleName());
ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(activity);
invocationHandler.addMethod(listenerCallBackMethod, method);
Object proxy = Proxy.newProxyInstance(listenerField.getClassLoader(), new Class[]{listenerField}, invocationHandler);
for (int viewId : viewIds) {
View view = activity.findViewById(viewId);
if (view != null) {
//注意getMethod(获取当前类和父类的所有public方法)与getDeclareMethod(获取当前类的方法)的区别
Method setListenerMethod = view.getClass().getMethod(listenerSetterMethod, listenerField);
setListenerMethod.invoke(view, proxy);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
五.上面用到的代理
import android.util.Log;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* 将系统的方法回调,比如onClick更改为开发者自定义的事件名称,比如btnClick,tvClick等
* 使用时,将需要更改的方法增加到定义的map中,然后写invoke
*/
public class ListenerInvocationHandler implements InvocationHandler {
private Object target;
//key 为原方法名,如onClick, value 为新的方法 如btnClick等
private HashMap<String, Method> methodHashMap = new HashMap<>();
public ListenerInvocationHandler(Object object) {
this.target = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("test", "args--->" + args[0].getClass().getSimpleName());
if (target == null) {
return null;
}
String oldMethodName = method.getName();
if (methodHashMap.containsKey(oldMethodName)) {
method = methodHashMap.get(oldMethodName);
if (method != null) {
return method.invoke(target, args);
}
}
return null;
}
public void addMethod(String oldMethodName, Method newMethod) {
methodHashMap.put(oldMethodName, newMethod);
}
}
网友评论