安卓注解编程 Support Annotations 详解

http://tools.android.com/tech-docs/support-annotations


Support Annotations

As of version 19.1 of the Android support library, there is a new annotations package which includes a number of useful metadata annotations you can decorate your own code with, to help catch bugs.  The support library itself has also been annotated with these annotations, so as a user of the support library, Android Studio will already check your code and flag potential problems based on these annotations. As of version 22.2 of the support library, there are an additional 13 new annotations you can also use.

Using the Library

The annotations are not included by default; they are shipped as a separate library. (The support library is now made up of a number of smaller libraries: v4-support, appcompat, gridlayout, mediarouter, and so on.)

(If you're using the appcompat library, you  already have access to the annotations, because appcompat itself depends on it.)

The easiest way to add the annotations is to just open the Project Structure Dialog. First select your module on the left, then on the right select the Dependencies tab, click on the + button at the bottom of the panel, select Library Dependency, and assuming you have the Android Support Repository installed in your SDK, the support annotation library will be one of the prepopulated links in the list you can just click on (it's the first element in the list) :

Hit OK to finish editing the project structure. This will edit your build.gradle file as follows, which you can also do by hand:
dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}

For Android application and Android library modules (e.g. when you're using  apply plugin:  'com.android.application' or  'com.android.library') that's all you need to do. If you'd like to use these annotations in a Java-only module, you'll also need to include the SDK repository explicitly, since the support libraries are not available from jcenter (the Android Gradle plugins automatically include these for dependencies, but the Java plugin does not.)

repositories {
   jcenter()
   maven { url '<your-SDK-path>/extras/android/m2repository' }
}

Enforcing Annotations
When you're using Android Studio and IntelliJ, the IDE will flag calls where you are passing the wrong type of parameter to methods annotated with these annotations. 

As of version 1.3.0-beta1 of the Gradle plugin, and with the Android M Preview platform tools installed, these checks are also checked/enforced on the command line by the "lint" gradle task. This is useful if you want to flag problems as part of your continuous integration server.  NOTE: That does not incude the nullness annotations. All the other annotations in this document are checked by lint.)

Nullness Annotations
The  @Nullable annotation can be used to indicate that a given parameter or return value can be null. 
Similarly, the  @NonNull annotation can be used to indicate that a given parameter (or return value) can  not be null.

If a local variable is known to be null (for example because some earlier code checked whether it was null), and you pass that as a parameter to a method where that parameter is marked as  @NonNull, the IDE will warn you that you have a potential crash.

Example from FragmentActivity in the v4 support library:

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
...
    
    /**
     * Add support for inflating the &lt;fragment> tag.
     */
    @Nullable
    @Override
    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        ...

(If you use the Analyze > Infer Nullity action, or if you type in @NotNull instead of @NonNull, the IDE may offer to attach the IntelliJ annotations. See the "IntelliJ Annotations" section at the bottom for more.)

Note that @NonNull and @Nullable are not opposites: there is a third option: unspecified. When you don’t specify @NonNull or @Nullable, the tools are not certain, and won’t flag uses of this API. 

Initially, we annotated findViewById as @Nullable, and technically, that’s correct: findViewById can return null. But it won’t return null if you know what you’re doing (if you pass it an id that is known to exist in the given context). When we annotated it @Nullable, it meant that a lot of code (which did not null check every findViewById call) would light up in the source editor with warnings.  Only use @Nullable on a return value if you’re prepared to say that every use of the method should be explicitly null checked.  As a rule of thumb: Take a look at existing “good code” (e.g. code reviewed production code) to see how the API is being used. If that code is null checking the results, you should probably mark the method @Nullable.

Resource Type Annotations

Resources in Android are typically passed around as integers. That means code which expects a parameter to refer to a drawable can easily be passed a reference to a string; they are both of type  int and the compiler is happy.

The resource type annotations allow you to add some type checking on top of this. By annotating an int parameter with  @StringRes for example, passing anything but a  R.string reference will be flagged by the IDE:
Example from ActionBar:
import android.support.annotation.StringRes;
...
    public abstract void setTitle(@StringRes int resId);

There are many different resource type annotations: one for each Android resource type:
@StringRes@DrawableRes@ColorRes@InterpolatorRes, and so on. In general if you have a resource of type "foo", the corresponding resource type annotation is FooRes.

In addition, there is a special resource annotation called  @AnyRes. This one is used where the specific type of resource is not known, but it must be a resource type. For example, in the framework, this is used in  Resources#getResourceName(@AnyRes int resId) such that you can call  getResources().getResourceName(R.drawable.icon) and getResources().getResourceName(R.string.app_name), but not  getResources().getResourceName(42).

Note that if your API supports multiple resource types, you can put more than one of these annotations on your parameter.

IntDef/StringDef: Typedef Annotations
In addition to passing ints around to represent resource references, ints are often passed around where they are intended to be used as "enums" instead.

The @IntDef annotation lets you basically create a "typedef", where you create another annotation which represents the valid integer constants that you expect, and then you decorate your API with this typedef annotation. 

Here's an example from the appcompat support library:

import android.support.annotation.IntDef;
...
public abstract class ActionBar {
    ...
    @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
    @Retention(RetentionPolicy.SOURCE)
    public @interface NavigationMode {}

    public static final int NAVIGATION_MODE_STANDARD = 0;
    public static final int NAVIGATION_MODE_LIST = 1;
    public static final int NAVIGATION_MODE_TABS = 2;

    @NavigationMode
    public abstract int getNavigationMode();

    public abstract void setNavigationMode(@NavigationMode int mode);

The non-bold parts above was the existing API. We create a new annotation (that's what the above line " public @interface NavigationMode {}" does) and then we annotate the annotation itself with  @IntDef, and we specify the constants that are the valid constants for return values or parameters. We also add the line " @Retention(RetentionPolicy.SOURCE)" to tell the compiler that usages of the new typedef annotation do not need to be recorded in .class files.

With this annotation, the IDE will flag cases where you pass a different integer in to the method, or check the return value for constants other than the specified ones.


You can also specify that an integer can be a  flag; that means client code will be allowed to pass in multiple constants combined using |, & etc:
    @IntDef(flag=true, value={
            DISPLAY_USE_LOGO,
            DISPLAY_SHOW_HOME,
            DISPLAY_HOME_AS_UP,
            DISPLAY_SHOW_TITLE,
            DISPLAY_SHOW_CUSTOM
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DisplayOptions {}

Finally, there is a String-version of this annotation, @StringDef, which is used for the same purpose but for strings. This is not nearly as common, but it serves the very useful purpose of defining the allowed set of constants you can pass to Activity#getSystemService.

For more details on the typedef annotations, see

(This is closely based on IntelliJ's MagicConstant annotation; you can find more information about that annotation here:  http://blog.jetbrains.com/idea/2012/02/new-magic-constant-inspection/ )

Threading Annotations: @UiThread, @WorkerThread, ...

(Support library 22.2 and later.)

If your method should only be called from a specific type of thread, you can annotate it with one of these 4 annotations:
  • @UiThread
  • @MainThread
  • @WorkerThread
  • @BinderThread

If all methods in a class share the same threading requirement, you can annotate the class itself. This is the case for android.view.View for example, which is annotated with @UiThread.

A good example for a usage of the threading annotations is AsyncTask:
 
    @WorkerThread
    protected abstract Result doInBackground(Params... params);

    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

If you attempt to call onProgressUpdate from a method which overrides doInBackground, or if you call into any View method, the tools will now flag this as an error:

@UiThread or @MainThread?

There is one and only one main thread in the process. That's the @MainThread.  That thread is  also a @UiThread. This thread is what the main window of an activity runs on, for example.  However it is also possible for applications to create other threads on which they run different windows.  This will be very rare; really the main place this distinction matters is the system process. Generally you'll want to annotate methods associated with the life cycle with @MainThread, and methods associated with the view hierarchy with @UiThread. Since the @MainThread is a @UiThread, and since it's usually the case that a @UiThread is the @MainThread, the tools (lint, Android Studio, etc) treat these threads as interchangeable, so you can call @UiThread methods from @MainThread methods and vice versa.

RGB Color Integers
You can specify @ColorRes when your API expects a color resource, but that won’t help when you have the opposite scenario: your API doesn’t expect a color resource id, but an actual RGB or ARGB color integer.

In that case, you can use the @ColorInt annotation, which states that an integer is expected to represent a color integer:

    public void setTextColor(@ColorInt int color)

With this, lint will flag incorrect code which passes a color id rather than a color:

Value Constraints: @Size, @IntRange, @FloatRange

If your parameter is a float or double, and must be in a certain range, you can use the  @FloatRange annotation:

public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {

If somebody is under the impression that your API takes a value from 0 - 255 and tries to call setAlpha(128), the tools will catch this:
(You can also specify whether the starting and ending values are inclusive or exclusive.)

Similarly, if your parameter is an int or long, you can use the  @IntRange annotation to constrain the value to a particular integer range:

public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }

This annotation is most useful when applied to parameters where users are likely to get the range wrong, such as the example above where some APIs take alpha to be a value from 0-255 and others a float from 0-1.

Finally, for arrays, collections and Strings, you can use the  @Size parameter to specify constraints on the size of the collection (or in the case of Strings, the length of the strings). 

This lets you say for example that a 
  • collection must be non-empty: @Size(min=1)
  • that a String must be at most 23 characters: @Size(max=23)
  • that an array must have precisely 2 elements: @Size(2)
  • that an array must have a size that is a multiple of 2 (e.g. in the graphics APIs that take an array of points x/y positions:@Size(multiple=2)

Permissions: @RequiresPermission

If your method call requires the caller to hold precisely one permission, you can use the  @RequiresPermission annotation like this:

@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;

If you require at least one from a set of permissions, you can use the anyOf attribute:

@RequiresPermission(anyOf = {
    Manifest.permission.ACCESS_COARSE_LOCATION,
    Manifest.permission.ACCESS_FINE_LOCATION})
public abstract Location getLastKnownLocation(String provider);

If you require multiple permissions, use the allOf attribute:

@RequiresPermission(allOf = {
    Manifest.permission.READ_HISTORY_BOOKMARKS, 
    Manifest.permission.WRITE_HISTORY_BOOKMARKS})
public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) {

For permissions on intents, place the permission requirement on the String field which defines the intent constant (these normally are already annotated with @SdkConstant) :
@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
            "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";

For permissions on content providers, you probably need separate permissions for read and write access, so wrap each permission requirement in a @Read or @Write annotation:

@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");

Overriding Methods: @CallSuper

If your API allows callers to override a method, but you need your own method to be invoked from any overriding methods as well, you can annotate the method with @CallSuper:

    @CallSuper
    protected void onCreate(@Nullable Bundle savedInstanceState) {

With this, the tools will immediately flag overriding code that fails to invoke the super method:
(There was a bug in this lint check when running inside the IDE in Android Studio 1.3 Preview 1 which caused it to report error on perfectly valid overriding methods. That bug has been fixed in Preview 2, now available in the canary channel.)

Return Values: @CheckResult

If your method is returning a value that callers are expected to do something with, annotate the method with @CheckResult.

You don’t need to go and annotate every single non-void method with this annotation. It’s main purpose is to help with cases where users of the API may be confused and may think that the method has a side effect.

For example, many new Java developers are confused by String.trim(), believing that calling that method will cause the string to be changed to remove whitespace. If that method is annotated with @CheckResult, the tools will flag uses of String.trim() where the caller does not actually do something with the result of the trim() call.

In Android, one API which has already been annotated with @CheckResult is Context#checkPermission:

    @CheckResult(suggest="#enforcePermission(String,int,int,String)")
    public abstract int checkPermission(@NonNull String permission, int pid, int uid);

This is important because some developers were calling context.checkPermission and believing that they had enforced a permission -- but that method only checks and returns a result for whether it was successful. Anyone calling that method without looking at the return value probably intended to call Context#enforcePermission instead.

@VisibleForTesting

This annotation lets you declare that a class, method or field is more visible than necessary, in order to be able to access it from tests.

@Keep

We've also added @Keep to the support annotations.  Note however that that annotation hasn't been hooked up to the Gradle plugin yet(though it's  in progress.) When finished this will let you annotate methods and classes that should be retained when minimizing the app.

Using Annotations in your Own Libraries

If you annotate your own libraries with these annotations, and use Gradle to build an AAR artifact, at build time the Android Gradle plugin will extract annotation information and ship it inside your AAR file for use by clients of your library. You can take a look at the annotations.zip file inside the AAR where the information is recorded; this is using IntelliJ's external annotations XML format. This is necessary because .class files cannot contain enough information about annotations to handle the @IntDef information above; note that we need to record a reference to the constant itself, not its value. The Android Gradle plugin will run the extract annotations task as part of the build if (and only if) your project depends on the annotations support library. (Note that only the source-retention annotations are placed in the .aar file; the class level retentions are kept in classes.jar.)

IntelliJ Annotations

IntelliJ, which Android Studio is built on top of, has its own set of annotations; the IntDef analysis is reusing the MagicConstant analysis code, and the IntelliJ null analysis uses a configurable set of null annotations. If you run the Analyze > Infer Nullity... action, it will try to figure out null constraints automatically and add them. That inspection will sometimes insert IntelliJ annotations instead. You can either search/replace these with the android support ones, or you can use the IntelliJ annotations directly. Add the following Gradle dependency, either in build.gradle or via the Project Structure Dialog's Dependencies panel:

dependencies {
    compile 'com.intellij:annotations:12.0'
}


发布了56 篇原创文章 · 获赞 3 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/haoxuezhe1988/article/details/49639977