0%

注解反射-学习笔记


注解和反射是Android开发的基础,也是项目框架搭建中用到的必不可少的技术,减少重复代码编写,提高开发效率,并且广泛用于知名的开源框架中,有利于我们阅读源码,同时提升自己的架构能力和封装基础库的能力。下面对注解和反射的学习做一个记录。


原注解

元注解是定义注解的注解

@Retention:该注解保留阶段,保留的时长, 源码(RetentionPolicy.SOURCE) < 字节码(RetentionPolicy.CLASS) < 运行时(RetentionPolicy.RUNTIME)

  • 源码级别的注解:应用于APT编译期处理注解生成JAVA代码,生成额外的辅助类,如Dagger2, ButterKnife, EventBus3
  • 字节码级别的注解:应用于字节码插桩,可用于埋点,如ASM,AspectJ
  • 运行时级别的注解:反射获取被注解标记的变量/方法/类的信息

@Target:该注解被使用的位置,字段枚举常量级(ElementType.FIELD),局部变量级(ElementType.LOCAL_VARIABLE),方法级(ElementType.METHOD),方法级(ElementType.PARAMETER),类级接口级(ElementType.TYPE),包级(ElementType.PACKAGE),构造方法(ElementType.CONSTRUCTOR),注解级(ElementType.ANNOTATION_TYPE)

替代枚举

自定义注解并使用@IntDef注解指定一个数组,使用该注解去标记参数或返回值时,这个参数只能接收在@IntDef中所指定的几个参数,比如Android中设置view可见性的源码:

1
2
3
4
5
6
7
8
@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}


public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}

在setVisibility中传入这三个以外的其它值时,编译器就会提示错误

注入布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 自定义注解InjectLayout
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectLayout {
int value() default -1; // Activity布局文件
}
// 在BaseActivity的onCreate中调用
public static void injectLayout(Activity activity) {
Class<?> activityClass = activity.getClass();
if (activityClass.isAnnotationPresent(InjectLayout.class)) {
InjectLayout injectLayout = activityClass.getAnnotation(InjectLayout.class);
activity.setContentView(injectLayout.value());
}
}

@InjectLayout(R.layout.activity_main)
public class MainActivity extends BaseActivity {}

查找控件id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 自定义注解BindView
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD})
public @interface BindView {
int value() default -1; // View的id
}

// 在BaseActivity的onCreate中调用
private static void bindView(Activity activity) {
Class<?> activityClass = activity.getClass();
Field[] declaredFields = activityClass.getDeclaredFields();
for (Field field : declaredFields) {
if (field.isAnnotationPresent(BindView.class)) {
BindView bindView = field.getAnnotation(BindView.class);
try {
View view = activity.findViewById(bindView.value());
field.setAccessible(true);
field.set(activity, view);
} catch (IllegalAccessException e ) {
e.printStackTrace();
}
}
}
}

@BindView(R.id.tv_test)
Button mButton;

注解+反射实现Intent参数传递

定义注解

用反射获取该变量的信息需保留到运行时阶段且注解应用于类的字段变量之上

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoInject {
String key();
}

注入逻辑

  1. 获取到该Activity的class对象,获取到该Activity的Intent数据
  2. 没有任何传值时直接返回
  3. 如果有参数传递,获取到该Activity的所有字段变量
  4. 确定每个字段变量的传值key,遍历所有的字段变量,判断该字段变量是否被注解,如果被注解则获取到注解对象,判断注解上的参数传值是否为空,如果为空直接使用被注解的变量名称为key,不为空则使用注解上的参数传值为key
  5. 判断传递的参数中是否有该key的值,如果有获取传入的值,如果字段变量不为数组,这里传入的值为最终结果
  6. 获取被注解的变量类型,如果该变量是数组并且是序列化的类,强转对象数组,并复制一份新的对象数组为最终结果,修改Activity中该变量的访问权限,将结果赋值给该变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public static void injectBundle(Activity activity){
    // 获取class对象
    Class<? extends Activity> cls = activity.getClass();
    Intent intent = activity.getIntent();
    Bundle bundle = intent.getExtras();
    // 如果没有传值返回
    if (bundle == null){
    return;
    }
    // 获取所有的变量
    Field[] fields = cls.getDeclaredFields();
    // 遍历activity的变量
    for(Field field: fields){
    // 判断是否被注解
    if (field.isAnnotationPresent(AutoInject.class)){
    // 获取到注解对象
    AutoInject autoInject = field.getAnnotation(AutoInject.class);
    // 判断注解传值是否为空,如果为空使用当前被注解的变量名称
    String key = TextUtils.isEmpty(autoInject.key()) ? field.getName() : autoInject.key();
    // 如果有该key的传值
    if (bundle.containsKey(key)){
    // 获取传入的值
    Object object = bundle.get(key);
    // 获取被注解的变量类型
    Class<?> componentType = field.getType().getComponentType();
    // 如果当前变量是数组并且是序列化的class
    if (field.getType().isArray() && Parcelable.class.isAssignableFrom(componentType)){
    // 强转对象数组
    Object[] objs = (Object[])object;
    // 复制到新的对象数组
    Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
    object = objects;
    }
    // 修改该变量的访问权限
    field.setAccessible(true);
    try {
    // 设置当前activity该变量的值为传值对象
    field.set(activity,object);
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

    使用

  • 第一个Activity传递参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //User是序列化对象
    User user1 = new User("小明",12);
    User user2 = new User("小王",13);
    User[] users = new User[2];
    users[0] = user1;
    users[1] = user2;
    ArrayList<User> userList = new ArrayList<User>();
    userList.add(user1);
    userList.add(user2);
    // 传对象
    intent.putExtra("test1",user1);
    // 传对象数组
    intent.putExtra("test2",users);
    // 传对象列表
    intent.putParcelableArrayListExtra("test3",userList);
  • 第二个Activity声明接收变量添加注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 接收对象
    @AutoInject(key = "test1")
    private User value1;
    // 接收对象数组
    @AutoInject(key = "test2")
    private User[] value4;
    // 接收对象列表
    @AutoInject(key = "test3")
    private ArrayList<User> value5;
    // Activity创建时调注入逻辑
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    InjectUtils.injectBundle(this);
    }

注解+代理器实现View.OnClick注入逻辑

定义注解

定义注解的注解

声明监听器类型,注入的方法

1
2
3
4
5
6
7
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {

Class listenerType();
String listenerSetter();
}

定义方法注解

用反射获取该变量的信息需保留到运行时阶段且注解应用于方法之上

普通点击监听的类型为View.OnClickListener.class,作用的方法为setOnClickListener

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener")
public @interface OnClick {
int[] value();

}

长按监听的类型为View.OnLongClickListener.class,作用的方法为setOnLongClickListener

1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnLongClickListener.class, listenerSetter = "setOnLongClickListener")
public @interface OnLongClick {
int[] value();
}

注入逻辑

  1. 获取到该Activity的class对象,获取到当前Activity的所有方法
  2. 遍历所有方法,获取到方法的所有注解
  3. 遍历所有注解,获取到当前注解类型
  4. 如果是EventType目标注解,获取到注解对象,获取到注解上定义的传值,监听的Class类型,注解作用的方法
  5. 获取到方法上注解传入的id
  6. 修改方法的访问权限
  7. 利用Java的代理器生成代理对象,动态代理OnClickListener/OnLongClickListener接口
  8. 自定义InvocationHandler添加在对应的点击事件上注入的逻辑
  9. 获取到View对象,在对应的点击方法上注入代理对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    public class InjectClick {
    public static void injectEvent(Activity activity){
    // 获取到当前的activity的class对象
    Class<? extends Activity> activityClass = activity.getClass();
    // 获取到当前activity的所有方法
    Method[] methods = activityClass.getDeclaredMethods();
    // 遍历所有方法
    for (Method method: methods){
    // 获取到方法的所有注解
    Annotation[] annotations = method.getAnnotations();
    // 遍历所有注解
    for (Annotation annotation: annotations){
    // 获取到注解的类型
    Class<? extends Annotation> annotationType = annotation.annotationType();
    // 如果是EventType的注解
    if (annotationType.isAnnotationPresent(EventType.class)){
    // 获取到注解对象
    EventType eventType = annotationType.getAnnotation(EventType.class);
    // 获取到注解上定义的传值
    Class listenerType = eventType.listenerType();
    String listenerSetter = eventType.listenerSetter();
    try{
    // 获取到注解传入的id值
    Method valueMethod = annotationType.getDeclaredMethod("value");
    int[] viewIds = (int[]) valueMethod.invoke(annotation);
    method.setAccessible(true);
    // 自定义InvocationHandler实现注入逻辑
    ListenerInvocationHandler<Activity> handler = new ListenerInvocationHandler(activity, method);
    // OnClickListener/OnLongClickListener的代理对象
    Object listenerProxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
    new Class[]{listenerType}, handler);

    // 遍历传入的id
    for (int viewId : viewIds) {
    // 获得view
    View view = activity.findViewById(viewId);
    // 获得OnClickListener/OnLongClickListener的setOnClickLisnter/setOnLongClickLisnter方法
    Method setter = view.getClass().getMethod(listenerSetter, listenerType);
    // 在View的点击方法上注入代理对象
    setter.invoke(view, listenerProxy);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    }
    }


    /**
    * 兼容自定义view注入,所以是泛型: T = Activity/View
    *
    * @param <T>
    */
    static class ListenerInvocationHandler<T> implements InvocationHandler {

    private Method method;
    private T target;

    public ListenerInvocationHandler(T target, Method method) {
    this.target = target;
    this.method = method;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Log.v("injectClick","注入点击事件逻辑");
    return this.method.invoke(target, args);
    }
    }

使用

声明对应的点击回调,并添加注解,传入被注入点击事件View的Id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@OnClick({R.id.text, R.id.button})
public void click(View view) {
switch (view.getId()) {
case R.id.text:
Log.i("click", "click: 按钮1");
break;
case R.id.button:
Log.i("click", "click: 按钮2");
break;
}
}

@OnLongClick({R.id.text, R.id.button})
public boolean longClick(View view) {
switch (view.getId()) {
case R.id.text:
Log.i("click", "longClick: 按钮1");
break;
case R.id.button:
Log.i("click", "longClick: 按钮2");
break;
}
return false;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectClick.injectEvent(this);
}