经过自定义Annotation简单实现ButterKnife注解功能

前言

经过对《Java Annotation简介》的简单介绍,咱们应该了解到了Java 中自定义注解的使用方式。实际上现有市场上已经出现了不少利用注解来完成依赖注入工做的库,例如ButterKnife。java

ButterKnife 简单来说就是经过注解的方式,简化代码中View变量与XML资源绑定的流程的工具。 
前面提到过,元注解@Retention有三种取值:ide

  • RetentionPoicy.SOURCE:在源文件中有效,在class文件中失效(即只在源文件保留);
  • RetentionPoicy.CLASS:在class文件中有效(即class保留);
  • RetentionPoicy.RUNTIME:在运行时有效(即运行时保留)。

ButterKnife使用的是RetentionPoicy.CLASS级别的注解,为了简单直观,咱们这里使用RUNTIME注解来模仿,固然由于须要经过反射得到元素对象,会损失运行时性能。工具

过程

首先咱们来看一个简单的使用场景:性能

...

@BindView(R.id.title_text)
TextView mTitleTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    ButterKnife.inject(this);
    mTitleTextView.setText("test");

}
...

在onCreate方法中使用如下方法后,咱们已经得到了该控件的引用,能够实现控件方法的各类调用,前提只须要在声明控件变量时添加BindView注解,并设置对应的资源变量。this

ButterKnife.inject(this)

咱们模仿这样一个流程,首先定义一个名为BindView的注解:spa

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface BindView {
    // 惟一的value是要指定的R.id变量
    int value();
}

在这里咱们定义注解保留在运行时环境中,而且这个注解是应用在类中的FIELD上。.net

下面咱们就能在Activity中使用这个注解了(暂时不起做用):code

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.textview_a)
    TextView textViewA;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

如今咱们来实现注解的功能。起一个名为PoorKnife的类,声明一个static方法inject,而且接收一个Activity类型的参数:对象

public class PoorKnife {
    public static void inject(Activity activity) {
        try {
         // 获取类变量
        Class contextClass = Class.forName(activity.getClass().getCanonicalName());
        // 遍历类中全部Field
        for (Field field : contextClass.getDeclaredFields()) {
            // 若是包含注解
            if (field.isAnnotationPresent(BindView.class)) {
                Log.d(TAG, field.getName() + " has annotation");

                // 获得注解值
                int rId = field.getAnnotation(BindView.class).value();
                String type = field.getType().toString();
                if (type.endsWith("TextView")) {
                    // 设置field可访问,并将经过set方法赋值view
                    field.setAccessible(true);
                    field.set(activity, activity.findViewById(rId));
                }
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
        }
     }
}

inject方法中的操做主要用到反射机制: blog

  1. 经过参数activity得到类变量 
  2. 遍历类中全部FIELD(成员变量),并检查是否被标注BindView注解 
  3. 如有注解,则获取注解给定的int值,并经过反射将findViewById获得的view赋值给这个变量 

接下来咱们再次重写onCreate方法,此时才完成了控件对象的初始化工做:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    PoorKnife.inject(this);

    textViewA.setText("PoorKnife succeed!!!");
}

因为是运行时经过反射调用,所以效率相对较低,同理注解 onClick 按照这个流程也能够很方便的实现。

下一步咱们讲讲《注解处理器Annotation Processor 的概念及使用》

相关文章
相关标签/搜索