본문 바로가기
User Guide

Improve Code Inspection with Annotations (12)

by 각종 잡상식 지식 모음 2016. 6. 2.
반응형

Improve Code Inspection with Annotations

Lint와 같은 코드 검사 도구를 사용하면 문제를 발견하고 코드를 개선할 수 있지만, 검사 도구는 많은 것을 추론만 할 수 있습니다.
예를 들어, 안드로이드 리소스 ids는, 문자열, 그래픽, 색상 및 기타 리소스 타입을 식별하기 위하여 int를 사용하므로, 검사 도구를 사용하면 색상을 지정해야 문자열 리소스를 언제 지정했는지 알려줄 수 없습니다.
이 상황은 코드검사를 사용한다 할지라도 app이 잘못 렌더링하거나 또는 전혀 실행되지 않을 수 있음을 의미합니다.

Using code inspections tools such as Lint can help you find problems and improve your code, but inspection tools can only infer so much. Android resource ids, for example, use an int to identify strings, graphics, colors and other resource types, so inspection tools cannot tell when you have specified a string resource where you should have specified a color. This situation means that your app may render incorrectly or fail to run at all, even if you use code inspection.

Annotations allow you to provide hints to code inspections tools like Lint, to help detect these, more subtle code problems. They are added as metadata tags that you attach to variables, parameters, and return values to inspect method return values, passed parameters, and local variables and fields. When used with code inspections tools, annotations can help you detect problems, such as null pointer exceptions and resource type conflicts.

For more information on enabling and running Lint inspections, see Improving Your Code with lint.

Android supports a variety of annotations for insertion in the methods, parameters, and return values in your code, for example:

@Nullable
Can be null.
@NonNull
Cannot be null.
@StringRes
References a R.string resource.
@DrawableRes
References a Drawable resource.
@ColorRes
References a Color resource.
@InterpolatorRes
References a Interpolator resource.
@AnyRes
References any type of R. resource.
@UiThread
Calls from a UI thread.

For a complete list of the supported annotations, either examine the contents of the Support-Annotations library or use the auto-complete feature to display the available options for the import android.support.annotation. statement. The SDK Manager packages the Support-Annotations library in the Android Support Repository for use with Android Studio and in the Android Support Library for use with other Android development tools.

Adding Basic Annotations


To add annotations to your code, first add a dependency to the Support-Annotations library:

  1. Select File > Project Structure.
  2. In the Project Structure dialog, select the desired module, click the Dependencies tab.
  3. Click the  icon to include a Library dependency.
  4. In the Choose Library Dependency dialog, select support-annotations and click Ok.

The build.gradle file is updated with the support-annotations dependency.

You can also manually add this dependency to your build.gradle file, as shown in the following example.

dependencies {
    compile
'com.android.support:support-annotations:23.3.0'
}

The Support-Annotations library is decorated with the supported annotations so using this library's methods and resources automatically checks the code for potential problems.

If you include annotations in a library and use the Android Plugin for Gradle to build an Android ARchive (AAR) artifact of that library, the annotations are included as part of the artifact in XML format in the annotations.zip file.

To start a code inspection from Android Studio, which includes validating annotations and automatic Lint checking, select Analyze > Inspect Code from the menu options. Android Studio displays conflict messages throughout the code to indication annotation conflicts and suggest possible resolutions.

Adding Nullness Annotations


Add @Nullable and @NonNull annotations to check the nullness of a given variable, parameter, or return value. For example, if a local variable that contains a null value is passed as a parameter to a method with the @NonNull annotation attached to that parameter, building the code generates a warning indicating a non-null conflict.

This example attaches the @NonNull annotation to the context and attrs parameters to check that the passed parameter values are not null.

import android.support.annotation.NonNull;
...

   
/** Add support for inflating the <fragment> tag. */
   
@NonNull
   
@Override
   
public View onCreateView(String name, @NonNull Context context,
     
@NonNull AttributeSet attrs) {
     
...
     
}
...

Nullability Analysis

Android Studio supports running a nullability analysis to automatically infer and insert nullness annotations in your code. A nullability analysis scans the contracts throughout the method hierarchies in your code to detect:

  • Calling methods that can return null
  • Methods that should not return null
  • Variables, such as fields, local variables, and parameters, that can be null
  • Variables, such as fields, local variables, and parameters, that cannot hold a null value

The analysis then automatically inserts the appropriate null annotations in the detected locations.

To run a nullability analysis in Android Studio, select Analyze > Infer Nullity. Android Studio inserts the Android @Nullable and @NonNull annotations in detected locations in your code. After running a null analysis, it's good practice to verify the injected annotations.

Note: When adding nullness annotations, autocomplete may suggest the IntelliJ @Nullable and @NotNull annotations instead of the Android null annotations and may auto-import the corresponding library. However, the Android Studio Lint checker only looks for the Android null annotations. When verifying your annotations, confirm that your project uses the Android null annotations so the Lint checker can properly notify you during code inspection.

Adding Resource Annotations


Validating resource types can be useful as Android references to resources, such as Drawables and R.string resources, are passed as integers. Code that expects a parameter to reference a specific type of resource, for example Drawables, can be passed the expected reference type of int, but actually reference a different type of resource, such as a R.string resource.

For example, add @StringRes annotations to check that a resource parameter contains a R.string reference. During code inspection, the annotation generates a warning if a R.string reference is not passed in the parameter.

This example attaches the @StringRes annotation to the resId parameter to validate that it is really a string resource.

import android.support.annotation.StringRes;
...
   
public abstract void setTitle(@StringRes int resId);
   
...

Annotations for the other resource types, such as @DrawableRes@DimenRes@ColorRes, and @InterpolatorRes can be added using the same annotation format and run during the code inspection.

Adding Thread Annotations


Thread annotations check if a method is called from a specific type of thread. The following thread annotations are supported:

  • @UiThread
  • @MainThread
  • @WorkerThread
  • @BinderThread

Note: The @MainThread and the @UiThread annotations are interchangeable so methods calls from either thread type are allowed for these annotations.

If all methods in a class share the same threading requirement, you can add a single thread annotation to the class to verify that all methods in the class are called from the same type of thread.

A common use of the thread annotation is to validate method overrides in the AsyncTask class as this class performs background operations and publishes results only on the UI thread.

Adding Value Constraint Annotations


Use the @IntRange@FloatRange, and @Size annotations to validate the values of passed parameters.

The @IntRange annotation validates that the parameter value is within a specified range. The following example ensures that the alpha parameter contains an integer value from 0 to 255:

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

The @FloatRange annotation checks that the parameter value is within a specified range of floating point values. The following example ensures that thealpha parameter contains a float value from 0.0 to 1.0:

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

The @Size annotation checks the size of a collection or array, as well as the length of a string. For example, use the @Size(min=1) annotation to check if a collection is not empty, and the @Size(2) annotation to validate that an array contains exactly two values. The following example ensures that thelocation array contains at least one element:

int[] location = new int[3];
button
.getLocationOnScreen(@Size(min=1) location);

Adding Permission Annotations


Use the @RequiresPermission annotation to validate the permissions of the caller of a method. To check for a single permission from a list the valid permissions, use the anyOf attribute. To check for a set of permissions, use the allOf attribute. The following example annotates the setWallpapermethod to ensure that the caller of the method has the permission.SET_WALLPAPERS permission.

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

This example requires the caller of the copyFile() method to have both read and write permissions to external storage:

@RequiresPermission(allOf = {
   
Manifest.permission.READ_EXTERNAL_STORAGE,
   
Manifest.permission.WRITE_EXTERNAL_STORAGE})
public static final void copyFile(String dest, String source) {
   
...
}

Adding CheckResults Annotations


Use the @CheckResults annotation to validate that a method's result or return value is actually used. The following example annotates thecheckPermissions method to ensure the return value of the method is actually referenced. It also names the enforcePermission method as a method to be suggested to the developer as a replacement.

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

@StringDef

Adding CallSuper Annotations


Use the @CallSuper annotation to validate that an overriding method calls the super implementation of the method. The following example annotates the onCreate method to ensure that any overriding method implementations call super.onCreate().

@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}

Creating Enumerated Annotations


Use the @IntDef and @StringDef annotations so you can create enumerated annotations of integer and string sets to validate other types of code references, such as passing references to a set of constants.

The following example illustrates the steps to create an enumerated annotation that ensures a value passed as a method parameter references one of the defined constants.

import android.support.annotation.IntDef;
...
public abstract class ActionBar {
   
...
   
//Define the list of accepted constants
   
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})

   
//Tell the compiler not to store annotation data in the .class file
   
@Retention(RetentionPolicy.SOURCE)

   
//Declare the NavigationMode annotation
   
public @interface NavigationMode {}

   
//Declare the constants
   
public static final int NAVIGATION_MODE_STANDARD = 0;
   
public static final int NAVIGATION_MODE_LIST = 1;
   
public static final int NAVIGATION_MODE_TABS = 2;

   
//Decorate the target methods with the annotation
   
@NavigationMode
   
public abstract int getNavigationMode();

   
//Attach the annotation
   
public abstract void setNavigationMode(@NavigationMode int mode);

When you build this code, a warning is generated if the mode parameter does not reference one of the defined constants (NAVIGATION_MODE_STANDARD,NAVIGATION_MODE_LIST, or NAVIGATION_MODE_TABS).

You can also define an annotation with a flag to check if a parameter or return value references a valid pattern. This example creates theDisplayOptions annotation with a list of valid DISPLAY_ constants.

import android.support.annotation.IntDef;
...

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

...

When you build code with an annotation flag, a warning is generated if the decorated parameter or return value does not reference a valid pattern.

반응형

댓글