深入分析安卓(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、从上面的常量选取值进行使用

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

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文件中

Null相关的注解

和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
}

区间范围注解

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

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

@CheckResult
public 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
    }
  };

@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方法没有线程注解,则不提示.

CallSuper

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

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

Keep

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

1、混淆代码

2、删除没有用的代码

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

@Keep
public static int getBitmapWidth(Bitmap bitmap) {
  return bitmap.getWidth();
}

ButterKnife

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...
 }
}

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然后在进行类型转换,赋值给MainActivitymyTextView属性

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

Otto

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

Otto 的工作原理

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;
 }

总结

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

(0)

相关推荐

  • Android注解框架对比分析

    Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解). 开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已.好在市面上有所谓的注解框架可以帮助开发者简化一些过程.比较流行的有butterknife

  • Android AOP 注解详解及简单使用实例(三)

    Android  注解 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.简介 在Android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 可以更加方便快捷 安全有效的编写Java代码.谷歌出的support-annotations这个库 就是主要干这个的. 和反射一起 提供一些类似于spring 可配置的功能,方便简洁. 二

  • Android注解ButterKnife的基本使用

    ButterKnife的最新版本是8.4.0. 首先,需要导入ButterKnife的jar包. 在AndroidStudio中,File->Project Structure->Dependencies->Library dependency 搜索butterknife即可,第一个就是. 另外一种就是直接在build:grade(app)dependencies里添加: compile 'com.jakewharton:butterknife:8.4.0' annotationProc

  • Android AOP之注解处理解释器详解(二)

    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.提取Annotation信息 当开发者使用了Annotation修饰了类.方法.Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息.这些处理提取和处理Annotation的代码统称为APT(Annotation

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

  • Android 中的注解深入探究

    本文系GDG Android Meetup分享内容总结文章 注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个

  • Android中封装SDK时常用的注解总结

    前言 在工作中我们经常需要将功能模块封装成库供合作厂商调用, 如何写好一个健壮的Android Library有很多讲究,使用注解可以对SDK暴露给开发者的接口做出一些限制,从而尽可能地避免开发者错误地使用API. 下面我们介绍几种封装SDK时常用到的注解,需要的朋友们可以参考学习. 一.IntDef与StringDef 我们有时候会使用int常量或者String常量来代替枚举, 特别在你编写SDK的时候,你可以通过IntDef或者StringDef来限制接口可接受的参数. 比如,有一个 dis

  • Android 中的注解详细介绍

    注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies

  • Android注解使用之ButterKnife 8.0详解

    前言: App项目开发大部分时候还是以UI页面为主,这时我们需要调用大量的findViewById以及setOnClickListener等代码,控件的少的时候我们还能接受,控件多起来有时候就会有一种想砸键盘的冲动.所以这个时候我们想着可以借助注解的方式让我们从这种繁重的工作中脱离出来,也让代码变得更加简洁,便于维护,今天主要学习一下只专注View.Resource.Action注解框架ButterKnife. ButterKnife介绍 ButterKnife是一个专注于Android系统的V

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

    归纳而言,Android中的注解大概有以下好处 1.提高我们的开发效率 2.更早的发现程序的问题或者错误 3.更好的增加代码的描述能力 4.更加利于我们的一些规范约束 5.提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies { compile 'com.android.support:support-annotations:22.2.0' } 但是如果我们已经引入了 app

  • 总结安卓(Android)中常用的跳转工具

    话不多说了,直接上代码,这篇文章包含了一些基本的并且常用的跳转工具,一起来看看吧. 首先,这是需要的对应的权限. <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="

  • 安卓(Android)开发之自定义饼状图

    先来看看效果图 先分析饼状图的构成,非常明显,饼状图就是一个又一个的扇形构成的,每个扇形都有不同的颜色,对应的有名字,数据和百分比. 经以上信息可以得出饼状图的最基本数据应包括:名字 数据值 百分比 对应的角度 颜色. 用户关心的数据 : 名字 数据值 百分比 需要程序计算的数据: 百分比 对应的角度 其中颜色这一项可以用户指 public class PieData { private String name; // 名字 private float value; // 数值 private

  • Android中pendingIntent与Intent的深入分析

    Android中pendingIntent的深入分析 pendingIntent字面意义:等待的,未决定的Intent. 要得到一个pendingIntent对象,使用方法类的静态方法 getActivity(Context, int, Intent, int),getBroadcast(Context, int, Intent, int),getService(Context, int, Intent, int)  分别对应着Intent的3个行为,跳转到一个activity组件.打开一个广播

  • android中UI主线程与子线程深入分析

    本文较为深入的分析了android中UI主线程与子线程.分享给大家供大家参考.具体如下: 在一个Android 程序开始运行的时候,会单独启动一个Process.默认的情况下,所有这个程序中的Activity或者Service(Service和 Activity只是Android提供的Components中的两种,除此之外还有Content Provider和Broadcast Receiver)都会跑在这个Process. 一个Android 程序默认情况下也只有一个Process,但一个Pr

  • Android中图片占用内存的深入分析

    目录 前言 一.图片占用内存与宽.高.色彩模式的关系 二.图片占用内存与存放文件夹的关系 三.从文件中加载图片和从网络加载图片占用内存 四.色彩模式 五.总结 前言 Android 在加载图片的时候一定会考虑到的一个点就是如何防止 OOM,那么一张图片在加载的时候到底会占用多少内存呢?有哪些因素会影响占用的内存呢?知道了这些,我们才能知道可以从哪些点去优化,从而避免 OOM. 一.图片占用内存与宽.高.色彩模式的关系 首先我们准备一张 1920*1080 的图片: 然后我使用的测试机是 Redm

  • Android 中使用 ViewPager实现屏幕页面切换和页面轮播效果

    之前关于如何实现屏幕页面切换,写过一篇博文<Android中使用ViewFlipper实现屏幕切换>,相比ViewFlipper,ViewPager更适用复杂的视图切换,而且Viewpager有自己的adapter,这也让其适应复杂对象,实现数据的动态加载. ViewPager是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api.而viewpager就是其中之一,利用它,我们可以做很多事情,从最简单的导航,到页面菜单等等. 下面我们就展示下Vie

  • Android 中构建快速可靠的 UI 测试

    前言 让我一起来看看 Iván Carballo和他的团队是如何使用Espresso, Mockito 和Dagger 2 编写250个UI测试,并且只花了三分钟就运行成功的. 在这篇文章中,我们会探索如何使用Mockito(译者注:Mockito是java编写的一个单元测试框架),Dagger 2 去创建快速可靠的Android UI测试.如果你正在开始编写Android中的UI 测试或者希望改善已有测试性能的开发者,那么这篇文章值得一读. 我第一次在安卓应用中使用UI自动化测试是在几年前使用

随机推荐