如何做一个简单的Android注解框架(JBInject)

反射(Reflect)

程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性.

Person.java

public class Person {
    
    private String  name;
    private int     age;
                    
    private void intro() {
        System.out.println("我叫" + name + ",我今年" + age + "岁.");
    }
}

TestDemo.java


public class TestDemo {
    
    public static void main(String[] args) throws Exception {
        
        Person person = new Person();
        Class<?> cls = person.getClass();
        // 获取Person运行时的类
        
        Field nameField = cls.getDeclaredField("name");
        Field ageField = cls.getDeclaredField("age");
        // 获取name和age属性
        // cls.getDeclaredFields(); //获取所有属性
        
        nameField.setAccessible(true);
        ageField.setAccessible(true);
        // 打破name和age属性的封装性
        
        nameField.set(person, "小明");
        ageField.set(person, 19);
        // 为person对象对应的属性设置值
        
        Method method = cls.getDeclaredMethod("intro");
        // 获取intro方法
        // cls.getDeclaredMethods(); //获取所有方法
        
        method.setAccessible(true);
        // 打破intro方法的封装性
        
        method.invoke(person);
        //调用person的intro方法,
        // method.invoke(person, "arg1","arg2"...); 支持不定长参数作为方法的参数
    }
}

注解(Annotation)

定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

Person.java

public class Person {
    
    private String  name;
    private int     age;
                    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "我叫" + name + ",今年" + age + "岁";
    }
    
}

PersonInject.java


package snippet;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD) // 改注解只能注解属性
                            // @Target({ElementType.FIELD,ElementType.METHOD})可指定多种类型
/**
 * TYPE(类型) FIELD(属性) METHOD(方法) PARAMETER(参数) CONSTRUCTOR(构造函数)
 * LOCAL_VARIABLE(局部变量) ANNOTATION_TYPE,PACKAGE(包),
 */

@Retention(RetentionPolicy.RUNTIME)
/**
 * SOURCE:代表的是这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,Annotation的数据就会消失,
 * 并不会保留在编译好的.class文件里面。
 * 
 * ClASS:意思是这个Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,在执行的时候,
 * 并不会把这一些信息加载到虚拟机(JVM)中去.注意一下,当你没有设定一个Annotation类型的Retention值时,系统默认值是CLASS.
 * 
 * RUNTIME:表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的.
 */

public @interface PersonInject {
    String name();
    
    int age();
    // 上面两个可以类比函数的形参
}


TestDemo.java


public class TestDemo {
    
    @PersonInject(name = "小明", age = 19) // 仅仅是添加注解
    Person person;
    
    public static void main(String[] args) throws Exception {
        
        // 现在才开始真正注射
        TestDemo demo = new TestDemo();
        Class<?> cls = demo.getClass();
        
        Field declaredField = cls.getDeclaredField("person");
        // 请看反射那一块
        
        PersonInject personInject = declaredField.getAnnotation(PersonInject.class);
        String name = personInject.name();
        int age = personInject.age();
        // 获取person属性的注解和它的值
        
        Person person = new Person();
        person.setName(name);
        person.setAge(age);
        
        declaredField.setAccessible(true);
        declaredField.set(demo, person);
        // 设置值
        
        System.out.println(demo.person);
    }
    
}

开始编写小型注解框架

好啦,现在我们把前面的东西用到Android里面吧.

布局文件


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="这是原来的文字" />

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="改变文字" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="改变文字" />

    

</LinearLayout>

ViewInject注解Demo

ViewInject.java


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    int value();
    // 这里默认是value,如果是age的话注解是写(age=19),如果是value的话可以直接(19)
}

MainActivity.java


public class MainActivity extends Activity implements OnClickListener {
    
    @ViewInject(R.id.tv)
    private TextView    tv;
                        
    @ViewInject(R.id.btn)
    private Button      btn;
                        
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            Class<?> cls = MainActivity.class;
            Field[] declaredFields = cls.getDeclaredFields();
            for (Field field : declaredFields) {
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    field.setAccessible(true);
                    View view = findViewById(viewInject.value());
                    field.set(MainActivity.this, view);
                }
            }
            
            btn.setOnClickListener(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    @Override
    public void onClick(View v) {
        tv.setText("ViewInject注解成功!");
    }
    
}


OnClick注解Demo

OnClick.java


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int value();
}

MainActivity.java


package com.example.viewinjectdemo;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import viewinject.OnClick;
import viewinject.ViewInject;

public class MainActivity extends Activity implements OnClickListener {
    
    @ViewInject(R.id.tv)
    private TextView    tv;
                        
    @ViewInject(R.id.btn)
    private Button      btn;
                        
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            Class<?> cls = MainActivity.class;
            Field[] declaredFields = cls.getDeclaredFields();
            for (Field field : declaredFields) {
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    field.setAccessible(true);
                    View view = findViewById(viewInject.value());
                    field.set(MainActivity.this, view);
                }
            }
            
            btn.setOnClickListener(this);
            
            Method[] declaredMethods = cls.getDeclaredMethods();
            for (final Method method : declaredMethods) {
                OnClick onClick = method.getAnnotation(OnClick.class);
                if (onClick != null) {
                    method.setAccessible(true);
                    View view = MainActivity.this.findViewById(onClick.value());
                    view.setOnClickListener(new OnClickListener() {
                        
                        @Override
                        public void onClick(View v) {
                            try {
                                method.invoke(MainActivity.this);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    
                }
                
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    @Override
    public void onClick(View v) {
        tv.setText("ViewInject注解成功!");
    }
    
    @OnClick(R.id.btn1)
    public void clickBtn() {
        tv.setText("OnClick注解成功!");
    }
    
}

封装

从前面我们可以看得到,注射方法跟属性的代码都是固定的.
因此我们可以把代码封装成一个类JBInject,只需要Activity作为参数就可以了.

JBInject.java


public class JBInject {
    private static boolean flag;
    
    public static boolean inject(final Activity activity) {
        flag = true;
        try {
            Class<?> cls = activity.getClass();
            Field[] declaredFields = cls.getDeclaredFields();
            for (Field field : declaredFields) {
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    field.setAccessible(true);
                    View view = activity.findViewById(viewInject.value());
                    field.set(activity, view);
                }
            }
            
            Method[] declaredMethods = cls.getDeclaredMethods();
            for (final Method method : declaredMethods) {
                OnClick onClick = method.getAnnotation(OnClick.class);
                if (onClick != null) {
                    method.setAccessible(true);
                    View view = activity.findViewById(onClick.value());
                    view.setOnClickListener(new OnClickListener() {
                        
                        @Override
                        public void onClick(View v) {
                            try {
                                method.invoke(activity);
                            } catch (Exception e) {
                                e.printStackTrace();
                                flag = false;
                            }
                        }
                    });
                    
                }
                
            }
        } catch (Exception e) {
            e.printStackTrace();
            flag = false;
        }
        return flag;
    }
    
}

MainActivity.java



public class MainActivity extends Activity implements OnClickListener {
    
    @ViewInject(R.id.tv)
    private TextView    tv;
                        
    @ViewInject(R.id.btn)
    private Button      btn;
                        
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        JBInject.inject(MainActivity.this);
        
        btn.setOnClickListener(this);
        
    }
    
    @Override
    public void onClick(View v) {
        tv.setText("ViewInject注解成功!");
    }
    
    @OnClick(R.id.btn1)
    public void clickBtn() {
        tv.setText("OnClick注解成功!");
    }
    
}

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,124评论 25 707
  • 上篇我们讲述了,我们每个人都需要团队,那加入团队,我们都需要遵循什么原则呢?这篇我们来详解一下。主要通过团队管理的...
    思维浪人阅读 693评论 1 1
  • 从前有一个小女孩,她和父母去野外玩,小女孩看那些纷飞的枫叶,突出奇想:“我可以做一个枫叶书签”,于是小女孩找起...
    程旭阳阅读 282评论 1 2
  • 知识是良好的创造性思维的根本,但是死记硬背一些知识是远远不够的,这些知识必须被消化,而且最终以“鲜活的结合和崭新的...
    茗姐说阅读 369评论 0 0