Android 注解和反射实践

Android 注解和反射实践


上篇说了 Java 自定义注解,这里来看下自定义注解和反射结合的实践例子

这里我们需要做到两点:

  1. A页面intent传递数据并跳转到B 页面,通过注解和反射实现数据的自动赋值
  2. 通过注解和反射实现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来解析注解的信息。

  1. 首先来看下@GetParam类中的实现:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetParam {
    String value();
}

从上面可以看出注解GetParam是作用在变量上的,并且保留界别是运行时。

  1. SecondActivity中,需要自动赋值的变量上面加上自定义的注解@GetParam,并指定value的值,这个值就是intent传递数据里面的key

  2. 解析成员变量上的注解,完成自动赋值

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();
                    }
                }
            }
        }
    }
}

  1. 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的控件注解
  1. 先写下自定义注解的类,由于findViewById最后需要赋值给变量,定义如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    @IdRes int value();
}
  1. 在需要使用的变量上引用
@InjectView(R.id.btn_jump)
private Button btnJump;
  1. 使用反射解析控件
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();
                }
            }
        }
    }
}
  1. onCreate中使用
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    InjectUtils.inject(this);
 }

启动页面button正常展示。

实现onClick方法的绑定
  1. 自定义注解@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();
}
  1. 定义一个方法加上@OnClick注解
@OnClick({R.id.btn_jump})
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.btn_jump:
            jump();
            break;
    }
}
  1. 解析注解,并且会用到动态代理
        // 设置点击监听
		// 获取当前类的所有变量
        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);
        }
    }
  1. onCreate方法中绑定即可
总结

自定义注解一般伴随着反射使用,在日常开发中多用多熟练会有更好的理解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值