怎样用 Android Annotations 写出高性能代码

文本出处:http://blog.csdn.net/feelang/article/details/49095235

Android Annotations

Annotations 可以帮助你写出更有意义的契约,它的表现力要大于注释和文档,而且 Android Studio 可以利用这些 Annotations 帮你检测出潜伏的bug。

国际惯例,使用之前,添加依赖包:

compile 'com.android.support:support-annotations:23.0.1
 
 
  • 1
  • 1

空值标记

NonNull 和 Nullable 是一对,通过源码的 @Target 可以看出,它们可以出现在方法声明、参数声明以及成员变量的声明。

@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

下面这段代码,setItemId(null) 肯定会导致 Crash,IDE 也没有提示 bug 风险。

no annotations

作为 setItemId 方法本身,为了保证安全,需要对参数 itemId 做一次判空处理:

public void setItemId(final String itemId) {
    if (!TextUtils.isEmpty(itemId)) {
        Toast.makeText(this, itemId, Toast.LENGTH_SHORT).show();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

调用方并不知道方法内部做了判空处理,很有可能又做了一次判空操作。明明只需要一次判空就OK了,由于信息不对称,导致调用方和提供方各自进行了一次判空操作,造成性能开销。

但是,如果给参数 final String itemId 加上 NonNull,方法提供方就可以放心大胆地不进行判空操作,因为已经通过 annotation 订立了使用”契约” - 参数不能为空

IED 也提示了 bug 风险。

NonNull

Nullable 也就意味着调用方无需再进行判空操作,道理是一样的。


资源标记

当 API 参数是一个表示资源 ID 的 int 时,可以加上资源标记。

Resources

看一下 StringRes 的定义,支持的类型除了方法、参数、成员变量之外,甚至还支持 局部变量。

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

资源标记除了针对任何资源都是用的 @AnyRes,还包括每种资源所对应的特有的标记。

@AnimatorRes@AnimRes@ArrayRes@AttrRes@BoolRes@ColorRes@DimenRes@DrawableRes@FractionRes,@IdRes@IntegerRes@InterpolatorRes@LayoutRes@MenuRes@PluralsRes@RawRes@StringRes,@StyleableRes@StyleRes@XmlRes.


值标记

与资源标记类似,我们还可以使用 @ColorInt 来标记一个表示颜色值 int 变量。甚至还还可以通过 @FloatRang 和@IntRange 来表示变量的取值范围。

float range


Proguard

Fresco 中使用 @DoNotStrip 来防止混淆,并在 proguard 文件中添加了一条规则:

 # Do not strip any method/class that is annotated with @DoNotStrip
 -keep @com.facebook.common.internal.DoNotStrip class *
 -keepclassmembers class * {
     @com.facebook.common.internal.DoNotStrip *;
 }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

现在 Android Annotations 也提供了一个类似的标记 @Keep,与 @DoNotStrip 相比其优势在于可以不用添加 Proguard 规则,Android 的 Gradle 插件自动会帮我们完成这步操作(还在开发中)。

Thread Annotations

Thread Annotations 有四位成员 - @UiThread@MainThread@WorkerThread@BinderThread,它们来自不同的Java 文件,却拥有着共同的 target,不信你看:

@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
 
 
  • 1
  • 2
  • 1
  • 2

如果标记 class,那么这个 class 的所有方法都必须在指定线程上执行,例如

@UiThread
public class NavigationBar {
  @NonNull
  private NavigationBar addEntry(
    @StringRes int iconFontRes, 
    @NonNull String title,
    @NonNull OnClickListener listener) {
    // ...
  }
  // ...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这样一来,NavigationBar 的所有方法都要运行在 Ui Thread,否则 Android Studio 会!报!错!

@MainThread 与 @UiThread 的区别比较微妙,首先放放链接 - Support Annotations。总结起来一句话:@MainThread 用于标记与生命周期相关的方法,@UiThread 用于 View Hierarchy,但是 Android Studio 认为两者是可以互换的,所以有这两种标记的方法可以互相调用。

然后我再稍微补充几句:

一个进程有且只有一个主线程 - @MainThread,同时它也是一个 @UiThread。例如,activity 的 main window 就运行在 @MainThread 上,但是系统也允许应用创建其他的线程以运行不同的 window(除了系统进程会这么干,几乎没有其他场景)

@CheckResult

这个 Annotation 对于【只看方法名无法判断是否有返回值】的方法特别管用,例如:

public boolean openUp()
 
 
  • 1
  • 1

只看方法名openUp,可能不会多想,调用一下就完事了,也不会判断返回值。 
但是如果必须要判断 openUp 的返回值,除了文档约束好像也没有其他办法,有了 @CheckResults,一切都变得简单明了。

check result


@CallSuper

如果你提供了 api 给别人用,但是这个 api 必须要先调用父类方法才能正确执行,怎么办?让 @CallSuper 来拯救你。

CallSuper


Enumerated Annotations

编写高性能代码,从不用 enum 开始!

每一个 enum 都是一个对象,无论从内存还是从性能上来看,都没有一个 primitive type 的变量效率高。

例如,定义一个类 - ActionBar,用户可以通过 setMode 设置不同的模式,如果使用 enum,可能会写成下面这样:

public class ActionBar {
    public enum Theme {
        DARK, LIGHT
    }
    private Theme mTheme;
    public void setTheme(Theme theme) {
        mTheme = theme;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

为了提升代码效率,可以用两个整形变量来替代 Theme,但是这样无法保证参数 theme 的合法性,用户可能传入了DARK 和 LIGHT 之外的值。

public class ActionBar {
    public static final int DARK = 0x00;
    public static final int LIGHT = 0x01;

    private int mTheme;

    public void setTheme(int theme) {
        mTheme = theme;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如何既能提升效率又可以保证传入参数的合法性呢? 
用 @IntDef 来添加一个约束就搞定了。

public class ActionBar {

    @IntDef({DARK, LIGHT})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Theme {}

    public static final int DARK = 0x00;
    public static final int LIGHT = 0x01;

    private int mTheme;

    public void setTheme(@Theme int theme) {
        mTheme = theme;
    }

    @Theme
    public int getTheme() {
        return mTheme;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

如果用户调用 setTheme 时传入了非法参数,Android Studio 会!报!错!

IntDef

如果我们打算把 DARK 和 LIGHT 作为一个 flag,允许用户通过逻辑运算去自由组合,也就说可以这样调用setTheme

setTheme(DARK | LIGHT);
setTheme(DARK & LIGHT);
 
 
  • 1
  • 2
  • 1
  • 2

只需要把 @IntDef 的 flag 属性设为 true 即可。

public class ActionBar {

    @IntDef(flag = true, value = {DARK, LIGHT})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Theme {}

    public static final int DARK = 0x00;
    public static final int LIGHT = 0x01;

    private int mTheme;

    public void setTheme(@Theme int theme) {
        mTheme = theme;
    }

    @Theme
    public int getTheme() {
        return mTheme;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

@StringDef 与 @IntDef 的原理一样,只不过用到的常量是 String 类型。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值