Cómo mejorar la inspección de código con anotaciones

Las herramientas de inspección de código como lint pueden ayudarte a detectar problemas y mejorar tu código, pero no tienen demasiada capacidad para realizar inferencias. Los ID de recursos de Android, por ejemplo, usan un elemento int para identificar strings, gráficos, colores y otros tipos de recursos, por lo cual las herramientas de inspección no pueden detectar si, en lugar de especificar un recurso de string, deberías especificar un color. Esta situación puede provocar que no se procese correctamente o no se ejecute tu app, incluso aunque uses la inspección de código.

Las anotaciones te permiten proporcionar pistas para las herramientas de inspección de código como lint, a fin de que se detecten estos problemas de código más sutiles. Se agregan como etiquetas de metadatos que adjuntas a variables, parámetros y valores de retorno para inspeccionar valores de retorno de método, parámetros pasados, variables locales y campos. Cuando se usan con las herramientas de inspección de código, las anotaciones pueden ayudarte a detectar problemas, como excepciones de puntero nulas y conflictos de tipos de recursos.

Android admite diferentes anotaciones por medio de la biblioteca de compatibilidad de anotaciones. Puedes acceder a ella a través del paquete android.support.annotation.

Nota: Si un módulo tiene una dependencia en un procesador de anotaciones, debes usar la configuración de dependencia "annotationProcessor" a fin de agregar esa dependencia. Para obtener más información, consulta el artículo Cómo usar la configuración de dependencias del procesador de anotaciones.

Cómo agregar anotaciones a tu proyecto

Para habilitar las anotaciones en tu proyecto, agrega la dependencia support-annotations a tu biblioteca o app. Se verificará cualquier anotación que agregues cuando ejecutes una inspección de código o una tarea lint.

Cómo agregar la dependencia de biblioteca de anotaciones de compatibilidad

La biblioteca de anotaciones de compatibilidad se publica en el repositorio Maven de Google. Para agregar la biblioteca de anotaciones de compatibilidad a tu proyecto, incluye la siguiente línea en el bloque dependencies de tu archivo build.gradle:

    dependencies {
        implementation 'com.android.support:support-annotations:28.0.0'
    }
    
En la barra de herramientas o en la notificación de sincronización que aparezca, haz clic en Sync Now.

Si usas anotaciones en tu propio módulo de biblioteca, estas se incluyen como parte del artefacto de Android ARchive (AAR) en formato XML en el archivo annotations.zip. Agregar la dependencia support-annotations no introduce una dependencia para usuarios intermedios de tu biblioteca.

Nota: Si usas la biblioteca appcompat, no es necesario que agregues la dependencia support-annotations. Dado que la biblioteca appcompat ya depende de la biblioteca de anotaciones, puedes acceder a las anotaciones.

Para obtener una lista completa de las anotaciones incluidas en el repositorio de compatibilidad, examina la referencia sobre la biblioteca de anotaciones de compatibilidad o usa la función de autocompletado para mostrar las opciones disponibles para la instrucción import android.support.annotation..

Cómo ejecutar inspecciones de código

Para iniciar una inspección de código desde Android Studio, que incluye validación de anotaciones y revisión automática de lint, selecciona Analyze > Inspect Code en la barra del menú. Android Studio muestra mensajes de conflicto para marcar posibles problemas de conflicto entre el código y las anotaciones, y sugerir posibles soluciones.

También puedes ejecutar la tarea lint mediante la línea de comandos a fin de aplicar anotaciones. Si bien este método puede resultar útil para marcar problemas con un servidor de integración continua, ten en cuenta que la tarea lint no permite implementar anotaciones de nulidad (solo Android Studio lo permite). Para obtener más información sobre cómo habilitar y ejecutar inspecciones de lint, consulta el artículo Cómo mejorar tu código con lint.

Ten en cuenta que, si bien los conflictos de anotaciones generan advertencias, estas no impiden que tu app realice compilaciones.

Anotaciones de nulidad

Agrega las anotaciones @Nullable y @NonNull a fin de verificar la nulidad de una variable, un parámetro o un resultado determinados. La anotación @Nullable indica que una variable, un parámetro o un resultado pueden ser nulos, mientras que @NonNull indica que no.

Por ejemplo, si una variable local que contiene un valor nulo se pasa como parámetro a un método con la anotación @NonNull adjunta a ese parámetro, se genera una advertencia durante la compilación del código mediante la que se indica un conflicto de no nulidad. Por otro lado, si intentas hacer referencia al resultado de un método marcado por @Nullable sin verificar primero si el resultado es nulo, se genera una advertencia de nulidad. Solo debes usar @Nullable en el resultado de un método si se debe verificar explícitamente la nulidad de cada uso del método.

En el siguiente ejemplo, se adjunta la anotación @NonNull a los parámetros context y attrs para verificar que los valores del parámetro que se pasan no sean nulos. Además, se verifica que con el método onCreateView() no se muestre un resultado nulo. Ten en cuenta que, con Kotlin, no es necesario usar la anotación @NonNull porque se la agregará automáticamente al código de bytes generado cuando se especifique un tipo que no admita nulidad:

Kotlin

    import android.support.annotation.NonNull
    ...

        /** Add support for inflating the <fragment> tag. **/
        fun onCreateView(
                name: String?,
                context: Context,
                attrs: AttributeSet
        ): View? {
            ...
        }
    ...
    

Java

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

Análisis de nulidad

Android Studio es compatible con la ejecución de un análisis de nulidad para realizar inferencias e insertar anotaciones de nulidad en tu código de forma automática. Un análisis de nulidad analiza los contratos en las jerarquías de los métodos de tu código para detectar lo siguiente:

  • Métodos de llamada que pueden mostrar un valor nulo
  • Métodos que no deben mostrar un valor nulo
  • Variables, como campos, variables locales y parámetros, que pueden tener un valor nulo
  • Variables, como campos, variables locales y parámetros, que no pueden tener un valor nulo

Luego, el análisis inserta automáticamente las anotaciones de valor nulo correspondientes en las ubicaciones detectadas.

Para ejecutar un análisis de nulidad en Android Studio, selecciona Analyze > Infer Nullity. Android Studio inserta las anotaciones @Nullable y @NonNull de Android en las ubicaciones detectadas de tu código. Después de ejecutar un análisis de nulidad, recomendamos verificar las anotaciones insertadas.

Nota: Cuando se agregan anotaciones de nulidad, es posible que la función de autocompletado sugiera las anotaciones @Nullable y @NotNull de IntelliJ, en lugar de las anotaciones de valor nulo de Android, y que importe automáticamente la biblioteca correspondiente. Sin embargo, el revisor lint de Android Studio solo detecta las anotaciones de valor nulo de Android. Cuando verifiques tus anotaciones, confirma que en el proyecto se usen las anotaciones de valor nulo de Android, de modo que el revisor lint pueda enviarte notificaciones apropiadas durante la inspección del código.

Anotaciones de recursos

La validación de tipos de recursos puede resultar útil porque las referencias a recursos de Android, como drawable y string, se pasan como valores enteros. Un código para el cual se espera que un parámetro haga referencia a un tipo de recurso específico, como Drawables, puede recibir el tipo de referencia int esperado, pero, en realidad, puede hacer referencia a un tipo de recurso diferente, como R.string.

Por ejemplo, agrega anotaciones @StringRes para verificar que un parámetro de recurso contenga una referencia R.string, como se muestra a continuación:

Kotlin

    abstract fun setTitle(@StringRes resId: Int)
    

Java

    public abstract void setTitle(@StringRes int resId)
    

Durante la inspección de código, la anotación genera una advertencia si no se pasa una referencia R.string en el parámetro.

Se pueden agregar anotaciones para los otros tipos de recursos, como @DrawableRes, @DimenRes, @ColorRes y @InterpolatorRes mediante el mismo formato de anotación. Además, estas se pueden ejecutar durante la inspección de código. Si tu parámetro admite varios tipos de recursos, puedes disponer más de una de estas anotaciones en un parámetro determinado. Usa @AnyRes para indicar que el parámetro con anotaciones puede ser cualquier tipo de recurso R.

Si bien puedes usar @ColorRes para especificar que un parámetro debe ser un recurso de color, no se reconoce un valor entero de color (en el formato RRGGBB o AARRGGBB) como recurso de color. En su lugar, usa la anotación @ColorInt para indicar que un parámetro debe ser un valor entero de color. Las herramientas de compilación enviarán una advertencia si un código incorrecto pasa un ID de recurso de color como android.R.color.black, en lugar de un valor entero de color, a métodos con anotaciones.

Anotaciones de subprocesos

Mediante estas anotaciones, se verifica si se llama a un método desde un tipo específico de subproceso. Se admiten las siguientes anotaciones de subprocesos:

Nota: Las herramientas de compilación consideran las anotaciones @MainThread y @UiThread como intercambiables, por lo que puedes llamar a los métodos @UiThread desde los métodos @MainThread y viceversa. Sin embargo, es posible que un subproceso de IU sea diferente del subproceso principal en el caso de las apps de sistema que tienen varias vistas en distintos subprocesos. Por lo tanto, con @UiThread, debes anotar los métodos asociados con la jerarquía de vistas de una app y, con @MainThread, solo los métodos vinculados al ciclo de vida de una app.

Si todos los métodos de una clase comparten el mismo requisito de subproceso, puedes agregar una anotación de subproceso único a la clase para verificar que se llame a todos los métodos de esta última desde el mismo tipo de subproceso.

Un uso común de la anotación de subproceso consiste en validar las anulaciones de métodos en la clase AsyncTask, ya que esta lleva a cabo operaciones en segundo plano y publica los resultados solo en el subproceso de IU.

Anotaciones de restricción de valor

Usa las anotaciones @IntRange, @FloatRange y @Size para validar los valores de los parámetros pasados. Tanto @IntRange como @FloatRange resultan muy útiles cuando se aplican a parámetros para los cuales es probable que los usuarios obtengan el rango incorrecto.

La anotación @IntRange valida el posicionamiento de un valor entero o un valor de parámetro dentro de un rango especificado. En el siguiente ejemplo, se garantiza que el parámetro alpha contenga un valor entero entre 0 y 255:

Kotlin

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

Java

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

La anotación @FloatRange permite verificar que un valor de parámetro doble o flotante se encuentre dentro de un rango especificado de valores de punto flotante. En el siguiente ejemplo, se garantiza que el parámetro alpha contenga un valor flotante de 0.0 a 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) {...}
    

La anotación @Size permite verificar el tamaño de un conjunto o arreglo y la extensión de una string. La anotación @Size puede usarse para verificar los siguientes atributos:

  • Tamaño mínimo (por ejemplo, @Size(min=2))
  • Tamaño máximo (por ejemplo, @Size(max=2))
  • Tamaño exacto (por ejemplo, @Size(2))
  • Un número cuyo tamaño debe ser un múltiplo (por ejemplo, @Size(multiple=2))

Por ejemplo, @Size(min=1) permite verificar si un conjunto no está vacío y @Size(3) permite validar que un arreglo contenga exactamente tres valores. En el siguiente ejemplo, se garantiza que el arreglo location contenga al menos un elemento:

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

Anotaciones de permisos

Usa la anotación @RequiresPermission para validar los permisos del emisor de un método. Para verificar un solo permiso de una lista de permisos válidos, usa el atributo anyOf. Para verificar un conjunto de permisos, usa el atributo allOf. En el siguiente ejemplo, se anota el método setWallpaper() para garantizar que el emisor tenga el permiso 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;
    

En este ejemplo, se requiere que el emisor del método copyFile() tenga permisos de lectura y escritura con respecto al almacenamiento externo:

Kotlin

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

Java

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

En el caso de los permisos relacionados con intents, coloca el requisito de permiso en el campo de la string que define el nombre de la acción del intent:

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

En el caso de los permisos relacionados con proveedores de contenido para los cuales necesites permisos de lectura y escritura por separado, une cada requisito de permiso en una anotación @RequiresPermission.Read o @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");
    

Permisos indirectos

Cuando un permiso dependa del valor específico que se proporciona al parámetro de un método, usa @RequiresPermission en el parámetro, sin enumerar los permisos específicos. Por ejemplo, el método startActivity(Intent) usa un permiso indirecto sobre el intent que se pasa al método:

Kotlin

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

Java

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

Cuando usas permisos indirectos, las herramientas de compilación realizan un análisis de flujo de datos para verificar si el argumento que se pasó al método tiene alguna anotación @RequiresPermission. Luego, implementan cualquier anotación del parámetro existente en el método. En el ejemplo de startActivity(Intent), las anotaciones de la clase Intent generan las advertencias resultantes sobre los usos no válidos de startActivity(Intent) cuando se pasa un intent sin los permisos correspondientes al método, como se muestra en la figura 1.

Figura 1: Advertencia que se genera de una anotación de permisos indirecta en el método startActivity(Intent)

Las herramientas de compilación generan la advertencia sobre startActivity(Intent) a partir de la anotación en el nombre de la acción del intent correspondiente en la clase Intent:

Kotlin

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

Java

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

Si es necesario, puedes reemplazar @RequiresPermission por @RequiresPermission.Read o @RequiresPermission.Write cuando se anota el parámetro de un método. Sin embargo, en el caso de los permisos indirectos, no se debe usar @RequiresPermission junto con las anotaciones de permisos de lectura ni escritura.

Anotaciones de los valores que se muestran

Usa la anotación @CheckResult para validar el uso efectivo del resultado o del valor que se muestra de un método. En lugar de anotar cada método no vacío con @CheckResult, agrega la anotación para aclarar los resultados de métodos que podrían resultar confusos. Por ejemplo, los nuevos desarrolladores de Java a menudo cometen el error de pensar que <String>.trim() permite quitar los espacios en blanco de la string original. La anotación del método con @CheckResult permite marcar los usos de <String>.trim() con los cuales el emisor no realiza ninguna acción con el valor que se muestra del método.

En el siguiente ejemplo, se usan anotaciones en el método checkPermissions() a fin de garantizar que se haga referencia al valor que se muestra del método. Además, se designa el método enforcePermission() como método de reemplazo sugerido al desarrollador:

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

Anotaciones CallSuper

Usa la anotación @CallSuper para validar que un método predominante llame a la superimplementación del método. En el siguiente ejemplo, se realiza una anotación en el método onCreate() para garantizar que cualquier implementación del método predominante llame a super.onCreate():

Kotlin

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

Java

    @CallSuper
    protected void onCreate(Bundle savedInstanceState) {
    }
    

Anotaciones typedef

Usa las anotaciones @IntDef y @StringDef para crear anotaciones enumeradas de conjuntos de strings y valores enteros a fin de validar otros tipos de referencias de código. Las anotaciones typedef permiten garantizar que un parámetro, valor que se muestra o campo en particular haga referencia a un conjunto de constantes específico. Además, permiten que se complete el código para ofrecer automáticamente las constantes permitidas.

En este tipo de anotaciones, se usa @interface para declarar el nuevo tipo de anotación enumerada. Las anotaciones @IntDef y @StringDef, junto con @Retention, permiten crear la nueva anotación y son necesarias para definir el tipo enumerado. La anotación @Retention(RetentionPolicy.SOURCE) indica al compilador que no debe almacenar los datos de la anotación enumerada en el archivo .class.

En el siguiente ejemplo, se muestran los pasos para crear una anotación que garantice que un valor pasado como parámetro del método haga referencia a una de las constantes definidas:

Kotlin

    import android.support.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 android.support.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);
    }
    

Cuando compilas este código, se genera una advertencia si el parámetro mode no hace referencia a una de las constantes definidas (NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST o NAVIGATION_MODE_TABS).

También puedes combinar @IntDef y @IntRange para indicar que un valor entero puede ser un conjunto determinado de constantes o un valor dentro de un rango.

Cómo habilitar la combinación de constantes con marcas

Si los usuarios pueden combinar las constantes permitidas con una marca (por ejemplo, |, &, ^, etc.), puedes definir una anotación con un atributo flag para verificar si un parámetro o valor que se muestra hace referencia a un patrón válido. En el siguiente ejemplo se crea la anotación DisplayOptions con una lista de constantes DISPLAY_ válidas:

Kotlin

    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(AnnotationRetention.SOURCE)
    annotation class DisplayOptions
    ...
    

Java

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

    ...
    

Cuando compilas código con un indicador de anotación, se genera una advertencia si el parámetro o valor de retorno decorado no hace referencia a un patrón válido.

Cómo mantener la anotación

La anotación @Keep permite garantizar que no se quite una clase o un método con anotaciones cuando se reduzca el código durante el tiempo de compilación. Por lo general, esta anotación se agrega a métodos y clases a los que se accede mediante reflexión para evitar que el compilador lo considere como código no usado.

Precaución: Las clases y los métodos que anotas con @Keep siempre aparecen en el APK de la app, incluso si nunca hiciste una referencia a estas clases y estos métodos en la lógica de la app.

Para mantener el tamaño de la app reducido, considera si es necesario preservar todas las anotaciones @Keep en la app. Si usas la reflexión para acceder a una clase o un método con anotaciones, utiliza un condicional -if en tus reglas de ProGuard que especifique la clase que hace las llamadas de reflexión.

Para obtener más información sobre cómo reducir el tamaño del código al mínimo y especificar qué partes no se deben quitar, consulta el artículo Cómo reducir el código y los recursos.

Anotaciones de visibilidad de código

Usa las siguientes anotaciones para indicar la visibilidad de partes específicas del código, como métodos, clases, campos o paquetes.

Cómo hacer un método visible para una prueba

La anotación @VisibleForTesting indica que un método con anotaciones es más visible de lo necesario para que se puedan hacer pruebas en este. Esta anotación tiene un argumento otherwise opcional que te permite indicar cuál debería haber sido la visibilidad del método si no hubiese sido por la necesidad de que sea visible para las pruebas. Lint usa el argumento otherwise para aplicar la visibilidad deseada.

En el siguiente ejemplo, myMethod() suele ser private, pero es un paquete privado para las pruebas. Con la siguiente designación VisibleForTesting.PRIVATE, lint muestra un mensaje si se llama a este método desde afuera del contexto permitido por el acceso private, como desde una unidad de compilación diferente.

Kotlin

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

Java

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

También puedes especificar @VisibleForTesting(otherwise = VisibleForTesting.NONE) para indicar que existe un método solo para pruebas. Este formulario equivale al uso de @RestrictTo(TESTS), ya que ambos recursos realizan la misma verificación de lint.

Cómo restringir una API

La anotación @RestrictTo indica que el acceso a la API con anotaciones (paquete, clase o método) está limitado de la siguiente manera.

Subclases

Usa el formulario de anotación @RestrictTo(RestrictTo.Scope.SUBCLASSES) para que solo las subclases puedan acceder a la API.

Solo las clases que extienden la clase con anotaciones pueden acceder a esta API. El modificador protected de Java no es lo suficientemente restrictivo porque permite el acceso de clases no relacionadas en el mismo paquete. Además, existen casos en los que te conviene dejar un método como public para tener mayor flexibilidad en el futuro, ya que no es posible que un método protected y anulado anterior se vuelva public, pero sí puedes indicar que la clase solo está destinada a usos dentro de la clase o desde subclases.

Bibliotecas

Usa el formulario de anotación @RestrictTo(RestrictTo.Scope.GROUP_ID) para que solo tus bibliotecas puedan acceder a la API.

Solo el código de tu biblioteca puede acceder a la API con anotaciones. Esto te permite no solo organizar tu código en la jerarquía de paquetes que desees, sino también compartirlo entre un grupo de bibliotecas relacionadas. Esta opción ya está disponible para las bibliotecas de compatibilidad con grandes cantidades de código de implementación que no está destinado para uso externo, pero que debe ser public para compartirlo entre las diversas bibliotecas de compatibilidad complementarias.

Nota: Los paquetes y las clases de la biblioteca de compatibilidad de Android ahora tienen anotaciones con @RestrictTo(GROUP_ID), lo que significa que si usas estas clases de implementación por error, lint te advertirá que no es una práctica recomendable.

Pruebas

Usa el formulario de anotación @RestrictTo(RestrictTo.Scope.TESTS) para evitar que otros desarrolladores accedan a tus API de prueba.

Solo el código de prueba puede acceder a la API con anotaciones. De esta manera, puedes evitar que otros desarrolladores usen API que solo tienen fines de prueba.