Добавление аннотаций для инспекций кода

Инструменты проверки кода, такие как lint, помогают находить проблемы и улучшать код, но их возможности ограничены. Например, идентификаторы ресурсов Android используют int для обозначения строк, графики, цветов и других типов ресурсов, поэтому инструменты проверки не могут определить, когда вы указали строковый ресурс вместо цвета. В этом случае приложение может отображаться некорректно или вообще не работать, даже если вы используете проверку кода.

Аннотации позволяют предоставлять подсказки инструментам проверки кода, таким как lint, чтобы помочь обнаружить эти более тонкие проблемы с кодом. Аннотации добавляются в виде тегов метаданных, которые вы прикрепляете к переменным, параметрам и возвращаемым значениям, чтобы проверять возвращаемые значения методов, переданные параметры, локальные переменные и поля. В сочетании с инструментами проверки кода аннотации помогают выявлять такие проблемы, как исключения нулевого указателя и конфликты типов ресурсов.

Android поддерживает различные аннотации через библиотеку аннотаций Jetpack. Доступ к библиотеке можно получить через пакет androidx.annotation.

Примечание. Если модуль зависит от инструмента обработки аннотаций, для добавления этой зависимости необходимо использовать конфигурацию kapt или ksp для Kotlin или annotationProcessor для Java.

Как добавить аннотации в проект

Чтобы включить аннотации в проекте, добавьте зависимость androidx.annotation:annotation в библиотеку или приложение. Все добавленные аннотации проверяются при проверке кода или выполнении задачи lint.

Добавьте зависимость библиотеки Jetpack Annotations

Библиотека Jetpack Annotations опубликована в репозитории Google Maven. Чтобы добавить библиотеку Jetpack Annotations в проект, включите следующую строку в блок dependencies файла build.gradle или build.gradle.kts:

Kotlin

dependencies {
    implementation("androidx.annotation:annotation:1.11.0")
}

Яркий

dependencies {
    implementation 'androidx.annotation:annotation:1.11.0'
}
Затем на панели инструментов или в появившемся уведомлении о синхронизации нажмите Синхронизировать.

Если вы используете аннотации в собственном модуле библиотеки, они включаются в состав артефакта Android Archive (AAR) в формате XML в файле annotations.zip. Добавление зависимости androidx.annotation не создает зависимостей для пользователей вашей библиотеки.

Примечание. Если вы используете другие библиотеки Jetpack, возможно, вам не нужно добавлять зависимость androidx.annotation. Поскольку многие другие библиотеки Jetpack зависят от библиотеки аннотаций, у вас уже может быть доступ к аннотациям.

Полный список аннотаций, включенных в репозиторий Jetpack, можно найти в справочнике по библиотеке аннотаций Jetpack. Также можно использовать функцию автозаполнения, чтобы посмотреть доступные варианты для оператора import androidx.annotation..

Запуск проверок кода

Чтобы запустить проверку кода в Android Studio, которая включает проверку аннотаций и автоматическую проверку lint, выберите в меню Analyze (Анализ) > Inspect Code (Проверить код). Android Studio показывает сообщения о конфликтах, чтобы предупредить о возможных проблемах, когда код конфликтует с аннотациями, и предлагает возможные решения.

Вы также можете принудительно применить аннотации, запустив задачу lint с помощью командной строки. Хотя это может быть полезно для выявления проблем с сервером непрерывной интеграции, задача lint не обеспечивает соблюдение аннотаций nullness (описанных в следующем разделе). Это делает только Android Studio. Подробнее о том, как включить и запустить проверку lint…

Хотя конфликты аннотаций вызывают предупреждения, они не мешают компиляции приложения.

Аннотации Nullness

Аннотации Nullness могут быть полезны в коде Java для принудительного определения того, могут ли значения быть нулевыми. Они менее полезны в коде Kotlin, поскольку в Kotlin есть встроенные правила допустимости значений NULL, которые применяются во время компиляции.

Добавьте аннотации @Nullable и @NonNull, чтобы проверить, является ли переменная, параметр или возвращаемое значение нулевым. Аннотация @Nullable указывает на переменную, параметр или возвращаемое значение, которое может быть нулевым. @NonNull указывает на переменную, параметр или возвращаемое значение, которое не может быть нулевым.

Например, если локальная переменная, содержащая нулевое значение, передается в качестве параметра методу с аннотацией @NonNull, прикрепленной к этому параметру, при сборке кода генерируется предупреждение, указывающее на ненулевой конфликт. Кроме того, попытка сослаться на результат метода, помеченного аннотацией @Nullable, без предварительной проверки того, является ли результат нулевым, генерирует предупреждение о нулевом значении. Используйте @Nullable для возвращаемого значения метода, только если при каждом использовании метода необходимо явно проверять значение на null.

В следующем примере показано, как работает допустимость значения NULL. В примере кода на Kotlin аннотация @NonNull не используется, поскольку она автоматически добавляется в сгенерированный байт-код, когда указан тип, не допускающий значения NULL. В примере на Java используется аннотация @NonNull для параметров context и attrs, чтобы проверить, что переданные значения параметров не равны null. Также проверяется, что сам метод onCreateView() не возвращает значение null:

Kotlin

...
    /** Annotation not used because of the safe-call operator(?)**/
    override fun onCreateView(
            name: String?,
            context: Context,
            attrs: AttributeSet
    ): View? {
        ...
    }
...

Java

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

Анализ допустимости значения NULL

Android Studio поддерживает анализ допустимости значения NULL, чтобы автоматически выводить и вставлять аннотации NULL в код. Анализ допустимости значения NULL сканирует контракты в иерархиях методов в коде, чтобы обнаружить:

  • Вызов методов, которые могут возвращать значение null.
  • Методы, которые не должны возвращать значение null.
  • Переменные, такие как поля, локальные переменные и параметры, которые могут быть нулевыми.
  • Переменные, такие как поля, локальные переменные и параметры, которые не могут содержать нулевое значение.

Затем анализ автоматически вставляет соответствующие аннотации null в обнаруженные места.

Чтобы запустить анализ допустимости значения NULL в Android Studio, выберите Analyze (Анализ) > Infer Nullity (Вывести допустимость значения NULL). Android Studio вставит аннотации @Nullable и @NonNull в обнаруженные места в коде. После выполнения анализа нулевых значений рекомендуется проверить добавленные аннотации.

Примечание. При добавлении аннотаций null-допустимости автозаполнение может предлагать аннотации IntelliJ @Nullable и @NotNull вместо аннотаций null-допустимости Android и автоматически импортировать соответствующую библиотеку. Однако инструмент проверки кода Android Studio ищет только аннотации null для Android. При проверке аннотаций убедитесь, что в проекте используются аннотации Android null, чтобы инструмент lint checker мог правильно уведомлять вас во время проверки кода.

Аннотации ресурсов

Проверка типов ресурсов может быть полезна, поскольку ссылки Android на ресурсы, такие как drawable и строковый ресурс, передаются как целые числа.

Код, который ожидает, что параметр будет ссылаться на определенный тип ресурса, например String, может быть передан ожидаемому типу ссылки int, но на самом деле ссылаться на другой тип ресурса, например R.string.

Например, добавьте аннотации @StringRes, чтобы проверить, содержит ли параметр ресурса ссылку R.string, как показано ниже:

Kotlin

abstract fun setTitle(@StringRes resId: Int)

Java

public abstract void setTitle(@StringRes int resId)

Если в параметр не передана ссылка на R.string, при проверке кода аннотация генерирует предупреждение.

Аннотации для других типов ресурсов, таких как @DrawableRes, @DimenRes, @ColorRes и @InterpolatorRes, можно добавлять в том же формате. Они будут выполняться во время проверки кода.

Если параметр поддерживает несколько типов ресурсов, вы можете добавить к нему несколько аннотаций. Используйте @AnyRes, чтобы указать, что аннотированный параметр может быть ресурсом R любого типа.

Хотя вы можете использовать @ColorRes, чтобы указать, что параметр должен быть ресурсом цвета, целочисленное значение цвета (в формате RRGGBB или AARRGGBB) не распознается как ресурс цвета. Вместо этого используйте аннотацию @ColorInt, чтобы указать, что параметр должен быть целочисленным значением цвета. Инструменты сборки будут помечать некорректный код, который передает аннотированным методам идентификатор ресурса цвета, например android.R.color.black, а не целочисленное значение цвета.

Заметки к цепочке

Аннотации потоков проверяют, вызывается ли метод из определенного типа потока. Поддерживаются следующие аннотации цепочек:

Инструменты сборки обрабатывают аннотации @MainThread и @UiThread как взаимозаменяемые, поэтому вы можете вызывать методы @UiThread из методов @MainThread и наоборот. Однако в системных приложениях с несколькими представлениями в разных потоках поток UI может отличаться от основного. Поэтому методы, связанные с иерархией View приложения, следует помечать аннотацией @UiThread, а методы, связанные с жизненным циклом приложения, – аннотацией @MainThread.

Если все методы в классе имеют одинаковые требования к потокам, вы можете добавить в класс одну аннотацию потока, чтобы убедиться, что все методы в классе вызываются из потока одного типа.

Часто аннотации потоков используются для проверки того, что методы или классы, аннотированные с помощью @WorkerThread, вызываются только из соответствующего фонового потока.

Аннотации с ограничениями значений

Используйте аннотации @IntRange, @FloatRange и @Size, чтобы проверить значения переданных параметров. Оба параметра (@IntRange и @FloatRange) наиболее полезны, когда применяются к параметрам, в которых пользователи могут ошибиться с диапазоном.

Аннотация @IntRange проверяет, находится ли значение параметра integer или long в указанном диапазоне. В примере ниже показано, что параметр alpha должен содержать целое число от 0 до 255:

Kotlin

fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { ... }

Java

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

Аннотация @FloatRange проверяет, находится ли значение параметра с плавающей запятой или двойной точностью в указанном диапазоне значений с плавающей запятой. В следующем примере показано, что параметр alpha должен содержать значение с плавающей запятой от 0,0 до 1,0:

Kotlin

fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {...}

Java

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

Аннотация @Size проверяет размер коллекции или массива либо длину строки. Аннотация @Size позволяет проверить следующие свойства:

  • Минимальный размер, например @Size(min=2).
  • Максимальный размер, например @Size(max=2).
  • Точный размер, например @Size(2).
  • Число, кратное размеру, например @Size(multiple=2).

Например, @Size(min=1) проверяет, не пуста ли коллекция, а @Size(3) – содержит ли массив ровно три значения.

В следующем примере показано, что массив location должен содержать хотя бы один элемент:

Kotlin

fun getLocation(button: View, @Size(min=1) location: IntArray) {
    button.getLocationOnScreen(location)
}

Java

void getLocation(View button, @Size(min=1) int[] location) {
    button.getLocationOnScreen(location);
}

Аннотации разрешений

Используйте аннотацию @RequiresPermission, чтобы проверить разрешения вызывающего метода. Чтобы проверить одно разрешение из списка действительных разрешений, используйте атрибут anyOf. Чтобы проверить набор разрешений, используйте атрибут allOf. В следующем примере метод setWallpaper() аннотируется, чтобы указать, что вызывающий метод должен иметь разрешение permission.SET_WALLPAPERS:

Kotlin

@RequiresPermission(Manifest.permission.SET_WALLPAPER)
@Throws(IOException::class)
abstract fun setWallpaper(bitmap: Bitmap)

Java

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

В примере ниже вызывающий метод copyImageFile() должен иметь доступ на чтение как к внешнему хранилищу, так и к географическим метаданным в скопированном изображении:

Kotlin

@RequiresPermission(allOf = [
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_MEDIA_LOCATION
])
fun copyImageFile(dest: String, source: String) {
    ...
}

Java

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

Чтобы задать разрешения для намерений, добавьте требование к разрешению в строковое поле, определяющее название действия намерения:

Kotlin

@RequiresPermission(android.Manifest.permission.BLUETOOTH)
const val ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"

Java

@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
            "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";

Если поставщику контента требуются отдельные разрешения на чтение и запись, заключите каждое требование в аннотацию @RequiresPermission.Read или @RequiresPermission.Write.

Kotlin

@RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS))
val BOOKMARKS_URI = Uri.parse("content://browser/bookmarks")

Java

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

Косвенные разрешения

Если разрешение зависит от конкретного значения, переданного параметру метода, используйте @RequiresPermission для самого параметра, не перечисляя конкретные разрешения. Например, метод startActivity(Intent) использует косвенное разрешение для намерения, переданного методу:

Kotlin

abstract fun startActivity(@RequiresPermission intent: Intent, bundle: Bundle?)

Java

public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle)

При использовании косвенных разрешений инструменты сборки выполняют анализ потока данных, чтобы проверить, есть ли у аргумента, переданного в метод, какие-либо аннотации @RequiresPermission. Затем они применяют все существующие аннотации из параметра к самому методу. В примере с startActivity(Intent) аннотации в классе Intent вызывают предупреждения о недопустимом использовании startActivity(Intent), когда методу передается намерение без соответствующих разрешений, как показано на рисунке 1.

Рисунок 1. Предупреждение, созданное на основе аннотации косвенных разрешений для метода startActivity(Intent).

Инструменты сборки генерируют предупреждение в startActivity(Intent) на основе аннотации в названии действия намерения в классе Intent:

Kotlin

@RequiresPermission(Manifest.permission.CALL_PHONE)
const val ACTION_CALL = "android.intent.action.CALL"

Java

@RequiresPermission(Manifest.permission.CALL_PHONE)
public static final String ACTION_CALL = "android.intent.action.CALL";

При необходимости вы можете заменить @RequiresPermission на @RequiresPermission.Read или @RequiresPermission.Write при аннотировании параметра метода. Однако для косвенных разрешений @RequiresPermission не следует использовать вместе с аннотациями разрешений на чтение или разрешение на запись.

Аннотации возвращаемых значений

Используйте аннотацию @CheckResult, чтобы проверить, действительно ли используется результат или возвращаемое значение метода. Вместо того чтобы добавлять аннотацию @CheckResult ко всем методам, которые возвращают значение, используйте ее только для методов, результаты которых могут быть неочевидны.

Например, начинающие разработчики на Java часто ошибочно полагают, что <String>.trim() удаляет пробелы из исходной строки. Если добавить к методу аннотацию @CheckResult, то будут помечаться случаи использования <String>.trim(), когда вызывающий объект ничего не делает с возвращаемым значением метода.

В следующем примере аннотация checkPermissions() используется для проверки того, действительно ли возвращаемое значение метода используется в коде. В нем также указывается метод enforcePermission(), который предлагается разработчику в качестве замены.

Kotlin

@CheckResult(suggest = "#enforcePermission(String,int,int,String)")
abstract fun checkPermission(permission: String, pid: Int, uid: Int): Int

Java

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

Аннотации CallSuper

Используйте аннотацию @CallSuper, чтобы проверить, что переопределяющий метод вызывает суперреализацию метода.

В следующем примере метод onCreate() аннотируется, чтобы гарантировать, что любые переопределяющие реализации метода вызывают super.onCreate():

Kotlin

@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
}

Java

@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}

Аннотации Typedef

Аннотации Typedef проверяют, ссылается ли определенный параметр, возвращаемое значение или поле на определенный набор констант. Они также позволяют автоматически предлагать допустимые константы при автозаполнении кода.

Используйте аннотации @IntDef и @StringDef, чтобы создавать перечисляемые аннотации наборов целых чисел и строк для проверки других типов ссылок на код.

В аннотациях Typedef используется @interface для объявления нового типа аннотации перечисления. Аннотации @IntDef и @StringDef, а также @Retention, аннотируют новую аннотацию и необходимы для определения перечисляемого типа. Аннотация @Retention(RetentionPolicy.SOURCE) сообщает компилятору, что не нужно сохранять данные аннотации в файле .class.

В следующем примере показаны шаги по созданию аннотации, которая проверяет, ссылается ли значение, переданное в качестве параметра метода, на одну из определенных констант:

Kotlin

import androidx.annotation.IntDef
//...
// Define the list of accepted constants and declare the NavigationMode annotation.
@Retention(AnnotationRetention.SOURCE)
@IntDef(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS)
annotation class NavigationMode

// Declare the constants.
const val NAVIGATION_MODE_STANDARD = 0
const val NAVIGATION_MODE_LIST = 1
const val NAVIGATION_MODE_TABS = 2

abstract class ActionBar {

    // Decorate the target methods with the annotation.
    // Attach the annotation.
    @get:NavigationMode
    @setparam:NavigationMode
    abstract var navigationMode: Int

}

Java

import androidx.annotation.IntDef;
//...
public abstract class ActionBar {
    //...
    // Define the list of accepted constants and declare the NavigationMode annotation.
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
    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);
}

При сборке этого кода будет создано предупреждение, если параметр mode не ссылается на одну из определенных констант (NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST или NAVIGATION_MODE_TABS).

Объедините @IntDef и @IntRange, чтобы указать, что целое число может быть либо заданным набором констант, либо значением в пределах диапазона.

Включить объединение констант с флагами

Если пользователи могут комбинировать разрешенные константы с флагом (например, |, &, ^ и т. д.), вы можете определить аннотацию с атрибутом flag, чтобы проверить, ссылается ли параметр или возвращаемое значение на допустимый шаблон.

В следующем примере создается аннотация DisplayOptions со списком допустимых констант DISPLAY_:

Kotlin

import androidx.annotation.IntDef
...

@IntDef(flag = true, value = [
    DISPLAY_USE_LOGO,
    DISPLAY_SHOW_HOME,
    DISPLAY_HOME_AS_UP,
    DISPLAY_SHOW_TITLE,
    DISPLAY_SHOW_CUSTOM
])
@Retention(AnnotationRetention.SOURCE)
annotation class DisplayOptions
...

Java

import androidx.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 {}

...

Если при сборке кода с флагом аннотации декорированный параметр или возвращаемое значение не ссылается на допустимый шаблон, генерируется предупреждение.

Сохранить аннотацию

Аннотация @Keep гарантирует, что аннотированный класс или метод не будет удален при минификации кода во время сборки. Обычно эта аннотация добавляется к методам и классам, доступ к которым осуществляется через рефлексию, чтобы компилятор не считал код неиспользуемым.

Внимание! Классы и методы, аннотированные с помощью @Keep, всегда включаются в APK-файл приложения, даже если вы не ссылаетесь на них в логике приложения.

Чтобы размер приложения оставался небольшим, подумайте, нужно ли сохранять каждую аннотацию @Keep. Если вы используете рефлексию для доступа к аннотированному классу или методу, добавьте в правила ProGuard условие -if, указав класс, который выполняет вызовы рефлексии.

Подробнее о том, как уменьшить размер кода и указать, какой код не нужно удалять…

Аннотации видимости кода

Используйте следующие аннотации, чтобы обозначить видимость определенных частей кода, таких как методы, классы, поля или пакеты.

Как сделать код видимым для тестирования

Аннотация @VisibleForTesting указывает, что аннотированный метод более заметен, чем обычно необходимо для того, чтобы сделать метод тестируемым. У этой аннотации есть необязательный аргумент otherwise, который позволяет указать, какой была бы видимость метода, если бы не необходимость сделать его видимым для тестирования. Lint использует аргумент otherwise, чтобы обеспечить нужную видимость.

В примере ниже myMethod() обычно имеет значение private, но для тестов используется package-private. Если метод с аннотацией VisibleForTesting.PRIVATE вызывается за пределами контекста, разрешенного доступом private, например из другого блока компиляции, lint показывает сообщение об ошибке.

Kotlin

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun myMethod() {
    ...
}

Java

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
void myMethod() { ... }

Вы также можете указать @VisibleForTesting(otherwise = VisibleForTesting.NONE), чтобы обозначить, что метод существует только для тестирования. Эта форма аналогична использованию @RestrictTo(TESTS). Оба выполняют одну и ту же проверку.

Как ограничить API

Аннотация @RestrictTo указывает, что доступ к аннотированному API (пакету, классу или методу) ограничен следующим образом:

Подклассы

Используйте форму аннотации @RestrictTo(RestrictTo.Scope.SUBCLASSES), чтобы ограничить доступ к API только для подклассов.

Доступ к этому API могут получить только классы, расширяющие аннотированный класс. Модификатор protected в Java недостаточно строгий, поскольку он разрешает доступ из несвязанных классов в том же пакете. Кроме того, в некоторых случаях вы можете оставить метод public для гибкости в будущем, поскольку вы никогда не сможете сделать ранее protected и переопределенный метод public, но вы хотите дать подсказку, что класс предназначен для использования только внутри класса или из подклассов.

Библиотеки

Используйте форму аннотации @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX), чтобы ограничить доступ к API только для ваших библиотек.

Доступ к аннотированному API может получить только код вашей библиотеки. Это позволяет не только организовать код в любой иерархии пакетов, но и использовать его в группе связанных библиотек. Этот вариант уже доступен для библиотек Jetpack, в которых есть много кода реализации, не предназначенного для внешнего использования, но который должен быть public, чтобы его можно было использовать в различных дополнительных библиотеках Jetpack.

Тестирование

Используйте форму аннотации @RestrictTo(RestrictTo.Scope.TESTS), чтобы запретить другим разработчикам доступ к вашим API для тестирования.

Доступ к аннотированному API может получить только код тестирования. Это не позволит другим разработчикам использовать API, предназначенные только для тестирования.