深入分析安卓(Android)中的注解
归纳而言,Android中的注解大概有以下好处
1、提高我们的开发效率
2、更早的发现程序的问题或者错误
3、更好的增加代码的描述能力
4、更加利于我们的一些规范约束
5、提供解决问题的更优解
准备工作
默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包.
dependencies{
compile'com.android.support:support-annotations:22.2.0'
}
但是如果我们已经引入了appcompat则没有必要再次引用support-annotations,因为appcompat默认包含了对其引用.
替代枚举
在最早的时候,当我们想要做一些值得限定实现枚举的效果,通常是
1、定义几个常量用于限定
2、从上面的常量选取值进行使用
一个比较描述上面问题的示例代码如下
publicstaticfinalintCOLOR_RED=0;
publicstaticfinalintCOLOR_GREEN=1;
publicstaticfinalintCOLOR_YELLOW=2;
publicvoidsetColor(intcolor){
//somecodehere
}
//调用
setColor(COLOR_RED)
然而上面的还是有不尽完美的地方
1、setColor(COLOR_RED)与setColor(0)效果一样,而后者可读性很差,但却可以正常运行
2、setColor方法可以接受枚举之外的值,比如setColor(3),这种情况下程序可能出问题
一个相对较优的解决方法就是使用Java中的Enum.使用枚举实现的效果如下
//ColorEnum.java
publicenumColorEmun{
RED,
GREEN,
YELLOW
}
publicvoidsetColorEnum(ColorEmuncolorEnum){
//somecodehere
}
setColorEnum(ColorEmun.GREEN);
然而Enum也并非最佳,Enum因为其相比方案一的常量来说,占用内存相对大很多而受到曾经被Google列为不建议使用,为此Google特意引入了一些相关的注解来替代枚举.
Android中新引入的替代枚举的注解有IntDef和StringDef,这里以IntDef做例子说明一下.
publicclassColors{
@IntDef({RED,GREEN,YELLOW})
@Retention(RetentionPolicy.SOURCE)
public@interfaceLightColors{}
publicstaticfinalintRED=0;
publicstaticfinalintGREEN=1;
publicstaticfinalintYELLOW=2;
}
1、声明必要的int常量
2、声明一个注解为LightColors
3、使用@IntDef修饰LightColors,参数设置为待枚举的集合
4、使用@Retention(RetentionPolicy.SOURCE)指定注解仅存在与源码中,不加入到class文件中
Null相关的注解
和Null相关的注解有两个
@Nullable注解的元素可以是Null
@NonNull注解的元素不能是Null
上面的两个可以修饰如下的元素
1、成员属性
2、方法参数
3、方法的返回值
@Nullable
privateStringobtainReferrerFromIntent(@NonNullIntentintent){
returnintent.getStringExtra("apps_referrer");
}
NonNull检测生效的条件
1、显式传入null
2、在调用方法之前已经判断了参数为null时
setReferrer(null);//提示警告
//不提示警告
Stringreferrer=getIntent().getStringExtra("apps_referrer");
setReferrer(referrer);
//提示警告
Stringreferrer=getIntent().getStringExtra("apps_referrer");
if(referrer==null){
setReferrer(referrer);
}
privatevoidsetReferrer(@NonNullStringreferrer){
//somecodehere
}
区间范围注解
Android中的IntRange和FloatRange是两个用来限定区间范围的注解,
floatcurrentProgress;
publicvoidsetCurrentProgress(@FloatRange(from=0.0f,to=1.0f)floatprogress){
currentProgress=progress;
}
如果我们传入非法的值,如下所示
setCurrentProgress(11);
就会得到这样的错误
Valuemustbe>=0.0and<=1.0(was11)
长度以及数组大小限制
限制字符串的长度
privatevoidsetKey(@Size(6)Stringkey){
}
限定数组集合的大小
privatevoidsetData(@Size(max=1)String[]data){
}
setData(newString[]{"b","a"});//erroroccurs
限定特殊的数组长度,比如3的倍数
privatevoidsetItemData(@Size(multiple=3)String[]data){
}
权限相关
在Android中,有很多场景都需要使用权限,无论是Marshmallow之前还是之后的动态权限管理.都需要在manifest中进行声明,如果忘记了,则会导致程序崩溃.好在有一个注解能辅助我们避免这个问题.使用RequiresPermission注解即可.
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
publicvoidchangeWallpaper(Bitmapbitmap)throwsIOException{
}
资源注解
在Android中几乎所有的资源都可以有对应的资源id.比如获取定义的字符串,我们可以通过下面的方法
publicStringgetStringById(intstringResId){
returngetResources().getString(stringResId);
}
使用这个方法,我们可以很容易的获取到定义的字符串,但是这样的写法也存在着风险.
getStringById(R.mipmap.ic_launcher)
如果我们在不知情或者疏忽情况下,传入这样的值,就会出现问题.但是如果我们使用资源相关的注解修饰了参数,就能很大程度上避免错误的情况.
publicStringgetStringById(@StringResintstringResId){
returngetResources().getString(stringResId);
}
在Android中资源注解如下所示
AnimRes
AnimatorRes
AnyRes
ArrayRes
AttrRes
BoolRes
ColorRes
DimenRes
DrawableRes
FractionRes
IdRes
IntegerRes
InterpolatorRes
LayoutRes
MenuRes
PluralsRes
RawRes
StringRes
StyleRes
StyleableRes
TransitionRes
XmlRes
Color值限定
上面部分提到了ColorRes,用来限定颜色资源id,这里我们将使用ColorInt,一个用来限定Color值的注解.在较早的TextView的setTextColor是这样实现的.
publicvoidsetTextColor(intcolor){
mTextColor=ColorStateList.valueOf(color);
updateTextColors();
}
然而上面的方法在调用时常常会出现这种情况
myTextView.setTextColor(R.color.colorAccent);
如上,如果传递过去的参数为color的资源id就会出现颜色取错误的问题,这个问题在过去还是比较严重的.好在ColorInt出现了,改变了这一问题.
publicvoidsetTextColor(@ColorIntintcolor){
mTextColor=ColorStateList.valueOf(color);
updateTextColors();
}
当我们再次传入Color资源值时,就会得到错误的提示.
CheckResult
这是一个关于返回结果的注解,用来注解方法,如果一个方法得到了结果,却没有使用这个结果,就会有错误出现,一旦出现这种错误,就说明你没有正确使用该方法。
@CheckResult
publicStringtrim(Strings){
returns.trim();
}
线程相关
Android中提供了四个与线程相关的注解
@UiThread,通常可以等同于主线程,标注方法需要在UIThread执行,比如View类就使用这个注解
@MainThread主线程,经常启动后创建的第一个线程
@WorkerThread工作者线程,一般为一些后台的线程,比如AsyncTask里面的doInBackground就是这样的.
@BinderThread注解方法必须要在BinderThread线程中执行,一般使用较少.
一些示例
newAsyncTask<Void,Void,Void>(){
//doInBackgroundisalreadyannotatedwith@WorkerThread
@Override
protectedVoiddoInBackground(Void...params){
returnnull;
updateViews();//error
}
};
@UiThread
publicvoidupdateViews(){
Log.i(LOGTAG,"updateViewsThreadInfo="+Thread.currentThread());
}
注意,这种情况下不会出现错误提示
newThread(){
@Override
publicvoidrun(){
super.run();
updateViews();
}
}.start();
虽然updateViews会在一个新的工作者线程中执行,但是在compile时没有错误提示.
因为它的判断依据是,如果updateView的线程注解(这里为@UiThread)和run(没有线程注解)不一致才会错误提示.如果run方法没有线程注解,则不提示.
CallSuper
重写的方法必须要调用super方法
使用这个注解,我们可以强制方法在重写时必须调用父类的方法比如Application的onCreate,onConfigurationChanged等.
Keep
在Android编译生成APK的环节,我们通常需要设置minifyEnabled为true实现下面的两个效果
1、混淆代码
2、删除没有用的代码
但是出于某一些目的,我们需要不混淆某部分代码或者不删除某处代码,除了配置复杂的Proguard文件之外,我们还可以使用@Keep注解.
@Keep
publicstaticintgetBitmapWidth(Bitmapbitmap){
returnbitmap.getWidth();
}
ButterKnife
ButterKnife是一个用来绑定View,资源和回调的提高效率的工具.作者为JakeWharton.ButterKnife的好处
1、使用BindView替代繁琐的findViewById和类型转换
2、使用OnClick注解方法来替换显式声明的匿名内部类
3、使用BindString,BindBool,BindDrawable等注解实现资源获取
一个摘自Github的示例
classExampleActivityextendsActivity{
@BindView(R.id.user)EditTextusername;
@BindView(R.id.pass)EditTextpassword;
@BindString(R.string.login_error)StringloginErrorMessage;
@OnClick(R.id.submit)voidsubmit(){
//TODOcallserver...
}
@OverridepublicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
//TODOUsefields...
}
}
ButterKnife工作原理
以BindView注解使用为例,示例代码为
publicclassMainActivityextendsAppCompatActivity{
@BindView(R.id.myTextView)
TextViewmyTextView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
1.程序在compile时,会根据注解自动生成两个类,这里为MainActivity_ViewBinder.class和MainActivity_ViewBinding.class
2.当我们调用ButterKnife.bind(this);时,会查找当前类对应的ViewBinder类,并调用bind方法,这里会调用到MainActiivty_ViewBinder.bind方法.
3.MainActiivty_ViewBinder.bind方法实际上是调用了findViewById然后在进行类型转换,赋值给MainActivity的myTextView属性
ButterKnife的bind方法
publicstaticUnbinderbind(@NonNullActivitytarget){
returngetViewBinder(target).bind(Finder.ACTIVITY,target,target);
}
ButterKnife的getViewBinder和findViewBinderForClass
@NonNull@CheckResult@UiThread
staticViewBinder<Object>getViewBinder(@NonNullObjecttarget){
Class<?>targetClass=target.getClass();
if(debug)Log.d(TAG,"Lookingupviewbinderfor"+targetClass.getName());
returnfindViewBinderForClass(targetClass);
}
@NonNull@CheckResult@UiThread
privatestaticViewBinder<Object>findViewBinderForClass(Class<?>cls){
//如果内存集合BINDERS中包含,则不再查找
ViewBinder<Object>viewBinder=BINDERS.get(cls);
if(viewBinder!=null){
if(debug)Log.d(TAG,"HIT:Cachedinviewbindermap.");
returnviewBinder;
}
StringclsName=cls.getName();
if(clsName.startsWith("android.")||clsName.startsWith("java.")){
if(debug)Log.d(TAG,"MISS:Reachedframeworkclass.Abandoningsearch.");
returnNOP_VIEW_BINDER;
}
//noinspectionTryWithIdenticalCatchesResolvestoAPI19+onlytype.
try{
//使用反射创建实例
Class<?>viewBindingClass=Class.forName(clsName+"_ViewBinder");
//noinspectionunchecked
viewBinder=(ViewBinder<Object>)viewBindingClass.newInstance();
if(debug)Log.d(TAG,"HIT:Loadedviewbinderclass.");
}catch(ClassNotFoundExceptione){
//如果没有找到,对父类进行查找
if(debug)Log.d(TAG,"Notfound.Tryingsuperclass"+cls.getSuperclass().getName());
viewBinder=findViewBinderForClass(cls.getSuperclass());
}catch(InstantiationExceptione){
thrownewRuntimeException("Unabletocreateviewbinderfor"+clsName,e);
}catch(IllegalAccessExceptione){
thrownewRuntimeException("Unabletocreateviewbinderfor"+clsName,e);
}
//加入内存集合,便于后续的查找
BINDERS.put(cls,viewBinder);
returnviewBinder;
}
MainActivity_ViewBinder的反编译源码
➜androidannotationsamplejavap-cMainActivity_ViewBinder
Warning:BinaryfileMainActivity_ViewBindercontainscom.example.admin.androidannotationsample.MainActivity_ViewBinder
Compiledfrom"MainActivity_ViewBinder.java"
publicfinalclasscom.example.admin.androidannotationsample.MainActivity_ViewBinderimplementsbutterknife.internal.ViewBinder<com.example.admin.androidannotationsample.MainActivity>{
publiccom.example.admin.androidannotationsample.MainActivity_ViewBinder();
Code:
0:aload_0
1:invokespecial#1//Methodjava/lang/Object."<init>":()V
4:return
publicbutterknife.Unbinderbind(butterknife.internal.Finder,com.example.admin.androidannotationsample.MainActivity,java.lang.Object);
Code:
0:new#2//classcom/example/admin/androidannotationsample/MainActivity_ViewBinding
3:dup
4:aload_2
5:aload_1
6:aload_3//创建ViewBinding实例
7:invokespecial#3//Methodcom/example/admin/androidannotationsample/MainActivity_ViewBinding."<init>":(Lcom/example/admin/androidannotationsample/MainActivity;Lbutterknife/internal/Finder;Ljava/lang/Object;)V
10:areturn
publicbutterknife.Unbinderbind(butterknife.internal.Finder,java.lang.Object,java.lang.Object);
Code:
0:aload_0
1:aload_1
2:aload_2
3:checkcast#4//classcom/example/admin/androidannotationsample/MainActivity
6:aload_3//调用上面的重载方法
7:invokevirtual#5//Methodbind:(Lbutterknife/internal/Finder;Lcom/example/admin/androidannotationsample/MainActivity;Ljava/lang/Object;)Lbutterknife/Unbinder;
10:areturn
}
MainActivity_ViewBinding的反编译源码
➜androidannotationsamplejavap-cMainActivity_ViewBinding
Warning:BinaryfileMainActivity_ViewBindingcontainscom.example.admin.androidannotationsample.MainActivity_ViewBinding
Compiledfrom"MainActivity_ViewBinding.java"
publicclasscom.example.admin.androidannotationsample.MainActivity_ViewBinding<Textendscom.example.admin.androidannotationsample.MainActivity>implementsbutterknife.Unbinder{
protectedTtarget;
publiccom.example.admin.androidannotationsample.MainActivity_ViewBinding(T,butterknife.internal.Finder,java.lang.Object);
Code:
0:aload_0
1:invokespecial#1//Methodjava/lang/Object."<init>":()V
4:aload_0
5:aload_1
6:putfield#2//Fieldtarget:Lcom/example/admin/androidannotationsample/MainActivity;
9:aload_1
10:aload_2
11:aload_3//调用Finder.findRequireViewAsType找到View,并进行类型转换,并复制给MainActivity中对一个的变量
12:ldc#4//int2131427412
14:ldc#5//Stringfield'myTextView'
16:ldc#6//classandroid/widget/TextView
//内部实际调用了findViewById
18:invokevirtual#7//Methodbutterknife/internal/Finder.findRequiredViewAsType:(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
21:checkcast#6//classandroid/widget/TextView
24:putfield#8//Fieldcom/example/admin/androidannotationsample/MainActivity.myTextView:Landroid/widget/TextView;
27:return
publicvoidunbind();
Code:
0:aload_0
1:getfield#2//Fieldtarget:Lcom/example/admin/androidannotationsample/MainActivity;
4:astore_1
5:aload_1
6:ifnonnull19
9:new#9//classjava/lang/IllegalStateException
12:dup
13:ldc#10//StringBindingsalreadycleared.
15:invokespecial#11//Methodjava/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
18:athrow
19:aload_1
20:aconst_null//解除绑定,设置对应的变量为null
21:putfield#8//Fieldcom/example/admin/androidannotationsample/MainActivity.myTextView:Landroid/widget/TextView;
24:aload_0
25:aconst_null
26:putfield#2//Fieldtarget:Lcom/example/admin/androidannotationsample/MainActivity;
29:return
}
Finder的源码
packagebutterknife.internal;
importandroid.app.Activity;
importandroid.app.Dialog;
importandroid.content.Context;
importandroid.support.annotation.IdRes;
importandroid.view.View;
@SuppressWarnings("UnusedDeclaration")//Usedbygeneratedcode.
publicenumFinder{
VIEW{
@OverridepublicViewfindOptionalView(Objectsource,@IdResintid){
return((View)source).findViewById(id);
}
@OverridepublicContextgetContext(Objectsource){
return((View)source).getContext();
}
@OverrideprotectedStringgetResourceEntryName(Objectsource,@IdResintid){
finalViewview=(View)source;
//Ineditmode,getResourceEntryName()isunsupportedduetouseofBridgeResources
if(view.isInEditMode()){
return"<unavailablewhileediting>";
}
returnsuper.getResourceEntryName(source,id);
}
},
ACTIVITY{
@OverridepublicViewfindOptionalView(Objectsource,@IdResintid){
return((Activity)source).findViewById(id);
}
@OverridepublicContextgetContext(Objectsource){
return(Activity)source;
}
},
DIALOG{
@OverridepublicViewfindOptionalView(Objectsource,@IdResintid){
return((Dialog)source).findViewById(id);
}
@OverridepublicContextgetContext(Objectsource){
return((Dialog)source).getContext();
}
};
//查找对应的Finder,如上面的ACTIVITY,DIALOG,VIEW
publicabstractViewfindOptionalView(Objectsource,@IdResintid);
publicfinal<T>TfindOptionalViewAsType(Objectsource,@IdResintid,Stringwho,
Class<T>cls){
Viewview=findOptionalView(source,id);
returncastView(view,id,who,cls);
}
publicfinalViewfindRequiredView(Objectsource,@IdResintid,Stringwho){
Viewview=findOptionalView(source,id);
if(view!=null){
returnview;
}
Stringname=getResourceEntryName(source,id);
thrownewIllegalStateException("Requiredview'"
+name
+"'withID"
+id
+"for"
+who
+"wasnotfound.Ifthisviewisoptionaladd'@Nullable'(fields)or'@Optional'"
+"(methods)annotation.");
}
//来自ViewBinding的调用
publicfinal<T>TfindRequiredViewAsType(Objectsource,@IdResintid,Stringwho,
Class<T>cls){
Viewview=findRequiredView(source,id,who);
returncastView(view,id,who,cls);
}
publicfinal<T>TcastView(Viewview,@IdResintid,Stringwho,Class<T>cls){
try{
returncls.cast(view);
}catch(ClassCastExceptione){
Stringname=getResourceEntryName(view,id);
thrownewIllegalStateException("View'"
+name
+"'withID"
+id
+"for"
+who
+"wasofthewrongtype.Seecauseformoreinfo.",e);
}
}
@SuppressWarnings("unchecked")//That'sthepoint.
publicfinal<T>TcastParam(Objectvalue,Stringfrom,intfromPos,Stringto,inttoPos){
try{
return(T)value;
}catch(ClassCastExceptione){
thrownewIllegalStateException("Parameter#"
+(fromPos+1)
+"ofmethod'"
+from
+"'wasofthewrongtypeforparameter#"
+(toPos+1)
+"ofmethod'"
+to
+"'.Seecauseformoreinfo.",e);
}
}
protectedStringgetResourceEntryName(Objectsource,@IdResintid){
returngetContext(source).getResources().getResourceEntryName(id);
}
publicabstractContextgetContext(Objectsource);
}
Otto
OttoBus是一个专为Android改装的EventBus,在很多项目中都有应用.由Square开源共享.
publicclassEventBusTest{
privatestaticfinalStringLOGTAG="EventBusTest";
BusmBus=newBus();
publicvoidtest(){
mBus.register(this);
}
classNetworkChangedEvent{
}
@Produce
publicNetworkChangedEventsendNetworkChangedEvent(){
returnnewNetworkChangedEvent();
}
@Subscribe
publicvoidonNetworkChanged(NetworkChangedEventevent){
Log.i(LOGTAG,"onNetworkChangedevent="+event);
}
}
Otto的工作原理
1、使用@Produce和@Subscribe标记方法
2、当调用bus.register方法,去检索注册对象的标记方法,并cache映射关系
3、当post事件时,将事件与handler方法对应加入事件队列
4、抽取事件队列,然后调用handler处理
如下为对Otto如何利用注解的分析
register的源码
publicvoidregister(Objectobject){
if(object==null){
thrownewNullPointerException("Objecttoregistermustnotbenull.");
}
enforcer.enforce(this);
//查找object中的Subscriber
Map<Class<?>,Set<EventHandler>>foundHandlersMap=handlerFinder.findAllSubscribers(object);
for(Class<?>type:foundHandlersMap.keySet()){
Set<EventHandler>handlers=handlersByType.get(type);
if(handlers==null){
//concurrentputifabsent
Set<EventHandler>handlersCreation=newCopyOnWriteArraySet<EventHandler>();
handlers=handlersByType.putIfAbsent(type,handlersCreation);
if(handlers==null){
handlers=handlersCreation;
}
}
finalSet<EventHandler>foundHandlers=foundHandlersMap.get(type);
if(!handlers.addAll(foundHandlers)){
thrownewIllegalArgumentException("Objectalreadyregistered.");
}
}
for(Map.Entry<Class<?>,Set<EventHandler>>entry:foundHandlersMap.entrySet()){
Class<?>type=entry.getKey();
EventProducerproducer=producersByType.get(type);
if(producer!=null&&producer.isValid()){
Set<EventHandler>foundHandlers=entry.getValue();
for(EventHandlerfoundHandler:foundHandlers){
if(!producer.isValid()){
break;
}
if(foundHandler.isValid()){
dispatchProducerResultToHandler(foundHandler,producer);
}
}
}
}
}
HandlerFinder源码
interfaceHandlerFinder{
Map<Class<?>,EventProducer>findAllProducers(Objectlistener);
Map<Class<?>,Set<EventHandler>>findAllSubscribers(Objectlistener);
//Otto注解查找器
HandlerFinderANNOTATED=newHandlerFinder(){
@Override
publicMap<Class<?>,EventProducer>findAllProducers(Objectlistener){
returnAnnotatedHandlerFinder.findAllProducers(listener);
}
@Override
publicMap<Class<?>,Set<EventHandler>>findAllSubscribers(Objectlistener){
returnAnnotatedHandlerFinder.findAllSubscribers(listener);
}
};
具体查找实现
/**Thisimplementationfindsallmethodsmarkedwitha{@linkSubscribe}annotation.*/
staticMap<Class<?>,Set<EventHandler>>findAllSubscribers(Objectlistener){
Class<?>listenerClass=listener.getClass();
Map<Class<?>,Set<EventHandler>>handlersInMethod=newHashMap<Class<?>,Set<EventHandler>>();
Map<Class<?>,Set<Method>>methods=SUBSCRIBERS_CACHE.get(listenerClass);
if(null==methods){
methods=newHashMap<Class<?>,Set<Method>>();
loadAnnotatedSubscriberMethods(listenerClass,methods);
}
if(!methods.isEmpty()){
for(Map.Entry<Class<?>,Set<Method>>e:methods.entrySet()){
Set<EventHandler>handlers=newHashSet<EventHandler>();
for(Methodm:e.getValue()){
handlers.add(newEventHandler(listener,m));
}
handlersInMethod.put(e.getKey(),handlers);
}
}
returnhandlersInMethod;
}
总结
以上就是关于Android中注解的一些总结,文章部分内容参考自SupportAnnotations,希望能帮助大家对注解有基础的认识,并运用到实际的日常开发之中。如有有疑问欢迎大家留言讨论。