Android 注解和反射实践
上篇说了 Java 自定义注解,这里来看下自定义注解和反射结合的实践例子
这里我们需要做到两点:
- A页面
intent
传递数据并跳转到B 页面,通过注解和反射实现数据的自动赋值 - 通过注解和反射实现
Activity
页面的findViewById()
功能和onClick()
功能
数据自动赋值
首先来看下我们需要达到的效果:
public class SecondActivity extends AppCompatActivity {
@GetParam(value = "name")
private String name;
@GetParam(value = "age")
private int age;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
GetParamUtils.get(this);
// name = getIntent().getStringExtra("name");
// age = getIntent().getIntExtra("age", 0);
// 能正常打印出数据
Log.d("yao", "name=" + name + ", age=" + age);
}
}
从上面的结果可以看出实现这个效果需要有一个自定义注解@GetParam
,还需要有一个GetParamUtils
来解析注解的信息。
- 首先来看下
@GetParam
类中的实现:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetParam {
String value();
}
从上面可以看出注解GetParam
是作用在变量上的,并且保留界别是运行时。
-
在
SecondActivity
中,需要自动赋值的变量上面加上自定义的注解@GetParam
,并指定value的值,这个值就是intent
传递数据里面的key
。 -
解析成员变量上的注解,完成自动赋值
public class GetParamUtils {
public static void get(Activity activity) {
Intent intent = activity.getIntent();
Bundle extras = intent.getExtras();
if (extras == null) return;
Class<? extends Activity> aClass = activity.getClass();
// 获取所有的成员变量
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
// 判断是否带有指定的注解
if (field.isAnnotationPresent(GetParam.class)) {
GetParam annotation = field.getAnnotation(GetParam.class);
// 获取key
String key = annotation.value();
if (TextUtils.isEmpty(key)) {
key = field.getName();
}
if (extras.containsKey(key)) {
Object obj = extras.get(key);
// Parcelable数组类型不能直接设置,其他的都可以.
// 获得数组单个元素类型
Class<?> componentType = field.getType().getComponentType();
//当前属性是数组并且是 Parcelable(子类)数组
if (field.getType().isArray() && Parcelable.class.isAssignableFrom(componentType)) {
Object[] objects = (Object[]) obj;
//创建对应类型的数组并由objects拷贝
Object[] objects1 = Arrays.copyOf(objects, objects.length, (Class<? extends Object[]>) field.getType());
obj = objects1;
}
// 设置是否可以读写
field.setAccessible(true);
try {
field.set(activity, obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
- 在
onCreate
中直接绑定,然后打印日志看看结果。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
GetParamUtils.get(this);
// name = getIntent().getStringExtra("name");
// age = getIntent().getIntExtra("age", 0);
// 能正常打印出数据
Log.d("yao", "name=" + name + ", age=" + age);
}
>>>yao: name=yao, age=20
ok, 自动赋值就完成了,接下来看下控件的解析。
实现findViewById的控件注解
- 先写下自定义注解的类,由于
findViewById
最后需要赋值给变量,定义如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
@IdRes int value();
}
- 在需要使用的变量上引用
@InjectView(R.id.btn_jump)
private Button btnJump;
- 使用反射解析控件
public class InjectUtils {
public static void inject(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
// 获取当前类的所有变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 是否有injectView注解
if (field.isAnnotationPresent(InjectView.class)) {
InjectView annotation = field.getAnnotation(InjectView.class);
int resId = annotation.value();
try {
// 获取findViewById方法
Method findViewById = clazz.getMethod("findViewById", int.class);
// 执行findViewById方法
Object o = findViewById.invoke(activity, resId);
field.setAccessible(true);
// 赋值
field.set(activity, o);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
- 在
onCreate
中使用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtils.inject(this);
}
启动页面button
正常展示。
实现onClick方法的绑定
- 自定义注解
@OnClick
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventClickType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener")
public @interface OnClick {
@IdRes int[] value();
}
在这里有定义了一个注解@EventClickType
来注解OnClick
,具体实现如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventClickType {
/**
* 监听的class类型
* @return
*/
Class listenerType();
/**
* 设置监听的方法名
* @return
*/
String listenerSetter();
}
- 定义一个方法加上
@OnClick
注解
@OnClick({R.id.btn_jump})
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_jump:
jump();
break;
}
}
- 解析注解,并且会用到动态代理
// 设置点击监听
// 获取当前类的所有变量
Field[] fields = clazz.getDeclaredFields();
// 获取所有的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
// 获取方法上所有的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
// 是否有EventClickType 注解
if (annotationType.isAnnotationPresent(EventClickType.class)) {
EventClickType eventClickType = annotationType.getAnnotation(EventClickType.class);
Class listenerType = eventClickType.listenerType();
String listenerSetter = eventClickType.listenerSetter();
// 获取onclick注解上的value方法
try {
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
method.setAccessible(true);
EventInvocationHandler<Activity> invocationHandler = new EventInvocationHandler<>(activity, method);
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, invocationHandler);
// 遍历onclick注解上的value
for (int viewId : viewIds) {
View view = activity.findViewById(viewId);
// 获取setOnClickListener方法, listenerSetter是方法名, listenerType=View.OnclickListener
Method listenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
// 执行方法
listenerMethod.invoke(view, proxy);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
/**
* InvokationHandler是Java 反射包里面的一个接口。
* InvokationHandler通过用户类来实现,来激发一个动态代理类的方法
* @param <T>
*/
static class EventInvocationHandler<T> implements InvocationHandler {
private T target;
private Method method;
public EventInvocationHandler(T target, Method method) {
this.target = target;
this.method = method;
}
/**
*
* @param o 实现方法的代理对象
* @param method 代理实例激发的方法,Porxy参数中的接口方法
* @param objects 传递给方法的一系列参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return this.method.invoke(target, objects);
}
}
- 在
onCreate
方法中绑定即可
总结
自定义注解一般伴随着反射使用,在日常开发中多用多熟练会有更好的理解。