Android 注解

Android 注解

元注解

元注解是由 Java 提供的基础注解,负责注解其他注解,常用的元注解有如下:

  • @Retention:注解对象的生命周期
  • @Target:注解对象的作用范围
  • @Inherited:作用在其他注解上,表示其他注解能否被继承

@Retention

标明注解保留的生命周期,对应 RetentionPolicy 枚举类,共三种类型:

public enum RetentionPolicy {

    // 只在源码中有效,编译时抛弃,不记录在 .class 文件中
    SOURCE,

    // 只有编译 class 文件时生效,注解将记录在 .class 文件中,但运行时无法获得
    CLASS,

    // 运行时生效,注解将记录在 .class 文件中并且在运行时被 JVM 保留,可以通过反射读取它们
    RUNTIME
}

@Target

标明注解的作用范围,对应 ElementType 枚举类,共十种类型:

public enum ElementType {

    // 类,接口,注解类型,枚举
    TYPE,

    // 成员变量,枚举常量
    FIELD,

    // 方法
    METHOD,

    // 参数
    PARAMETER,

    // 构造函数
    CONSTRUCTOR,

    // 局部变量
    LOCAL_VARIABLE,

    // 注解
    ANNOTATION_TYPE,

    // 包
    PACKAGE,

    // 类型参数
    TYPE_PARAMETER,

    // 类型使用声明
    TYPE_USE
}

@Inherited

注解所作用的类,在继承时默认无法继承父类的注解,除非注解声明了 @Inherited ,@Inherited 只对类有效,对方法/属性无效。

运行时注解具体案例

接下来,我们用注解简单实现一下 Activity 布局、控件和事件注入。首先定义注解类。

定义布局注解类,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {

    // 获取 int 值(这里是布局资源ID)
    int value() default -1;
}

上述注解类 ContentView 作用在类上,在运行时生效,使用时需要传入 Int 值。

定义控件注解类,如下:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {

    // 获取 int 值(这里是控件资源ID)
    int value() default  -1;
}

上述注解类 InjectView 作用在属性上,在运行时生效,使用时需要传入 Int 值。

定义事件注解基类,如下:

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseEvent {

    String listenerSetter();

    Class<?> listenerType();

    String callBackListener();
}

上述注解类 BaseEvent 作用在其他注解上,在运行时生效,使用时需要传入 String 、 Class<?> 、String 值。

定义事件注解类,如下:

@BaseEvent(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {

    // 获取 int 数组(这里是控件资源ID)
    int[] value();
}

上述注解类 OnClick 作用在方法上,在运行时生效,使用时需要传入 Int 数组。

定义注解管理类,如下:

public class InjectManager {
    public static void inject(Activity activity) {

        // 布局注入
        injectLayout(activity);

        // 控件的注入
        injectViews(activity);

        // 事件的注入
        injectEvents(activity);

    }
}

上述 InjectManager 类定义了一个静态的 inject 方法用于注解绑定,在 Activity 上调用该方法后,上述所定义的注解将会生效,具体的注入逻辑先跳过,接下来先看一下使用。

注解在 Activity 的使用

首先,在 Activity 的 onCreate()方法中调用 InjectManager.inject(this)。接着,在对应的位置上加上注解即可,如下:

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.tv_des)
    private TextView mDes;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 进行布局、控件、事件的注入
        InjectManager.inject(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        //        Toast.makeText(this, mDes.getText().toString(), Toast.LENGTH_SHORT).show();
    }

    @OnClick({R.id.btn_ioc, R.id.tv_des})
    public void onCLick(View view) {
        switch (view.getId()) {
            case R.id.tv_des:
                Toast.makeText(this, "tv_des", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_ioc:
                Toast.makeText(this, "btn_ioc", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

注解的注入逻辑

布局注入

private static void injectLayout(Activity activity) {
    Class<? extends Activity> clazz = activity.getClass();
    // 先判断注解类是否存在
    if (clazz.isAnnotationPresent(ContentView.class)) {
        // 获取指定的注解类
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        // 获取注解中传入的值
        int layoutId = contentView.value();
        // 方式一
        activity.setContentView(layoutId);

        // 方式二
        try {
            // 反射获取 setContentView 方法,这里不能用 getDeclaredMethod,因为 setContentView 方法是父类中的公共方法
            Method method = clazz.getMethod("setContentView", int.class);
            // 反射调用 setContentView 方法
            method.invoke(activity, layoutId);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

控件注入

private static void injectViews(Activity activity) {
    Class<? extends Activity> clazz = activity.getClass();
    // 拿到本类的所有属性,无论 public 、protected 、private
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        // 拿到属性上指定的注解
        InjectView injectView = field.getAnnotation(InjectView.class);
        // 注解不存在,继承遍历
        if (injectView == null) {
            continue;
        }
        try {
            // 反射获取 findViewById 方法,这里不能用 getDeclaredMethod,因为 findViewById 方法是父类中的公共方法
            Method method = clazz.getMethod("findViewById", int.class);
            // 允许修改反射属性,即改变权限(因为属性可能是 private 的)
            field.setAccessible(true);
            // 给属性赋值
            field.set(activity, method.invoke(activity, injectView.value()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

事件注入

private static void injectEvents(Activity activity) {
    Class<? extends Activity> clazz = activity.getClass();
    // 获取本类的所有方法
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        // 获取每个方法的所有注解
        Annotation[] annotations = method.getAnnotations();
        // 遍历注解
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> type = annotation.annotationType();
            if (type != null) {
                // 这里不能用 getDeclaredAnnotation ,因为要获取父注解 BaseEvent
                BaseEvent baseEvent = type.getAnnotation(BaseEvent.class);
                if (baseEvent != null) {
                    String listenerSetter = baseEvent.listenerSetter();
                    Class<?> listenerType = baseEvent.listenerType();
                    String callBackListener = baseEvent.callBackListener();

                    try {
                        // 找到注解对应的 value 方法
                        Method valueMethod = type.getDeclaredMethod("value");
                        // 执行 注解的 value 方法,获取注解的值
                        int[] viewIds = (int[]) valueMethod.invoke(annotation);

                        ListenerInvocationHandler handler = new ListenerInvocationHandler(activity);
                        // 拦截 callBackListener 方法,调用开发者自定义的 method 方法
                        handler.addMethod(callBackListener,method);
                        // 动态代理对应接口类
                        Object listener = Proxy.newProxyInstance(activity.getClassLoader(), new Class[]{listenerType}, handler);

                        for (int viewId : viewIds) {
                            // 获取当前的控件
                            View view = activity.findViewById(viewId);
                            // 获取指定的方法,比如(setXXXListener)
                            Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                            // 执行 View 的相应方法
                            setter.invoke(view, listener);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

public class ListenerInvocationHandler implements InvocationHandler {

    private final static long QUICK_EVENT_TIME_SPAN = 300;

    private long lastClickTime;

    private Object target ; // 需要拦截的对象,比如 Activity / Fragment
    private HashMap<String,Method> mMap ;

    public ListenerInvocationHandler(Object target) {
        this.target = target;
        mMap = new HashMap<>();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target != null) {
            // 获取需要拦截的方法名
            String methodName = method.getName();
            // 用传进来的方法来替换该方法
            method = mMap.get(methodName);

            long timeSpan = System.currentTimeMillis() - lastClickTime;
            if (timeSpan < QUICK_EVENT_TIME_SPAN) {
                Log.d("test", "阻尼事件");
                return null;
            }
            lastClickTime = System.currentTimeMillis();
            // 如果有开发者自定义的方法
            if (method != null) {
                // 判断参数的长度
                if (method.getGenericParameterTypes().length == 0) {
                   return method.invoke(target);
                }
                // 调用 Activity / Fragment 里的自定义方法,方法参数一样(偷天换日概念)
                return method.invoke(target, args);
            }
        }
        return null;
    }


    /**
     * 添加拦截方法
     */
     public void addMethod(String methodName, Method method) {
         mMap.put(methodName, method);
    }

}

事件注入比较复杂一些,这里简单介绍一下处理步骤:

  1. 获取本类的所有方法并获取每个方法的所有注解。
  2. 获取我们自定义的 BaseEvent 注解和传入的所有参数。
  3. 根据参数,利用动态代理技术,构建出相应的实现类。
  4. 利用反射技术,获取控件ID,并进一步获取控件,最后调用控件的注解控件方法。
  5. 在动态代理的 InvocationHandler 类中替换之前的方法,通过反射调用用户自定义的方法。