Android 中的注解深入探究

Android 中的注解深入探究,第1张

概述本文系GDGAndroidMeetup分享内容总结文章注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理.

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

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

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

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

准备工作

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

dependencIEs {  compile 'com.androID.support:support-annotations:22.2.0'}

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

替代枚举

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

定义几个常量用于限定 从上面的常量选取值进行使用 一个比较描述上面问题的示例代码如下
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.javapublic enum colorEmun {  RED,GREEN,YELLOW}public voID setcolorEnum(colorEmun colorEnum) {  //some code here}setcolorEnum(colorEmun.GREEN);

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

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

public class colors {  @IntDef({RED,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文件中

Null相关的注解

和Null相关的注解有两个

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

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

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

@Nullableprivate 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}

区间范围注解

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)

长度以及数组大小限制

限制字符串的长度

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) {
}

权限相关

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

@RequiresPermission(Manifest.permission.SET_WALLPAPER)  public voID changeWallpaper(Bitmap bitmap) throws IOException {}

资源注解

在AndroID中几乎所有的资源都可以有对应的资源ID.比如获取定义的字符串,我们可以通过下面的方法

public String getStringByID(int stringResID) {  return getResources().getString(stringResID);}

使用这个方法,我们可以很容易的获取到定义的字符串,但是这样的写法也存在着风险.

getStringByID(R.mipmap.ic_launcher)

如果我们在不知情或者疏忽情况下,传入这样的值,就会出现问题. 但是如果我们使用资源相关的注解修饰了参数,就能很大程度上避免错误的情况.

public String getStringByID(@StringRes int stringResID) {  return getResources().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是这样实现的.

public voID setTextcolor(int color) {  mTextcolor = colorStateList.valueOf(color);  updateTextcolors();}

然而上面的方法在调用时常常会出现这种情况

myTextVIEw.setTextcolor(R.color.colorAccent);

如上,如果传递过去的参数为color的资源ID就会出现颜色取错误的问题,这个问题在过去还是比较严重的.好在colorInt出现了,改变了这一问题.

public voID setTextcolor(@colorInt int color) {  mTextcolor = colorStateList.valueOf(color);  updateTextcolors();}

当我们再次传入color资源值时,就会得到错误的提示.

CheckResult

这是一个关于返回结果的注解,用来注解方法,如果一个方法得到了结果,却没有使用这个结果,就会有错误出现,一旦出现这种错误,就说明你没有正确使用该方法。

@CheckResultpublic String trim(String s) {  return s.trim();}

线程相关

AndroID中提供了四个与线程相关的注解

@UiThread,通常可以等同于主线程,标注方法需要在UIThread执行,比如VIEw类就使用这个注解 @MainThread 主线程,经常启动后创建的第一个线程 @WorkerThread 工作者线程,一般为一些后台的线程,比如AsyncTask里面的doInBackground就是这样的. @BinderThread 注解方法必须要在BinderThread线程中执行,一般使用较少.

一些示例

new AsyncTask<VoID,VoID,VoID>() {    //doInBackground is already annotated with @WorkerThread    @OverrIDe    protected VoID doInBackground(VoID... params) {      return null;      updateVIEws();//error    }  };@UiThreadpublic voID updateVIEws() {  Log.i(LOGTAG,"updateVIEws ThreadInfo=" + Thread.currentThread());}

注意,这种情况下不会出现错误提示

new Thread(){  @OverrIDe  public voID run() {    super.run();    updateVIEws();  }}.start();

虽然updateVIEws会在一个新的工作者线程中执行,但是在compile时没有错误提示.

因为它的判断依据是,如果updateVIEw的线程注解(这里为@UiThread)和run(没有线程注解)不一致才会错误提示.如果run方法没有线程注解,则不提示.

CallSuper

重写的方法必须要调用super方法

使用这个注解,我们可以强制方法在重写时必须调用父类的方法 比如Application的onCreate,onConfigurationChanged等.

Keep

在AndroID编译生成APK的环节,我们通常需要设置MinifyEnabled为true实现下面的两个效果

混淆代码

删除没有用的代码

但是出于某一些目的,我们需要不混淆某部分代码或者不删除某处代码,除了配置复杂的Proguard文件之外,我们还可以使用@Keep注解 .

@Keeppublic static int getBitmapWIDth(Bitmap bitmap) {  return bitmap.getWIDth();}

ButterKnife

ButterKnife是一个用来绑定VIEw,资源和回调的提高效率的工具.作者为Jake Wharton. ButterKnife的好处

使用BindVIEw替代繁琐的findVIEwByID和类型转换 使用OnClick注解方法来替换显式声明的匿名内部类 使用BindString,BindBool,BindDrawable等注解实现资源获取

一个摘自Github的示例

class ExampleActivity extends Activity { @BindVIEw(R.ID.user) EditText username; @BindVIEw(R.ID.pass) EditText password; @BindString(R.string.login_error) String loginErrorMessage; @OnClick(R.ID.submit) voID submit() {  // Todo call server... } @OverrIDe public voID onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentVIEw(R.layout.simple_activity);  ButterKnife.bind(this);  // Todo Use fIElds... }}

ButterKnife工作原理

以BindVIEw注解使用为例,示例代码为

public class MainActivity extends AppCompatActivity {  @BindVIEw(R.ID.myTextVIEw)  TextVIEw myTextVIEw;  @OverrIDe  protected voID onCreate(Bundle savedInstanceState) {    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方法

public static Unbinder bind(@NonNull Activity target) {  return getVIEwBinder(target).bind(Finder.ACTIVITY,target,target);}

ButterKnife的getVIEwBinder和findVIEwBinderForClass

@NonNull @CheckResult @UiThread static VIEwBinder<Object> getVIEwBinder(@NonNull Object target) {  Class<?> targetClass = target.getClass();  if (deBUG) Log.d(TAG,"Looking up vIEw binder for " + targetClass.getname());  return findVIEwBinderForClass(targetClass); } @NonNull @CheckResult @UiThread private static VIEwBinder<Object> findVIEwBinderForClass(Class<?> cls) {  //如果内存集合BINDERS中包含,则不再查找  VIEwBinder<Object> vIEwBinder = BINDERS.get(cls);  if (vIEwBinder != null) {   if (deBUG) Log.d(TAG,"HIT: Cached in vIEw binder map.");   return vIEwBinder;  }  String clsname = cls.getname();  if (clsname.startsWith("androID.") || clsname.startsWith("java.")) {   if (deBUG) Log.d(TAG,"MISS: Reached framework class. Abandoning search.");   return nop_VIEW_BINDER;  }  //noinspection TryWithIDenticalCatches Resolves to API 19+ only type.  try {   //使用反射创建实例   Class<?> vIEwBindingClass = Class.forname(clsname + "_VIEwBinder");   //noinspection unchecked   vIEwBinder = (VIEwBinder<Object>) vIEwBindingClass.newInstance();   if (deBUG) Log.d(TAG,"HIT: Loaded vIEw binder class.");  } catch (ClassNotFoundException e) {    //如果没有找到,对父类进行查找   if (deBUG) Log.d(TAG,"Not found. Trying superclass " + cls.getSuperclass().getname());   vIEwBinder = findVIEwBinderForClass(cls.getSuperclass());  } catch (InstantiationException e) {   throw new RuntimeException("Unable to create vIEw binder for " + clsname,e);  } catch (illegalaccessexception e) {   throw new RuntimeException("Unable to create vIEw binder for " + clsname,e);  }  //加入内存集合,便于后续的查找  BINDERS.put(cls,vIEwBinder);  return vIEwBinder; }

MainActivity_VIEwBinder的反编译源码

➜ androIDannotationsample javap -c MainActivity_VIEwBinderWarning: Binary file MainActivity_VIEwBinder contains com.example.admin.androIDannotationsample.MainActivity_VIEwBinderCompiled from "MainActivity_VIEwBinder.java"public final class com.example.admin.androIDannotationsample.MainActivity_VIEwBinder implements butterknife.internal.VIEwBinder<com.example.admin.androIDannotationsample.MainActivity> { public com.example.admin.androIDannotationsample.MainActivity_VIEwBinder();  Code:    0: aload_0    1: invokespecial #1         // Method java/lang/Object."<init>":()V    4: return public butterknife.Unbinder bind(butterknife.internal.Finder,com.example.admin.androIDannotationsample.MainActivity,java.lang.Object);  Code:    0: new      #2         // class com/example/admin/androIDannotationsample/MainActivity_VIEwBinding    3: dup    4: aload_2    5: aload_1    6: aload_3              // 创建VIEwBinding实例    7: invokespecial #3         // Method com/example/admin/androIDannotationsample/MainActivity_VIEwBinding."<init>":(Lcom/example/admin/androIDannotationsample/MainActivity;Lbutterknife/internal/Finder;Ljava/lang/Object;)V   10: areturn public butterknife.Unbinder bind(butterknife.internal.Finder,java.lang.Object,java.lang.Object);  Code:    0: aload_0    1: aload_1    2: aload_2    3: checkcast   #4         // class com/example/admin/androIDannotationsample/MainActivity    6: aload_3              //调用上面的重载方法    7: invokevirtual #5         // Method bind:(Lbutterknife/internal/Finder;Lcom/example/admin/androIDannotationsample/MainActivity;Ljava/lang/Object;)Lbutterknife/Unbinder;   10: areturn}MainActivity_VIEwBinding的反编译源码➜ androIDannotationsample javap -c MainActivity_VIEwBindingWarning: Binary file MainActivity_VIEwBinding contains com.example.admin.androIDannotationsample.MainActivity_VIEwBindingCompiled from "MainActivity_VIEwBinding.java"public class com.example.admin.androIDannotationsample.MainActivity_VIEwBinding<T extends com.example.admin.androIDannotationsample.MainActivity> implements butterknife.Unbinder { protected T target; public com.example.admin.androIDannotationsample.MainActivity_VIEwBinding(T,butterknife.internal.Finder,java.lang.Object);  Code:    0: aload_0    1: invokespecial #1         // Method java/lang/Object."<init>":()V    4: aload_0    5: aload_1    6: putfIEld   #2         // FIEld target:Lcom/example/admin/androIDannotationsample/MainActivity;    9: aload_1   10: aload_2   11: aload_3              //调用Finder.findRequireVIEwAsType找到VIEw,并进行类型转换,并复制给MainActivity中对一个的变量   12: ldc      #4         // int 2131427412   14: ldc      #5         // String fIEld 'myTextVIEw'   16: ldc      #6         // class androID/Widget/TextVIEw                      // 内部实际调用了findVIEwByID   18: invokevirtual #7         // Method butterknife/internal/Finder.findrequiredVIEwAsType:(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;   21: checkcast   #6         // class androID/Widget/TextVIEw   24: putfIEld   #8         // FIEld com/example/admin/androIDannotationsample/MainActivity.myTextVIEw:LandroID/Widget/TextVIEw;   27: return public voID unbind();  Code:    0: aload_0    1: getfIEld   #2         // FIEld target:Lcom/example/admin/androIDannotationsample/MainActivity;    4: astore_1    5: aload_1    6: ifnonnull   19    9: new      #9         // class java/lang/IllegalStateException   12: dup   13: ldc      #10         // String Bindings already cleared.   15: invokespecial #11         // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V   18: athrow   19: aload_1   20: aconst_null            // 解除绑定,设置对应的变量为null   21: putfIEld   #8         // FIEld com/example/admin/androIDannotationsample/MainActivity.myTextVIEw:LandroID/Widget/TextVIEw;   24: aload_0   25: aconst_null   26: putfIEld   #2         // FIEld target:Lcom/example/admin/androIDannotationsample/MainActivity;   29: return}

Finder的源码

package butterknife.internal;import androID.app.Activity;import androID.app.Dialog;import androID.content.Context;import androID.support.annotation.IDRes;import androID.vIEw.VIEw;@SuppressWarnings("UnusedDeclaration") // Used by generated code.public enum Finder { VIEW {  @OverrIDe public VIEw findOptionalVIEw(Object source,@IDRes int ID) {   return ((VIEw) source).findVIEwByID(ID);  }  @OverrIDe public Context getContext(Object source) {   return ((VIEw) source).getContext();  }  @OverrIDe protected String getResourceEntryname(Object source,@IDRes int ID) {   final VIEw vIEw = (VIEw) source;   // In edit mode,getResourceEntryname() is unsupported due to use of BrIDgeResources   if (vIEw.isInEditMode()) {    return "<unavailable while editing>";   }   return super.getResourceEntryname(source,ID);  } },ACTIVITY {  @OverrIDe public VIEw findOptionalVIEw(Object source,@IDRes int ID) {   return ((Activity) source).findVIEwByID(ID);  }  @OverrIDe public Context getContext(Object source) {   return (Activity) source;  } },DIALOG {  @OverrIDe public VIEw findOptionalVIEw(Object source,@IDRes int ID) {   return ((Dialog) source).findVIEwByID(ID);  }  @OverrIDe public Context getContext(Object source) {   return ((Dialog) source).getContext();  } }; //查找对应的Finder,如上面的ACTIVITY,DIALOG,VIEW public abstract VIEw findOptionalVIEw(Object source,@IDRes int ID); public final <T> T findOptionalVIEwAsType(Object source,@IDRes int ID,String who,Class<T> cls) {  VIEw vIEw = findOptionalVIEw(source,ID);  return castVIEw(vIEw,ID,who,cls); } public final VIEw findrequiredVIEw(Object source,String who) {  VIEw vIEw = findOptionalVIEw(source,ID);  if (vIEw != null) {   return vIEw;  }  String name = getResourceEntryname(source,ID);  throw new IllegalStateException("required vIEw '"    + name    + "' with ID "    + ID    + " for "    + who    + " was not found. If this vIEw is optional add '@Nullable' (fIElds) or '@Optional'"    + " (methods) annotation."); } //来自VIEwBinding的调用 public final <T> T findrequiredVIEwAsType(Object source,Class<T> cls) {  VIEw vIEw = findrequiredVIEw(source,who);  return castVIEw(vIEw,cls); } public final <T> T castVIEw(VIEw vIEw,Class<T> cls) {  try {   return cls.cast(vIEw);  } catch (ClassCastException e) {   String name = getResourceEntryname(vIEw,ID);   throw new IllegalStateException("VIEw '"     + name     + "' with ID "     + ID     + " for "     + who     + " was of the wrong type. See cause for more info.",e);  } } @SuppressWarnings("unchecked") // That's the point. public final <T> T castParam(Object value,String from,int fromPos,String to,int topos) {  try {   return (T) value;  } catch (ClassCastException e) {   throw new IllegalStateException("Parameter #"     + (fromPos + 1)     + " of method '"     + from     + "' was of the wrong type for parameter #"     + (topos + 1)     + " of method '"     + to     + "'. See cause for more info.",e);  } } protected String getResourceEntryname(Object source,@IDRes int ID) {  return getContext(source).getResources().getResourceEntryname(ID); } public abstract Context getContext(Object source);}

otto

otto Bus 是一个专为AndroID改装的Event Bus,在很多项目中都有应用.由Square开源共享.public class EventBusTest {  private static final String LOGTAG = "EventBusTest";  Bus mBus = new Bus();  public voID @R_419_5253@ {    mBus.register(this);  }  class NetworkChangedEvent {  }  @Produce  public NetworkChangedEvent sendNetworkChangedEvent() {    return new NetworkChangedEvent();  }  @Subscribe  public voID onNetworkChanged(NetworkChangedEvent event) {    Log.i(LOGTAG,"onNetworkChanged event=" + event);  }}

otto 的工作原理

使用@Produce和@Subscribe标记方法 当调用bus.register方法,去检索注册对象的标记方法,并cache映射关系 当post事件时,将事件与handler方法对应加入事件队列 抽取事件队列,然后调用handler处理

如下为对otto如何利用注解的分析

register的源码

public voID register(Object object) {  if (object == null) {   throw new NullPointerException("Object to register must not be null.");  }  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) {    //concurrent put if absent    Set<EventHandler> handlersCreation = new copyOnWriteArraySet<EventHandler>();    handlers = handlersByType.putIfAbsent(type,handlersCreation);    if (handlers == null) {      handlers = handlersCreation;    }   }   final Set<EventHandler> foundHandlers = foundHandlersMap.get(type);   if (!handlers.addAll(foundHandlers)) {    throw new IllegalArgumentException("Object already registered.");   }  }  for (Map.Entry<Class<?>,Set<EventHandler>> entry : foundHandlersMap.entrySet()) {   Class<?> type = entry.getKey();   EventProducer producer = producersByType.get(type);   if (producer != null && producer.isValID()) {    Set<EventHandler> foundHandlers = entry.getValue();    for (EventHandler foundHandler : foundHandlers) {     if (!producer.isValID()) {      break;     }     if (foundHandler.isValID()) {      dispatchProducerResultToHandler(foundHandler,producer);     }    }   }  } }

HandlerFinder源码

interface HandlerFinder { Map<Class<?>,EventProducer> findAllProducers(Object Listener); Map<Class<?>,Set<EventHandler>> findAllSubscribers(Object Listener); //otto注解查找器 HandlerFinder ANNOTATED = new HandlerFinder() {  @OverrIDe  public Map<Class<?>,EventProducer> findAllProducers(Object Listener) {   return AnnotatedHandlerFinder.findAllProducers(Listener);  }  @OverrIDe  public Map<Class<?>,Set<EventHandler>> findAllSubscribers(Object Listener) {   return AnnotatedHandlerFinder.findAllSubscribers(Listener);  } };

具体查找实现

/** This implementation finds all methods marked with a {@link Subscribe} annotation. */ static Map<Class<?>,Set<EventHandler>> findAllSubscribers(Object Listener) {  Class<?> ListenerClass = Listener.getClass();  Map<Class<?>,Set<EventHandler>> handlersInMethod = new HashMap<Class<?>,Set<EventHandler>>();  Map<Class<?>,Set<Method>> methods = SUBSCRIBERS_CACHE.get(ListenerClass);  if (null == methods) {   methods = new HashMap<Class<?>,Set<Method>>();   loadAnnotatedSubscriberMethods(ListenerClass,methods);  }  if (!methods.isEmpty()) {   for (Map.Entry<Class<?>,Set<Method>> e : methods.entrySet()) {    Set<EventHandler> handlers = new HashSet<EventHandler>();    for (Method m : e.getValue()) {     handlers.add(new EventHandler(Listener,m));    }    handlersInMethod.put(e.getKey(),handlers);   }  }  return handlersInMethod; }

以上就是关于AndroID中注解的一些总结,文章部分内容参考自 Support Annotations,希望能帮助大家对注解有基础的认识,并运用到实际的日常开发之中。

通过此文希望能帮助你彻底了解AndroID 注解机制,谢谢大家对本站的支持!

总结

以上是内存溢出为你收集整理的Android 中的注解深入探究全部内容,希望文章能够帮你解决Android 中的注解深入探究所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1148543.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-31
下一篇2022-05-31

发表评论

登录后才能评论

评论列表(0条)

    保存