Android 中的注解深入探究

本文系GDG Android Meetup分享内容总结文章

源自JakeWharton的经典制作ButterKnife, 相信大多数人都用的很熟练,
它一定程度上简化了我们的findViewById以及OnClick等操作,给平时开发带来了很大的便利。此处对是否应该使用它,我不作评论,也不是本篇的目的。

注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理.

这个使用了多年的框架,
你真的知道是什么原理吗?它的实现是怎样的,你有产生过好奇心吗?那么就让我们来解读一下。

归纳而言,Android中的注解大概有以下好处


  • 提高我们的开发效率
  • 更早的发现程序的问题或者错误
  • 更好的增加代码的描述能力
  • 更加利于我们的一些规范约束
  • 提供解决问题的更优解

解读ButterKnife之准备篇

准备工作

默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包.

dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}

但是如果我们已经引入了appcompat则没有必要再次引用support-annotations,因为appcompat默认包含了对其引用.

反射

这是一个多数框架都会使用的技术,只要知道类或对象,我们可以解析出关于此类的一切信息,为所欲为。一提反射,很多人会觉得性能消耗不会很大吗?肯定你也带着同样的想法吧?那么我来声明一下,反射存在性能消耗是有的,但是没有想象中的夸张;如果结合缓存使用的话,可以极大的抵消带来的性能的消耗,从而更好的享受它所带来的便利。作为一个Java开发者,不会反射真的有些过分了..

替代枚举

在最早的时候,当我们想要做一些值得限定实现枚举的效果,通常是

  • 定义几个常量用于限定
  • 从上面的常量选取值进行使用

一个比较描述上面问题的示例代码如下

public static final int COLOR_RED = 0;
public static final int COLOR_GREEN = 1;
public static final int COLOR_YELLOW = 2;

public void setColor(int color) {
    //some code here
}
//调用
setColor(COLOR_RED)

然而上面的还是有不尽完美的地方

  • setColor(COLOR_RED)setColor(0)效果一样,而后者可读性很差,但却可以正常运行
  • setColor方法可以接受枚举之外的值,比如setColor(3)澳门新葡亰手机版,,这种情况下程序可能出问题

一个相对较优的解决方法就是使用Java中的Enum.使用枚举实现的效果如下

// ColorEnum.java
public enum ColorEmun {
    RED,
    GREEN,
    YELLOW
}

public void setColorEnum(ColorEmun colorEnum) {
    //some code here
}

setColorEnum(ColorEmun.GREEN);

然而Enum也并非最佳,Enum因为其相比方案一的常量来说,占用内存相对大很多而受到曾经被Google列为不建议使用,为此Google特意引入了一些相关的注解来替代枚举.

Android中新引入的替代枚举的注解有IntDefStringDef,这里以IntDef做例子说明一下.

public class Colors {
    @IntDef({RED, GREEN, YELLOW})
    @Retention(RetentionPolicy.SOURCE)
    public @interface LightColors{}

    public static final int RED = 0;
    public static final int GREEN = 1;
    public static final int YELLOW = 2;
}
  • 声明必要的int常量
  • 声明一个注解为LightColors
  • 使用@IntDef修饰LightColors,参数设置为待枚举的集合
  • 使用@Retention(RetentionPolicy.SOURCE)指定注解仅存在与源码中,不加入到class文件中
AnnotationProcessor

大家都知道,ButterKnifed
便利来自于注解。那么既然存在注解,注解处理器技术的使用是必然的。现在的框架不同于以往;以前的框架类似XUtils的注解很大程度上是使用反射来解析的,上面说过,反射带来性能消耗还是有的;但是现在,
大多数的注解框架都是基于Apt或AnnotationProcessor的编译时解析实现的。试想一下,在程序编译时就完成了注解解析的工作,又会给性能带来什么影响呢?答案当然是没影响。

早期的注解处理器大多使用开源的Apt,
然而一是Apt已不再被作者所维护;二则Google推出了AnnotationProcessor来替代它,更是集成到了API中,所以怎么看,使用AnnotationProcessor都是一个很好的选择。

Null相关的注解

和Null相关的注解有两个

@Nullable 注解的元素可以是Null
@NonNull 注解的元素不能是Null

上面的两个可以修饰如下的元素

  • 成员属性
  • 方法参数
  • 方法的返回值

@Nullable
private String obtainReferrerFromIntent(@NonNull Intent intent) {
    return intent.getStringExtra("apps_referrer");
}

NonNull检测生效的条件

  • 显式传入null
  • 在调用方法之前已经判断了参数为null时

setReferrer(null);//提示警告

//不提示警告
String referrer = getIntent().getStringExtra("apps_referrer");
setReferrer(referrer);

//提示警告
String referrer = getIntent().getStringExtra("apps_referrer");
if (referrer == null) {
    setReferrer(referrer);
}

private void setReferrer(@NonNull String referrer) {
    //some code here
}
Javapoet

这是一个很神奇的技术,借助它,我们可以便捷的生成我们想要的代码。至少目前来看,GreenDao生成的Bean与Dao,
ButterKnife生成的Binding以及ARouter生成的Router&&Group&&xx等,都是使用了Poet技术。

以上3项技术被大多数框架所使用,如果你还不会,你已经Out了。那么读完这篇文章,让你脱离Out的领域。


区间范围注解

Android中的IntRange和FloatRange是两个用来限定区间范围的注解,

float currentProgress;

public void setCurrentProgress(@FloatRange(from=0.0f, to=1.0f) float progress) {
    currentProgress = progress;
}

如果我们传入非法的值,如下所示

setCurrentProgress(11);

就会得到这样的错误

Value must be >=0.0 and <= 1.0(was 11)

ButterKnife的使用

这里不着重说怎么用ButterKnife,
或者说作为一个如此简单的框架,压根就不用多说。这里引入它的使用,为ButterKnife的解析作为一个引导。

// 初始化
ButterKnife.bind(Object target, View source);

// 加入注解
@BindView(id)
Button 

以上就是ButterKnife的基本调用。初始化,然后添加各种注解,如BindView,
OnClick, BindBitmap。
如果你看过它的源码,你就会知道,ButterKnife支持的注解主要分为3类:
BindView之绑定View, BindBitmap/ BindColor等绑定资源,
OnClick等绑定事件。至于为什么不添加绑定setContentView,
大神说只能替换一行代码而已,没意思。大神就是这么任性…


长度以及数组大小限制

限制字符串的长度

private void setKey(@Size(6) String key) {
}

限定数组集合的大小

private void setData(@Size(max = 1) String[] data) {
}
setData(new String[]{"b", "a"});//error occurs

限定特殊的数组长度,比如3的倍数

private void setItemData(@Size(multiple = 3) String[] data) {
}

ButterKnife的实现思想

从注解,
从我列举的准备技术,我想聪明的你已经猜到了ButterKnife是如何实现了吧。是的,它所使用的核心技术可以称之为依赖注入。俗话说就是,我们不在Activity中初始化这些View,
这堆资源,
这系列的点击事件;我们通过生成自定义类来偷偷的初始化,偷偷的绑定监听。对,偷偷的…

权限相关

在Android中,有很多场景都需要使用权限,无论是Marshmallow之前还是之后的动态权限管理.都需要在manifest中进行声明,如果忘记了,则会导致程序崩溃.
好在有一个注解能辅助我们避免这个问题.使用RequiresPermission注解即可.

@RequiresPermission(Manifest.permission.SET_WALLPAPER)
    public void changeWallpaper(Bitmap bitmap) throws IOException {
}
1)添加注解

这个没什么多说的, ButterKnife的核心即是通过注解来简化操作。