源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

深入分析安卓(Android)中的注解

  • 时间:2022-08-29 21:58 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:深入分析安卓(Android)中的注解
[b]归纳而言,Android中的注解大概有以下好处[/b]       1、提高我们的开发效率       2、更早的发现程序的问题或者错误       3、更好的增加代码的描述能力       4、更加利于我们的一些规范约束       5、提供解决问题的更优解 [b]准备工作[/b] 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包.
dependencies {
  compile 'com.android.support:support-annotations:22.2.0'
}
但是如果我们已经引入了 appcompat 则没有必要再次引用 support-annotations ,因为 appcompat 默认包含了对其引用. [b]替代枚举[/b] 在最早的时候,当我们想要做一些值得限定实现枚举的效果,通常是       1、定义几个常量用于限定       2、从上面的常量选取值进行使用 一个比较描述上面问题的示例代码如下
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)
然而上面的还是有不尽完美的地方       1、setColor(COLOR_RED) 与 setColor(0) 效果一样,而后者可读性很差,但却可以正常运行       2、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中新引入的替代枚举的注解有 IntDef 和 StringDef ,这里以 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;
}
      1、声明必要的int常量       2、声明一个注解为LightColors       3、使用@IntDef修饰LightColors,参数设置为待枚举的集合       4、使用@Retention(RetentionPolicy.SOURCE)指定注解仅存在与源码中,不加入到class文件中 [b]Null相关的注解 [/b] 和Null相关的注解有两个        @Nullable 注解的元素可以是Null        @NonNull 注解的元素不能是Null 上面的两个可以修饰如下的元素        1、成员属性        2、方法参数        3、方法的返回值
@Nullable
private String obtainReferrerFromIntent(@NonNull Intent intent) {
  return intent.getStringExtra("apps_referrer");
}
NonNull检测生效的条件       1、显式传入null       2、在调用方法之前已经判断了参数为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
}
[b]区间范围注解[/b] 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)
[b]长度以及数组大小限制[/b] 限制字符串的长度
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) {
}
[b]权限相关[/b] 在Android中,有很多场景都需要使用权限,无论是Marshmallow之前还是之后的动态权限管理.都需要在manifest中进行声明,如果忘记了,则会导致程序崩溃. 好在有一个注解能辅助我们避免这个问题.使用RequiresPermission注解即可.
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
  public void changeWallpaper(Bitmap bitmap) throws IOException {
}
[b]资源注解[/b] 在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 [b]Color值限定[/b] 上面部分提到了 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资源值时,就会得到错误的提示. [b]CheckResult[/b] 这是一个关于返回结果的注解,用来注解方法,如果一个方法得到了结果,却没有使用这个结果,就会有错误出现,一旦出现这种错误,就说明你没有正确使用该方法。
@CheckResult
public String trim(String s) {
  return s.trim();
}
[b]线程相关[/b] 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
    }
  };

@UiThread
public 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方法没有线程注解,则不提示. [b]CallSuper[/b] 重写的方法必须要调用super方法 使用这个注解,我们可以强制方法在重写时必须调用父类的方法 比如Application的 onCreate , onConfigurationChanged 等. [b]Keep[/b] 在Android编译生成APK的环节,我们通常需要设置minifyEnabled为true实现下面的两个效果       1、混淆代码       2、删除没有用的代码 但是出于某一些目的,我们需要不混淆某部分代码或者不删除某处代码,除了配置复杂的Proguard文件之外,我们还可以使用@Keep注解 .
@Keep
public static int getBitmapWidth(Bitmap bitmap) {
  return bitmap.getWidth();
}
[b]ButterKnife [/b] ButterKnife是一个用来绑定View,资源和回调的提高效率的工具.作者为Jake Wharton. ButterKnife的好处        1、使用BindView替代繁琐的findViewById和类型转换        2、使用OnClick注解方法来替换显式声明的匿名内部类       3、使用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...
 }
}
[b]ButterKnife工作原理[/b] 以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时,会根据注解自动生成两个类,这里为 [code]MainActivity_ViewBinder.class [/code]和 [code]MainActivity_ViewBinding.class[/code] 2.当我们调用 [code]ButterKnife.bind(this); [/code]时,会查找当前类对应的[code]ViewBinder[/code]类,并调用[code]bind[/code]方法,这里会调用到 [code]MainActiivty_ViewBinder.bind [/code]方法. 3.[code]MainActiivty_ViewBinder.bind[/code]方法实际上是调用了[code]findViewById[/code]然后在进行类型转换,赋值给[code]MainActivity[/code]的[code]myTextView[/code]属性 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_ViewBinder
Warning: Binary file MainActivity_ViewBinder contains com.example.admin.androidannotationsample.MainActivity_ViewBinder
Compiled 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_ViewBinding
Warning: Binary file MainActivity_ViewBinding contains com.example.admin.androidannotationsample.MainActivity_ViewBinding
Compiled 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, @IdRes int id, 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, @IdRes int id, String who,
   Class<T> cls) {
  View view = findRequiredView(source, id, who);
  return castView(view, id, who, cls);
 }

 public final <T> T castView(View view, @IdRes int id, String who, 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);
}
[b]Otto[/b] Otto Bus 是一个专为Android改装的Event Bus,在很多项目中都有应用.由Square开源共享.
public class EventBusTest {
  private static final String LOGTAG = "EventBusTest";
  Bus mBus = new Bus();

  public void test() {
    mBus.register(this);
  }

  class NetworkChangedEvent {

  }

  @Produce
  public NetworkChangedEvent sendNetworkChangedEvent() {
    return new NetworkChangedEvent();
  }


  @Subscribe
  public void onNetworkChanged(NetworkChangedEvent event) {
    Log.i(LOGTAG, "onNetworkChanged event=" + event);
  }
}
[b]Otto 的工作原理[/b]       1、使用@Produce和@Subscribe标记方法       2、当调用bus.register方法,去检索注册对象的标记方法,并cache映射关系       3、当post事件时,将事件与handler方法对应加入事件队列       4、抽取事件队列,然后调用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;
 }
[b]总结[/b] 以上就是关于Android中注解的一些总结,文章部分内容参考自 Support Annotations ,希望能帮助大家对注解有基础的认识,并运用到实际的日常开发之中。如有有疑问欢迎大家留言讨论。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部