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 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 hacer que no se procese correctamente o no se ejecute tu app, incluso si usas 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 anotación, debes usar la configuración de dependencia "annotationProcessor" a fin de agregar esa dependencia. Para obtener más información, lee Cómo usar la configuración de dependencias del procesador de anotaciones.

Cómo agregar anotaciones a tu proyecto

Para habilitar las anotaciones de tu proyecto, agrega la dependencia support-annotations a tu biblioteca o app. Cualquier anotación que agregues luego se verificará 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, se incluyen como parte del artefacto de archivo de Android (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 estás usando la biblioteca appcompat, no es necesario que agregues la dependencia support-annotations. Debido a que la biblioteca appcompat ya depende de la biblioteca de anotaciones, puedes acceder a estas últimas.

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 para 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 continuo, ten en cuenta que la tarea lint no permite implementar tareas de nulidad (solo Android Studio lo permite). Para obtener más información sobre cómo habilitar y ejecutar inspecciones de lint, consulta 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 anotaciones @Nullable y @NonNull a fin de verificar la nulidad de una variable, un parámetro o un valor de retorno determinados. La anotación @Nullable indica que una variable, un parámetro o un valor de retorno pueden ser nulos, mientras que @NonNull indica que una variable, un parámetro o un valor de retorno no lo pueden ser.

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, durante la compilación del código se genera una advertencia 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 valor de retorno de un método si se debe verificar explícitamente la nulidad de cada uso de dicho método.

En el siguiente ejemplo, se adjunta la anotación @NonNull a los parámetros context y attrs para verificar que los valores de 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 bits generado cuando se especifique un valor 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, el autocompletado podría sugerir las anotaciones @Nullable y @NotNull de IntelliJ en lugar de las anotaciones de valor nulo de Android, y podría importar 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 los recursos 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 aquí:

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 demás tipos de recursos, como @DrawableRes, @DimenRes, @ColorRes y @InterpolatorRes usando 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 anotado puede ser cualquier tipo de recurso R.

Si bien puedes usar @ColorRes para especificar que un parámetro debe ser un recurso de color, un valor entero de color (en el formato RRGGBB o AARRGGBB) no se reconoce 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 los métodos anotados.

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, debes anotar con @UiThread los métodos vinculados a 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 debido a que esta clase lleva a cabo operaciones en segundo plano y publica los resultados solo en el subproceso de la 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 y @FloatRange resultan muy útiles cuando se aplican a parámetros para los cuales los usuarios probablemente 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 verifica 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 también la extensión de una string. La anotación @Size puede usarse para verificar los siguientes atributos:

  • Tamaño mínimo (como @Size(min=2))
  • Tamaño máximo (como @Size(max=2))
  • Tamaño exacto (como @Size(2))
  • Un número del cual el tamaño debe ser múltiplo (como @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 de la 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, encapsula 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 proporcionado al parámetro de un método, usa @RequiresPermission en el parámetro, sin incluir una lista de los permisos específicos. Por ejemplo, el método startActivity(Intent) usa un permiso indirecto sobre la 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 una intent sin los permisos correspondientes al método, como se muestra en la figura 1.

Figura 1: Advertencia generada a partir de una anotación de permiso indirecto sobre 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 crean anotaciones sobre el parámetro de un método. Sin embargo, en el caso de los permisos indirectos, no debe usarse @RequiresPermission en conjunto con las anotaciones de permisos de lectura o escritura.

Anotaciones de valor de retorno

Usa la anotación @CheckResult para validar el uso efectivo del resultado o valor de retorno 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 posiblemente confusos. Por ejemplo, los nuevos desarrolladores de Java a menudo piensan erróneamente 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 de retorno 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 de retorno 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. Este tipo de anotaciones permite garantizar que un parámetro, valor de retorno o campo en particular haga referencia a un conjunto de constantes específico. Además, permiten habilitar la compleción del código para ofrecer las constantes permitidas de manera automática.

En las anotaciones typedef, 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 indicadores

Si los usuarios pueden combinar las constantes permitidas con un indicador (por ejemplo, |, &, ^, etc.), puedes definir una anotación con un atributo flag para verificar si un parámetro o valor de retorno 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 anotado 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 un tamaño reducido de la app, 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 anotados, utiliza un condicional -if en las 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 del código no se deben quitar, consulta 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 visible para la 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 esté visible para las pruebas. Lint usa el argumento otherwise para aplicar la visibilidad deseada.

En el siguiente ejemplo, myMethod() normalmente es private, pero es un paquete privado para las pruebas. Mediante 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) a fin de indicar que un solo existe un método para pruebas. Es lo mismo que usar @RestrictTo(TESTS). 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 restringir el acceso a la API solo a las subclases.

Solo las clases que extienden la clase anotada pueden acceder a esta API. El modificador protected de Java no es lo suficientemente restrictivo porque permite el acceso de clases no relacionadas dentro del mismo paquete. Además, hay casos en lo que te conviene dejar un método como public para tener mayor flexibilidad a futuro, ya que no es posible que un método protected y anulado anterior pase a ser 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 restringir el acceso de la API solo a tus bibliotecas.

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 compartir el código 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 quiere decir que si usas estas clases de implementación por error, lint te advierte que no es una práctica recomendable.

Prueba

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 realicen desarrollos con API que solo tienen fines de prueba.