Улучшите проверку кода с помощью аннотаций

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

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

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

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

Добавьте аннотации в свой проект

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

Добавьте зависимость библиотеки аннотаций Jetpack.

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

Котлин

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

классный

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

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

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

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

Запускайте проверки кода

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

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

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

Аннотации недействительности

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

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

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

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

Котлин

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

Джава

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

Анализ обнуляемости

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

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

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

Чтобы запустить анализ на недействительность в Android Studio, выберите «Анализ» > «Вывести недействительность» . Android Studio вставляет аннотации Android @Nullable и @NonNull в обнаруженные места вашего кода. После выполнения нулевого анализа рекомендуется проверить введенные аннотации.

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

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

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

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

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

Котлин

abstract fun setTitle(@StringRes resId: Int)

Джава

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 и наоборот. Однако поток пользовательского интерфейса может отличаться от основного потока в случае системных приложений с несколькими представлениями в разных потоках. Поэтому вам следует аннотировать методы, связанные с иерархией представлений приложения, с помощью @UiThread и аннотировать только методы, связанные с жизненным циклом приложения, с помощью @MainThread .

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

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

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

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

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

Котлин

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

Джава

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

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

Котлин

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

Джава

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 должен содержать хотя бы один элемент:

Котлин

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

Джава

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

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

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

Котлин

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

Джава

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

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

Котлин

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

Джава

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

Для разрешений на намерения поместите требование разрешения в строковое поле, которое определяет имя действия намерения:

Котлин

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

Джава

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

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

Котлин

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

Джава

@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) использует косвенное разрешение на намерение, передаваемое в метод:

Котлин

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

Джава

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

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

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

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

Котлин

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

Джава

@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() методом, который следует предложить разработчику в качестве замены:

Котлин

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

Джава

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

CallSuper аннотации

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

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

Котлин

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

Джава

@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}

Аннотации определения типа

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

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

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

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

Котлин

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

}

Джава

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_ :

Котлин

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

Джава

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 . Если вы используете отражение для доступа к аннотированному классу или методу, используйте условие -if в правилах ProGuard, указав класс, который выполняет вызовы отражения.

Дополнительные сведения о том, как минимизировать код и указать, какой код не следует удалять, см . в разделе Сжатие, запутывание и оптимизация приложения .

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

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

Сделайте код видимым для тестирования

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

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

Котлин

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

Джава

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

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

Ограничить API

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

Подклассы

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

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

Библиотеки

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

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

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

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

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