Android自定义注解与反射

反射

  JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

1.获取Class对象

举个例子,我们要获取MainActivity的Class对象,有以下几种方式

1.Class<MainActivity> clazz = MainActivity.class;
2.Class<? extends MainActivity> clazz = this.getClass();
3.Class<?> clazz = Class.forName("utils.utilcode.blankj.com.dongtai.MainActivity");

2.通过Class对象获取构造方法,成员变量,方法等

构造函数

getConstructor();//获取本类或父类中public修饰的无参构造函数
getDeclaredConstructor();//获取本类无参构造函数
getDeclaredConstructor(Class<?>... parameterTypes): 获取本类中指定参数的构造器

成员变量

getFields(): 获取本类或父类中所有public属性
getField(String name): 获取本类或父类中特定名字的public属性
getDeclaredFields(): 获取本类中声明的所有属性

获取方法

getMethods(): 获取本类或父类中所有public方法(包括构造器方法)
getMethod(String name, Class<?>... parameterTypes): 获取本类或父类中特定名字和参数的public方法
getDeclaredMethods(): 获取本类中声明的所有方法(包括非public但不包括继承来的)
getDeclaredMethod(String name, Class<?>... parameterTypes): 获取本类中声明的特定名字和参数的方法(最常用)

获取注解

getAnnotation(Class<A> annotationClass): 获取这个元素上指定类型的注解(常用)
getDeclaredAnnotations(): 获取直接标注在这个元素上的注解

父类子类(接口)相关

getSuperclass(): 返回本类的父类
getGenericSuperclass(): 以Type的形式返回本类的父类, 带有范型信息
getInterfaces(): 返回本类直接实现的接口
getGenericInterfaces(): 以Type的形式返回本类直接实现的接口, 带有范型信息

自定义注解

新建一个InjectView类
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value();
}

Target表示作用范围,这里表示作用于成员变量

/** Targe表示作用范围
 * TYPE 作用对象类/接口/枚举
 * FIELD 成员变量
 * METHOD 成员方法
 * PARAMETER 方法参数
 * ANNOTATION_TYPE 注解的注解
 */

Retention指定了注解有效期直到运行时时期
value就是用来指定id,也就是findViewById的参数

在MainActivity中添加注解
    @InjectView(R.id.bind_view_btn)
    Button mBindView;
    @InjectView(R.id.textView)
    TextView textView;
在MainActivity的OnCreate方法中进行注入
  super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Utils.injectView(this);
新建Utils这个类,创建injectView方法

代码中的每一行都有详细的注释

    public static void injectView(Activity activity) {
        if (null == activity) return;
        //获取activity的.class对象
        Class<? extends Activity> activityClass = activity.getClass();
//        获取所有成员变量集合
        Field[] declaredFields = activityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //找到有@InjectView注解的成员变量
            if (field.isAnnotationPresent(InjectView.class)) {
                //得到注解类的对象
                InjectView annotation = field.getAnnotation(InjectView.class);
                //找到VIew的id
                int value = annotation.value();
                try {
                    //找到findViewById方法
                    Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                   //暴力访问,可获取私有方法
                    findViewByIdMethod.setAccessible(true);
                    View view = (View) findViewByIdMethod.invoke(activity, value);
                    //将结果赋值给成员变量
                    field.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

首先是获取activity的.class对象,通过Class对象找到该类中所有的成员变量。继而找到有InjectView注解的成员变量,然后得到注解类的对象,找到VIew的id,通过反射获取Activity的findViewById方法,然后将结果传给成员变量。

注解onClick点击事件

新建一个onClick类

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
public @interface onClick {
    int[] value();
}
   Target指定了onClick注解作用对象是成员方法
   Retention指定了onClick注解有效期直到运行时时期
   value就是用来指定id,也就是findViewById的参数
新建一个EventType类
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
    Class listenerType();
    String listenerSetter();
    String methodName();
}
Target指定了EventType注解作用对象是注解,也就是注解的注解
Retention指定了EventType注解有效期直到运行时时期
listenerType用来指定点击监听类型,比如OnClickListener
listenerSetter用来指定设置点击事件方法,比如setOnClickListener
methodName用来指定点击事件发生后会回调的方法,比如onClick

MainActivity中添加注解

    @onClick({R.id.button})
    public void onclick(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        switch (view.getId()) {
            case R.id.button:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }

记得注入onClick事件

  setContentView(R.layout.activity_main);
  Utils.injectView(this);
  Utils.injectEvent(this);

创建injectEvent方法

    public static void injectEvent(Activity activity) {
        if (null == activity) {
            return;
        }
        //获取activity的.class对象
        Class<? extends Activity> activityClass = activity.getClass();
        // 获取所有方法
        Method[] declaredMethods = activityClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
       //找到有@onClick注解的方法
            if (method.isAnnotationPresent(onClick.class)) {
                //得到注解类的对象
                onClick annotation = method.getAnnotation(onClick.class);
                //获取控件id的集合
                int[] value = annotation.value();
                //获取注解类中的注解
                EventType eventType = annotation.annotationType().getAnnotation(EventType.class);
               //得到注解类中的注解的属性
                Class listenerType = eventType.listenerType();
                String listenerSetter = eventType.listenerSetter();
                String methodName = eventType.methodName();
                //创建InvocationHandler和动态代理(代理要实现listenerType,这个例子就是处理onClick点击事件)
                ProxyHandler proxyHandler = new ProxyHandler(activity);
                //得到实现类的接口对象(1:实现类的类加载器2:实现类的接口数组3:proxyHandler对象(当执行接口的方法时会先执行里面的invoke,再执行实际方法,实现动态代理)
                Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, proxyHandler);
                // 将Activity中被注解的方法传至ProxyHandler 中
                proxyHandler.mapMethod(methodName, method);
                try {
                    for (int id : value) {
                        //找到Button
                        Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                        findViewByIdMethod.setAccessible(true);
                        View btn = (View) findViewByIdMethod.invoke(activity, id);
                        //根据listenerSetter方法名和listenerType方法参数找到method
                        Method listenerSetMethod = btn.getClass().getMethod(listenerSetter, listenerType);
                        listenerSetMethod.setAccessible(true);
                        //反射执行setOnclickListener
                        listenerSetMethod.invoke(btn, listener);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

ProxyHandler代码如下

public class ProxyHandler implements InvocationHandler {

    private WeakReference<Activity> mHandlerRef;

    private HashMap<String, Method> mMethodHashMap;

    public ProxyHandler(Activity activity) {
        mHandlerRef = new WeakReference<>(activity);
        mMethodHashMap = new HashMap<>();
    }

    public void mapMethod(String name, Method method) {
        mMethodHashMap.put(name, method);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Log.i("TAG", "method name = " + method.getName() + " and args = " + Arrays.toString(args));

        Object handler = mHandlerRef.get();

        if (null == handler) return null;

        String name = method.getName();

        //得到injectEvent中传递过来的被@onClick注解的方法
        Method realMethod = mMethodHashMap.get(name);
        if (null != realMethod){
       //反射执行Activity中onClick方法
            return realMethod.invoke(handler, args);
        }

        return null;
    }
}

重点是动态代理。这里简单分析一下ProxyHandler 类,它主要是用于动态代理。这里为什么要用动态代理呢!是因为当我们点击按钮时,会触发onClick方法,而我们需要在onClick里面执行我们注解的方法,所以必须对Activity动态代理。

Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, proxyHandler);

第一个参数为接口的加载器(本例中为Activity的接口View.OnclickListener),第二个参数为Activity的接口数组(View.OnclickListener),第三个参数为ProxyHandler 对象。返回一个实现类(View.OnclickListener)的对象。由于在injectEvent中我们反射执行了setOnClickListener,当用户触发点击事件时,会执行
接口中的方法(setOnClickListener的onClick方法)然后会执行ProxyHandler 类中的invoke方法,然后反射得到Activity中被@onClick注解的方法即onClick方法,然后反射调用它,执行注解中的方法。因此

    @onClick({R.id.button})
    public void onclick(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        switch (view.getId()) {
            case R.id.button:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }

不管方法名是什么,当用户点击后,都会反射执行对应方法体

注解类对象

这次我们通过注解得到Animal 的对象


public class Animal implements Fly, Run {

    public static final String TAG = "ProxyTest";

    public Animal() {
    }

    @Override
    public void fly() {
        System.out.println("Animal fly");
    }

    @Override
    public void run() {
        System.out.println("Animal run");

    }
}

同理新建一个自定义注解

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

在MainActivity中进行注解

   @Inject
    Animal mAnimal;

然后注入

  setContentView(R.layout.activity_main);
  Utils.injectView(this);
  Utils.injectEvent(this);
  InjectUtils.inject(this);

InjectUtil

public class InjectUtils {
    public static void inject(Activity activity) {
        if (null == activity) {
            return;
        }
        //获取activity的.class对象
        Class<? extends Activity> activityClass = activity.getClass();
        //        获取所有成员变量集合
        Field[] declaredFields = activityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //找到有Inject注解的成员变量
            if (field.isAnnotationPresent(Inject.class)) {
                //得到成员变量的类型的字节码文件
                Class<?> clazz = field.getType();
                Constructor con = null;
               try {
                    //得到无参构造
                   con = clazz.getConstructor();
                    //获取Animal其对象
                   Object instance = con.newInstance();
                    //赋值给field
                  field.set(activity,instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

本文中所有Demo的GitHub地址为https://github.com/zhonghongwen/Custom-Annotation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容