IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Android注解式绑定控件,没你想象的那么难

开源实验室 2016-04-02 13:37:47 累计浏览 3,063 次
本机暂存

Android开发中,有一个让人又爱又恨的方法叫findViewById(int);我想如果你是一民Android开发者,必然知道这个方法。 那么为什么让人又爱又恨呢?想必大家也是很有感触。

吐槽

Android开发中,有一个让人又爱又恨的方法叫findViewById(int);我想如果你是一民Android开发者,必然知道这个方法,

为什么说findViewById(int);让人又爱又恨呢?想必大家也是很有感触。
写一个布局,用Java代码写和用xml文件写,完成速度完全是无法比拟的。xml布局太方便了。
同样的,想获取一个控件的对象,如果你是使用的xml布局文件写的布局,那么你必须调用findViewById()这个方法。

TextView t = (TextView) findViewById(R.id.x);

这是我们最常见的 获取xml布局中一个textview对象的过程。
那么问题就来了,这特么奇葩的方法名也太长了吧!!!好吧,其实人家名字起的也没有错,要描述清楚这函数的含义,也必须这么多个字母。

可是你丫的返回一个View让我用的时候还得强转,这也太麻烦了吧。我一行代码总共也就80列(Eclipse默认),缩进八格(方法写在类里面,语句写在方法里面),
就算像上面的例子textView对象只有一个字母,id也只有一个字母,这一个初始化也要占我54列了。要是变量名再长点,缩进层次再深点,这一个初始化就两行了。
一个界面至少也有四个控件吧,这么复杂的初始化,太坑爹了。
有问题总会有对应的解决办法,下面我就向大家介绍一下KJFrameForAndroid框架使用注解解决这种麻烦。

了解注解:

从jdk1.5开始,Java提供了注解的功能,允许开发者定义和使用自己的注解类型,该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,一个使用注解修饰的class文件和一个注解处理工具组成。
首先,你需要接受一个关键字@interface ,噢,它可不是接口定义关键字,更不是OC里面的@interface关键字,是Java中表示声明一个注解类的关键字。
使用@interface表示我们已经继承了java.lang.annotation.Annotation类,这是一个注解的基类接口,就好像Object类,现在你只需要知道它的存在就行了。
还有一条规定:在定义注解时,不能继承其他的注解或接口。
那么,这就是最简单的一个注解类

public @interfaceMyAnnotation {}

然而通常在使用时我们都会给这个注解类加上两个注解:

@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)

ElementType、RetentionPolicy是两个枚举类,ElementType.FIELD表示我们需要注解的是一个字段,以下是摘自JDK1.6文档中的介绍:

使用注解:

以下为KJFrameForAndroid框架中绑定控件注解部分的定义与使用:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
public int id();
public boolean click() default false;
}
 
@BindView(id = R.id.x, click = true)
private TextView t;

我们可以看到,除了明显减少了代码量,还使得代码结构更加清晰。
其中,定义部分的id() 表示注解接受一个int类型的数据作为id所对应的值(就如使用中的id = R.id.xxx);
同理,定义部分的click表示接受一个Boolean类型的数据作为click对应的值,还可以设置一个默认值使用default修饰;

处理注解:

我们已经知道了注解怎么定义和使用,接下来就应该知道怎么处理了。
上面已经说了,bindview注解可以接受一个int类型的值和一个Boolean类型的值,那么这两个值接受了以后如何获取呢?
其实获取的方式很简单就是通过一个BindView类型的对象,调用这个对象来自声明中定义的两个方法——>id()或click()方法。
现在就有一个问题了,注解类型是不能直接new对象的,那么这个BindView对象从哪里来呢?
这时就需要用到Java的反射机制。我们知道,每一个继承自Object类的类都会继承一个getClass()方法,下面看一下这个方法的原型:

/**
* Returns the unique instance of {@link Class} that represents this
* object's class. Note that {@code getClass()} is a special case in that it
* actually returns {@code Class<? extends Foo>} where {@code Foo} is the
* erasure of the type of the expression {@code getClass()} was called upon.
* <p>
* As an example, the following code actually compiles, although one might
* think it shouldn't:
* <p>
* <pre>{@code
*   List<Integer> l = new ArrayList<Integer>();
*   Class<? extends List> c = l.getClass();}</pre>
*
* @return this object's {@code Class} instance.
*/
public final native Class<?> getClass();

是一个native方法,根据注释我们知道,这个方法返回的是该类的Class对象,同时也是该类的二进制对象。
Class中有一个方法叫getDeclaredFields(),是用来返回这个类的全部字段,返回类型是Field[]
通过Field对象的getAnnotation(Class<?>)方法,我们可以获取到任何一个Class的对象,通过getAnnotation(Class<?>),我们就可以获取到BindView的对象了。

例如:

Field[] fields = currentClass.getClass().getDeclaredFields();
for(int i = 0; i &lt; fields.length; i++){
 
BindView bindView = field.getAnnotation(BindView.class);
 
int viewId = bindView.id();  //这是我们传的id
 
boolean clickLis = bindView.click(); //这是我们传的click
}

在Android项目中应用:

至此,我们已经了解了注解,并且知道怎么使用,怎么处理注解了,现在只剩下最后一个问题:在项目中使用。
很简单,传一个Activity对象,调用findViewById()不就行了。
于是,我们可以这样
activity.findViewById( bindView.id() );
最后在我们的Activity中调用这个函数就OK了。

以下是Android应用框架KJFrameForAndroid中使用注解绑定控件的核心代码:

/**
* @param currentClass
*            当前类,一般为Activity或Fragment
* @param sourceView
*            待绑定控件的直接或间接父控件
*/
public static void initBindView(Object currentClass, View sourceView) {
  // 通过反射获取到全部属性,反射的字段可能是一个类(静态)字段或实例字段
  Field[] fields = currentClass.getClass().getDeclaredFields();
  if (fields != null; fields.length > 0) {
    for (Field field : fields) {
      // 返回BindView类型的注解内容
      BindView bindView = field.getAnnotation(BindView.class);
      if (bindView != null) {
        int viewId = bindView.id();
        boolean clickLis = bindView.click();
        try {
          field.setAccessible(true);
          if (clickLis) {
            sourceView.findViewById(viewId).setOnClickListener(
            (OnClickListener) currentClass);
          }
          // 将currentClass的field赋值为sourceView.findViewById(viewId)
          field.set(currentClass, sourceView.findViewById(viewId));
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }
}

其实安卓中的注解式绑定控件(也是所谓的IOC控制反转在安卓中的一种应用)其实本质的使用就是Java基础中反射的使用。值得一提的是,反射执行的效率是很低的
如果不是必要,应当尽量减少反射的使用,因为它会大大拖累你应用的执行效率。
顺带一提:我一直很排斥注解,因为类反射的效率太低了。现在有很多安卓应用开发框架,比如KJFrameForAndroid, xUtils, afinal, thinkAndroid,这些框架都是使用反射来起到注解绑定控件。
更有的框架甚至是一切东西都使用注解去完成,我只能说注解便捷,但请慎用。</p

同分类推荐文章

  1. 「置顶」我做了什么 (2026-05-05 12:13:28)
  2. 万字长文推演:手机不再从 App 开始,Agent OS 如何接管任务入口 (2026-04-28 14:57:22)
  3. Android Perfetto 系列 10 - Binder 调度与锁竞争 (2025-11-16 15:33:30)

查看更多 移动开发 文章 →

建议继续学习

  1. 情绪版(Mood board)操作流程的新思考 (累计阅读 41,753)
  2. android 开发入门 (累计阅读 19,529)
  3. Android 连接SSID隐藏网络以及 LEAP 认证的方法 (累计阅读 9,539)
  4. 让安卓手机通过代理翻墙的方法 (累计阅读 9,116)
  5. 手机产品设计方向 (累计阅读 7,954)
  6. 实时监控Android设备网络封包 (累计阅读 6,558)
  7. Eclipse开发Android应用程序入门:重装上阵 (累计阅读 6,461)
  8. 基于 PhoneGap 与 Java 开发的 Android 应用的性能对比 (累计阅读 6,411)
  9. Android用户界面设计:表格布局 (累计阅读 6,186)
  10. Windows下使用VMware安装Android (累计阅读 5,633)