El objetivo de esta página es servir de guía para que los desarrolladores comprendan los principios generales que el Consejo de la API aplica en las revisiones de las APIs.
Además de seguir estos lineamientos cuando escriban APIs, los desarrolladores deben ejecutar la herramienta API Lint, que codifica muchas de estas reglas en verificaciones que se ejecutan en las APIs.
Piensa en esto como la guía de las reglas que obedece esa herramienta de lint, además de consejos generales sobre las reglas que no se pueden codificar en esa herramienta con alta precisión.
Herramienta de lint de API
API Lint está integrado en la herramienta de análisis estático Metalava y se ejecuta automáticamente durante la validación en CI. Puedes ejecutarlo de forma manual desde una verificación de plataforma local con m
checkapi
o una verificación de AndroidX local con ./gradlew :path:to:project:checkApi
.
Reglas de la API
La plataforma de Android y muchas bibliotecas de Jetpack existían antes de que se creara este conjunto de lineamientos, y las políticas que se establecen más adelante en esta página evolucionan continuamente para satisfacer las necesidades del ecosistema de Android.
Como resultado, es posible que algunas APIs existentes no sigan los lineamientos. En otros casos, podría proporcionar una mejor experiencia del usuario para los desarrolladores de apps si una API nueva se mantiene coherente con las APIs existentes en lugar de cumplir estrictamente con los lineamientos.
Usa tu criterio y comunícate con el Consejo de la API si tienes preguntas difíciles sobre una API que se deben resolver o lineamientos que se deben actualizar.
Conceptos básicos de la API
Esta categoría se relaciona con los aspectos principales de una API de Android.
Se deben implementar todas las APIs
Independientemente del público de una API (por ejemplo, público o @SystemApi
), todas las plataformas de la API deben implementarse cuando se combinan o exponen como API. No combines los stubs de la API con la implementación que se realizará más adelante.
Las plataformas de API sin implementaciones tienen varios problemas:
- No se garantiza que se haya expuesto una superficie adecuada o completa. Hasta que los clientes prueben o usen una API, no hay forma de verificar que un cliente tenga las APIs adecuadas para poder usar la función.
- Las APIs sin implementación no se pueden probar en las versiones preliminares para desarrolladores.
- Las APIs sin implementación no se pueden probar en CTS.
Se deben probar todas las APIs
Esto está alineado con los requisitos de CTS de la plataforma, las políticas de AndroidX y, en general, con la idea de que se deben implementar las APIs.
Probar las plataformas de API proporciona una garantía básica de que la plataforma de API es utilizable y que abordamos los casos de uso esperados. Probar la existencia no es suficiente; se debe probar el comportamiento de la API.
Un cambio que agregue una API nueva debe incluir las pruebas correspondientes en el mismo tema de CL o Gerrit.
Las APIs también deben ser probables. Debes poder responder la pregunta: "¿Cómo probará un desarrollador de apps el código que usa tu API?".
Todas las APIs deben estar documentadas
La documentación es una parte clave de la usabilidad de las APIs. Si bien la sintaxis de una plataforma de API puede parecer obvia, los clientes nuevos no comprenderán la semántica, el comportamiento ni el contexto detrás de la API.
Todas las APIs generadas deben cumplir con los lineamientos
Las APIs que generan las herramientas deben seguir los mismos lineamientos de API que el código escrito a mano.
Herramientas que no se recomiendan para generar APIs:
AutoValue
: Infringe los lineamientos de varias maneras, por ejemplo, no hay manera de implementar clases de valor final ni compiladores finales con la forma en que funciona AutoValue.
Estilo de código
Esta categoría se relaciona con el estilo de código general que deben usar los desarrolladores, especialmente cuando escriben APIs públicas.
Sigue las convenciones de programación estándar, excepto donde se indique
Aquí se documentan las convenciones de codificación de Android para los colaboradores externos:
https://source.android.com/source/code-style.html
En general, solemos seguir las convenciones de codificación estándar de Java y Kotlin.
Los acrónimos no deben escribirse en mayúsculas en los nombres de los métodos.
Por ejemplo, el nombre del método debe ser runCtsTests
y no runCTSTests
.
Los nombres no deben terminar con Impl.
Esto expone los detalles de la implementación, evita eso.
Clases
En esta sección, se describen las reglas sobre clases, interfaces y herencia.
Heredar clases públicas nuevas de la clase base adecuada
La herencia expone elementos de la API en tu subclase que podrían no ser apropiados.
Por ejemplo, una nueva subclase pública de FrameLayout
se ve como FrameLayout
, además de los nuevos comportamientos y elementos de la API. Si esa API heredada no es adecuada para tu caso de uso, hereda de una clase más arriba en el árbol, por ejemplo, ViewGroup
o View
.
Si tienes la tentación de anular métodos de la clase base para arrojar UnsupportedOperationException
, reconsidera qué clase base estás usando.
Usa las clases de colecciones básicas
Ya sea que tomes una colección como argumento o la muestres como un valor, siempre prefiere la clase base a la implementación específica (como mostrar List<Foo>
en lugar de ArrayList<Foo>
).
Usa una clase base que exprese las restricciones adecuadas para la API. Por ejemplo, usa List
para una API cuya colección debe estar ordenada y usa Set
para una API cuya colección debe constar de elementos únicos.
En Kotlin, prefiere las colecciones inmutables. Consulta Mutability de colecciones para obtener más detalles.
Comparación entre clases abstractas e interfaces
Java 8 agrega compatibilidad con métodos de interfaz predeterminados, lo que permite a los diseñadores de APIs agregar métodos a las interfaces y, al mismo tiempo, mantener la compatibilidad binaria. El código de la plataforma y todas las bibliotecas de Jetpack deben orientarse a Java 8 o versiones posteriores.
En los casos en que la implementación predeterminada no tenga estado, los diseñadores de la API deben preferir las interfaces en lugar de las clases abstractas, es decir, los métodos de interfaz predeterminados se pueden implementar como llamadas a otros métodos de interfaz.
En los casos en que la implementación predeterminada requiera un constructor o un estado interno, se deben usar clases abstractas.
En ambos casos, los diseñadores de la API pueden optar por dejar un solo método abstracto para simplificar el uso como una lambda:
public interface AnimationEndCallback {
// Always called, must be implemented.
public void onFinished(Animation anim);
// Optional callbacks.
public default void onStopped(Animation anim) { }
public default void onCanceled(Animation anim) { }
}
Los nombres de las clases deben reflejar lo que extienden
Por ejemplo, las clases que extienden Service
deben llamarse FooService
para mayor claridad:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Sufijos genéricos
Evita usar sufijos de nombres de clase genéricos, como Helper
y Util
, para colecciones de métodos de utilidad. En su lugar, coloca los métodos directamente en las clases asociadas o en las funciones de extensión de Kotlin.
En los casos en que los métodos conecten varias clases, asigna a la clase contenedora un nombre significativo que explique lo que hace.
En casos muy limitados, puede ser apropiado usar el sufijo Helper
:
- Se usa para la composición del comportamiento predeterminado.
- Puede implicar la delegación del comportamiento existente a clases nuevas.
- Es posible que requiera un estado persistente
- Por lo general, implica
View
Por ejemplo, si la portabilidad a versiones anteriores de las herramientas de ayuda requiere conservar el estado asociado con un View
y llamar a varios métodos en el View
para instalar la portabilidad a versiones anteriores, TooltipHelper
sería un nombre de clase aceptable.
No expongas el código generado por el IDL como APIs públicas directamente.
Mantén el código generado por el IDL como detalles de implementación. Esto incluye protobuf, sockets, FlatBuffers o cualquier otra plataforma de API que no sea de Java ni de NDK. Sin embargo, la mayoría de los IDL en Android están en AIDL, por lo que esta página se enfoca en AIDL.
Las clases de AIDL generadas no cumplen con los requisitos del guía de estilo de la API (por ejemplo, no pueden usar sobrecargas) y la herramienta de AIDL no está diseñada explícitamente para mantener la compatibilidad con la API de lenguaje, por lo que no puedes incorporarlas en una API pública.
En su lugar, agrega una capa de API pública sobre la interfaz de AIDL, incluso si inicialmente es un wrapper superficial.
Interfaces de Binder
Si la interfaz Binder
es un detalle de implementación, se puede cambiar libremente en el futuro, y la capa pública permite mantener la compatibilidad con versiones anteriores requerida. Por ejemplo, es posible que debas agregar argumentos nuevos a las llamadas internas o optimizar el tráfico de IPC con el procesamiento por lotes o la transmisión, con memoria compartida o algo similar. No se puede hacer ninguna de estas acciones
si tu interfaz AIDL también es la API pública.
Por ejemplo, no expongas FooService
como una API pública directamente:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
En su lugar, une la interfaz Binder
dentro de un administrador o de otra clase:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
Si más adelante se necesita un argumento nuevo para esta llamada, la interfaz interna puede ser mínima y se pueden agregar sobrecargas convenientes a la API pública. Puedes usar la capa de unión para controlar otras inquietudes de retrocompatibilidad a medida que la implementación evoluciona:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo, int flags);
}
public IFooManager {
public void doFoo(String foo) {
if (mAppTargetSdkLevel < 26) {
useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
} else {
mFooService.doFoo(foo, 0);
}
}
public void doFoo(String foo, int flags) {
mFooService.doFoo(foo, flags);
}
}
En el caso de las interfaces Binder
que no forman parte de la plataforma de Android (por ejemplo, una interfaz de servicio que exportan los Servicios de Google Play para que la usen las apps), el requisito de una interfaz IPC estable, publicada y con versión significa que es mucho más difícil evolucionar la interfaz en sí. Sin embargo, aún vale la pena tener una capa de wrapper a su alrededor para que coincida con otros lineamientos de la API y facilitar el uso de la misma API pública para una nueva versión de la interfaz de IPC, si alguna vez fuera necesario.
No uses objetos Binder sin procesar en la API pública
Un objeto Binder
no tiene ningún significado por sí solo y, por lo tanto, no se debe usar en la API pública. Un caso de uso común es usar un Binder
o IBinder
como un token porque tiene semántica de identidad. En lugar de usar un objeto Binder
sin procesar, usa una clase de token de wrapper.
public final class IdentifiableObject {
public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
/**
* @hide
*/
public Binder getRawValue() {...}
/**
* @hide
*/
public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}
public final class IdentifiableObject {
public IdentifiableObjectToken getToken() {...}
}
Las clases de administrador deben ser definitivas
Las clases de administrador deben declararse como final
. Las clases de administrador se comunican con los servicios del sistema y son el único punto de interacción. No es necesario personalizarlo, así que decláralo como final
.
No uses CompletableFuture ni Future
java.util.concurrent.CompletableFuture
tiene una gran plataforma de API que permite la mutación arbitraria del valor futuro y tiene valores predeterminados propensos a errores.
Por el contrario, a java.util.concurrent.Future
le falta la escucha no bloqueante, lo que dificulta su uso con código asíncrono.
En el código de la plataforma y las APIs de bibliotecas de bajo nivel que consumen Kotlin y Java, prefiere una combinación de una devolución de llamada de finalización, Executor
, y, si la API admite la cancelación, CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
Si tu segmentación es para Kotlin, prefiere las funciones suspend
.
suspend fun asyncLoadFoo(): Foo
En las bibliotecas de integración específicas de Java, puedes usar ListenableFuture
de Guava.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
No uses Opcional
Si bien Optional
puede tener ventajas en algunas plataformas de API, no es coherente con el área de la plataforma de la API de Android existente. @Nullable
y @NonNull
proporcionan asistencia de herramientas para la seguridad de null
, y Kotlin aplica contratos de nulabilidad a nivel del compilador, lo que hace que Optional
sea innecesario.
Para las primitivas opcionales, usa los métodos has
y get
vinculados. Si el valor no se establece (has
muestra false
), el método get
debe generar un IllegalStateException
.
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
Usa constructores privados para clases no inicializables
Las clases que solo pueden crear Builder
, las que contienen solo constantes o métodos estáticos, o las que no se pueden crear instancias, deben incluir al menos un constructor privado para evitar la creación de instancias con el constructor predeterminado sin argumentos.
public final class Log {
// Not instantiable.
private Log() {}
}
Singletons
No se recomienda usar singleton porque tienen las siguientes desventajas relacionadas con las pruebas:
- La clase administra la construcción, lo que evita el uso de falsificaciones.
- Las pruebas no pueden ser herméticas debido a la naturaleza estática de un singleton.
- Para solucionar estos problemas, los desarrolladores deben conocer los detalles internos del singleton o crear un wrapper a su alrededor.
Prefiere el patrón de instancia única, que se basa en una clase base abstracta para abordar estos problemas.
Instancia única
Las clases de instancia única usan una clase base abstracta con un constructor private
o internal
y proporcionan un método getInstance()
estático para obtener una instancia. El método getInstance()
debe mostrar el mismo objeto en llamadas posteriores.
El objeto que muestra getInstance()
debe ser una implementación privada de la clase base abstracta.
class Singleton private constructor(...) {
companion object {
private val _instance: Singleton by lazy { Singleton(...) }
fun getInstance(): Singleton {
return _instance
}
}
}
abstract class SingleInstance private constructor(...) {
companion object {
private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
fun getInstance(): SingleInstance {
return _instance
}
}
}
La instancia única difiere del singleton en que los desarrolladores pueden crear una versión falsa de SingleInstance
y usar su propio framework de Dependency Injection para administrar la implementación sin tener que crear un wrapper, o la biblioteca puede proporcionar su propia falsificación en un artefacto -testing
.
Las clases que liberan recursos deben implementar AutoCloseable.
Las clases que liberan recursos a través de close
, release
, destroy
o métodos similares deben implementar java.lang.AutoCloseable
para permitir que los desarrolladores limpien automáticamente estos recursos cuando usan un bloque try-with-resources
.
Evita introducir nuevas subclases de View en android.*
No introduzcas clases nuevas que hereden directamente o indirectamente de android.view.View
en la API pública de la plataforma (es decir, en android.*
).
El kit de herramientas de la IU de Android ahora prioriza Compose. Las nuevas funciones de la IU que expone la plataforma deben exponerse como APIs de nivel inferior que se pueden usar para implementar Jetpack Compose y, de manera opcional, componentes de IU basados en objetos View para desarrolladores en bibliotecas de Jetpack. Ofrecer estos componentes en bibliotecas proporciona oportunidades para implementaciones con portabilidad a versiones anteriores cuando las funciones de la plataforma no están disponibles.
Campos
Estas reglas se refieren a los campos públicos en las clases.
No expongas campos sin procesar
Las clases de Java no deben exponer campos directamente. Los campos deben ser privados y solo se puede acceder a ellos con métodos get y set públicos, independientemente de si estos campos son finales o no.
Las excepciones poco frecuentes incluyen estructuras de datos básicas en las que no es necesario mejorar el comportamiento de especificar o recuperar un campo. En esos casos, los campos deben asignarse nombres con convenciones de nombres de variables estándar, por ejemplo, Point.x
y Point.y
.
Las clases de Kotlin pueden exponer propiedades.
Los campos expuestos deben marcarse como finales.
No se recomienda usar campos sin formato (@see No expongas campos sin formato). Sin embargo, en el caso poco frecuente en el que un campo se expone como un campo público, márcalo como final
.
Los campos internos no deben exponerse
No hagas referencia a nombres de campos internos en la API pública.
public int mFlags;
Usa público en lugar de protegido
@see Usa público en lugar de protegido
Constantes
Estas son reglas sobre constantes públicas.
Las constantes de marca no deben superponerse con valores int o long.
Marcas implica bits que se pueden combinar en algún valor de unión. Si no es así, no llames a la variable ni a la constante flag
.
public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;
Consulta @IntDef
para marcas de máscara de bits si necesitas más información para definir constantes de marcas públicas.
Las constantes finales estáticas deben usar una convención de nombres en mayúsculas separadas por guiones bajos.
Todas las palabras de la constante deben estar en mayúsculas y las varias palabras deben estar separadas por _
. Por ejemplo:
public static final int fooThing = 5
public static final int FOO_THING = 5
Usa prefijos estándar para las constantes
Muchas de las constantes que se usan en Android son para elementos estándar, como marcas, teclas y acciones. Estas constantes deben tener prefijos estándar para que sean más identificables como tales.
Por ejemplo, los extras de intent deben comenzar con EXTRA_
. Las acciones de intent deben comenzar con ACTION_
. Las constantes que se usan con Context.bindService()
deben comenzar con BIND_
.
Nombres y alcances de las constantes clave
Los valores de constantes de cadenas deben ser coherentes con el nombre de la constante y, por lo general, deben tener el alcance del paquete o dominio. Por ejemplo:
public static final String FOO_THING = "foo"
no tiene un nombre coherente ni un alcance adecuado. En su lugar, considera lo siguiente:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Los prefijos de android
en constantes de cadenas con alcance se reservan para el Proyecto de código abierto de Android.
Las acciones y los extras de intent, así como las entradas de paquete, deben tener un espacio de nombres con el nombre del paquete en el que se definen.
package android.foo.bar {
public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}
Usa público en lugar de protegido
@see Usa público en lugar de protegido
Usa prefijos coherentes
Todas las constantes relacionadas deben comenzar con el mismo prefijo. Por ejemplo, para un conjunto de constantes que se usarán con valores de marca, haz lo siguiente:
public static final int SOME_VALUE = 0x01;
public static final int SOME_OTHER_VALUE = 0x10;
public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;
public static final int FLAG_SOME_OTHER_VALUE = 0x10;
public static final int FLAG_SOME_THIRD_VALUE = 0x100;
@see Cómo usar prefijos estándar para constantes
Usa nombres de recursos coherentes
Los identificadores, atributos y valores públicos deben tener nombres que sigan la convención de nombres de CamelCase, por ejemplo, @id/accessibilityActionPageUp
o @attr/textAppearance
, similar a los campos públicos en Java.
En algunos casos, un identificador o atributo público incluye un prefijo común separado por un guion bajo:
- Valores de configuración de la plataforma, como
@string/config_recentsComponentName
en config.xml - Atributos de vista específicos del diseño, como
@attr/layout_marginStart
en attrs.xml
Los temas y estilos públicos deben seguir la convención de nombres jerárquica PascalCase, por ejemplo, @style/Theme.Material.Light.DarkActionBar
o @style/Widget.Material.SearchView.ActionBar
, similar a las clases anidadas en Java.
Los recursos de diseño y de elementos de diseño no deben exponerse como APIs públicas. Sin embargo, si deben exponerse, los diseños y elementos de diseño públicos deben tener un nombre que siga la convención de nombres de guion bajo, por ejemplo, layout/simple_list_item_1.xml
o drawable/title_bar_tall.xml
.
Cuando las constantes puedan cambiar, hazlas dinámicas.
El compilador puede intercalar valores constantes, por lo que mantener los valores iguales se considera parte del contrato de la API. Si el valor de una constante MIN_FOO
o MAX_FOO
podría cambiar en el futuro, considera convertirlos en métodos dinámicos.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Considera la compatibilidad con versiones posteriores para las devoluciones de llamada
Las apps que se orientan a versiones anteriores de la API no conocen las constantes definidas en versiones futuras de la API. Por este motivo, las constantes que se entregan a las apps deben tener en cuenta la versión de API objetivo de la app y asignar constantes más recientes a un valor coherente. Considera la siguiente situación:
Fuente hipotética del SDK:
// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;
App hipotética con targetSdkVersion="22"
:
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
En este caso, la app se diseñó dentro de las limitaciones del nivel de API 22 y se hizo una suposición (algo) razonable de que solo había dos estados posibles. Sin embargo, si la app recibe el STATUS_FAILURE_RETRY
agregado recientemente, lo interpreta como un éxito.
Los métodos que muestran constantes pueden controlar de forma segura casos como este limitando su salida para que coincida con el nivel de API al que se orienta la app:
private int mapResultForTargetSdk(Context context, int result) {
int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < 26) {
if (result == STATUS_FAILURE_ABORT) {
return STATUS_FAILURE;
}
if (targetSdkVersion < 23) {
if (result == STATUS_FAILURE_RETRY) {
return STATUS_FAILURE;
}
}
}
return result;
}
Los desarrolladores no pueden anticipar si una lista de constantes podría cambiar en el futuro. Si defines una API con una constante UNKNOWN
o UNSPECIFIED
que parece una opción general, los desarrolladores suponen que las constantes publicadas cuando escribieron su app son exhaustivas. Si no deseas establecer esta expectativa, reconsidera si una constante de captura general es una buena idea para tu API.
Además, las bibliotecas no pueden especificar su propio targetSdkVersion
separado de la app, y controlar los cambios de comportamiento de targetSdkVersion
desde el código de la biblioteca es complicado y propenso a errores.
Número entero o constante de cadena
Usa constantes de números enteros y @IntDef
si el espacio de nombres de los valores no es extensible fuera de tu paquete. Usa constantes de cadena si el espacio de nombres se comparte o se puede extender con código fuera de tu paquete.
Clases de datos
Las clases de datos representan un conjunto de propiedades inmutables y proporcionan un conjunto pequeño y bien definido de funciones de utilidad para interactuar con esos datos.
No uses data class
en las APIs públicas de Kotlin, ya que el compilador de Kotlin no garantiza la API del lenguaje ni la compatibilidad binaria para el código generado. En su lugar, implementa manualmente las funciones requeridas.
Creación de instancias
En Java, las clases de datos deben proporcionar un constructor cuando hay pocas propiedades o usar el patrón Builder
cuando hay muchas propiedades.
En Kotlin, las clases de datos deben proporcionar un constructor con argumentos predeterminados, independientemente de la cantidad de propiedades. Las clases de datos definidas en Kotlin también podrían beneficiarse de proporcionar un compilador cuando se segmentan clientes de Java.
Modificación y copia
En los casos en que se deban modificar los datos, proporciona una clase Builder
con un constructor de copia (Java) o una función miembro copy()
(Kotlin) que devuelva un objeto nuevo.
Cuando proporcionas una función copy()
en Kotlin, los argumentos deben coincidir con el constructor de la clase y los valores predeterminados deben propagarse con los valores actuales del objeto:
class Typography(
val labelMedium: TextStyle = TypographyTokens.LabelMedium,
val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
fun copy(
labelMedium: TextStyle = this.labelMedium,
labelSmall: TextStyle = this.labelSmall
): Typography = Typography(
labelMedium = labelMedium,
labelSmall = labelSmall
)
}
Comportamientos adicionales
Las clases de datos deben implementar equals()
y hashCode()
, y se debe tener en cuenta cada propiedad en las implementaciones de estos métodos.
Las clases de datos pueden implementar toString()
con un formato recomendado que coincida con la implementación de la clase de datos de Kotlin, por ejemplo, User(var1=Alex, var2=42)
.
Métodos
Estas son reglas sobre varios aspectos específicos de los métodos, como los parámetros, los nombres de los métodos, los tipos de datos que se devuelven y los especificadores de acceso.
Hora
Estas reglas abarcan cómo se deben expresar los conceptos de tiempo, como las fechas y la duración, en las APIs.
Siempre que sea posible, prefiere los tipos java.time.*
java.time.Duration
, java.time.Instant
y muchos otros tipos de java.time.*
están disponibles en todas las versiones de la plataforma a través de la expansión de sintaxis y deben preferirse cuando se expresa el tiempo en parámetros de API o valores que se muestran.
Se prefiere exponer solo variantes de una API que acepten o devuelvan java.time.Duration
o java.time.Instant
, y omitir variantes primitivas con el mismo caso de uso, a menos que el dominio de la API sea uno en el que la asignación de objetos en los patrones de uso previstos tenga un impacto prohibitivo en el rendimiento.
Los métodos que expresan duraciones deben llamarse duración
Si un valor de tiempo expresa la duración del tiempo involucrado, asigna el nombre "duration" al parámetro, no "time".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Excepciones:
“timeout” es apropiado cuando la duración se aplica específicamente a un valor de tiempo de espera.
"time" con un tipo de java.time.Instant
es apropiado cuando se hace referencia a un momento específico, no a una duración.
Los métodos que expresan duraciones o tiempos como una primitiva deben nombrarse con su unidad de tiempo y usar una cadena larga.
Los métodos que aceptan o muestran duraciones como una primitiva deben agregar un sufijo al nombre del método con las unidades de tiempo asociadas (como Millis
, Nanos
, Seconds
) para reservar el nombre sin decorar para usarlo con java.time.Duration
. Consulta Tiempo.
Los métodos también deben anotarse de forma adecuada con su unidad y base de tiempo:
@CurrentTimeMillisLong
: El valor es una marca de tiempo no negativa medida como la cantidad de milisegundos desde 1970-01-01T00:00:00Z.@CurrentTimeSecondsLong
: El valor es una marca de tiempo no negativa medida como la cantidad de segundos desde 1970-01-01T00:00:00Z.@DurationMillisLong
: El valor es una duración no negativa en milisegundos.@ElapsedRealtimeLong
: El valor es una marca de tiempo no negativa en la base de tiempoSystemClock.elapsedRealtime()
.@UptimeMillisLong
: El valor es una marca de tiempo no negativa en la base de tiempoSystemClock.uptimeMillis()
.
Los parámetros de tiempo primitivos o los valores que se muestran deben usar long
, no int
.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
Los métodos que expresan unidades de tiempo deben preferir la forma abreviada no abreviada para los nombres de las unidades.
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
Cómo anotar argumentos de tiempo prolongado
La plataforma incluye varias anotaciones para proporcionar un tipo más sólido para las unidades de tiempo de tipo long
:
@CurrentTimeMillisLong
: El valor es una marca de tiempo no negativa medida como la quantidad de milisegundos desde1970-01-01T00:00:00Z
, por lo tanto, en la base de tiempoSystem.currentTimeMillis()
.@CurrentTimeSecondsLong
: El valor es una marca de tiempo no negativa medida como la cantidad de segundos desde1970-01-01T00:00:00Z
.@DurationMillisLong
: El valor es una duración no negativa en milisegundos.@ElapsedRealtimeLong
: El valor es una marca de tiempo no negativa en la base de tiempoSystemClock#elapsedRealtime()
.@UptimeMillisLong
: El valor es una marca de tiempo no negativa en la base de tiempoSystemClock#uptimeMillis()
.
Unidades de medida
Para todos los métodos que expresan una unidad de medida distinta del tiempo, prefiere los prefijos de unidades del SI en mayúsculas y minúsculas.
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
Coloca los parámetros opcionales al final de las sobrecargas
Si tienes sobrecargas de un método con parámetros opcionales, mantén esos parámetros al final y mantén el orden coherente con los otros parámetros:
public int doFoo(boolean flag);
public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);
public int doFoo(boolean flag, int id);
Cuando se agregan sobrecargas para argumentos opcionales, el comportamiento de los métodos más simples debería ser exactamente el mismo que si se hubieran proporcionado argumentos predeterminados a los métodos más elaborados.
Corolario: No sobrecargas los métodos, excepto para agregar argumentos opcionales o para aceptar diferentes tipos de argumentos si el método es polimórfico. Si el método sobrecargado hace algo fundamentalmente diferente, asígnale un nombre nuevo.
Los métodos con parámetros predeterminados deben anotarse con @JvmOverloads (solo Kotlin).
Los métodos y los constructores con parámetros predeterminados deben anotarse con @JvmOverloads
para mantener la compatibilidad binaria.
Consulta Sobrecargas de funciones para valores predeterminados en la guía oficial de interoperabilidad de Kotlin-Java para obtener más detalles.
class Greeting @JvmOverloads constructor(
loudness: Int = 5
) {
@JvmOverloads
fun sayHello(prefix: String = "Dr.", name: String) = // ...
}
No quitar los valores de parámetros predeterminados (solo Kotlin)
Si un método se envió con un parámetro con un valor predeterminado, la eliminación del valor predeterminado es un cambio que genera errores de origen.
Los parámetros de método más distintivos y de identificación deben estar primero.
Si tienes un método con varios parámetros, coloca primero los más relevantes. Los parámetros que especifican marcas y otras opciones son menos importantes que los que describen el objeto sobre el que se actúa. Si hay una devolución de llamada de finalización, colócala al final.
public void openFile(int flags, String name);
public void openFileAsync(OnFileOpenedListener listener, String name, int flags);
public void setFlags(int mask, int flags);
public void openFile(String name, int flags);
public void openFileAsync(String name, int flags, OnFileOpenedListener listener);
public void setFlags(int flags, int mask);
Consulta también: Cómo colocar parámetros opcionales al final en sobrecargas
Compiladores
Se recomienda el patrón de Builder para crear objetos Java complejos y se usa con frecuencia en Android en los siguientes casos:
- Las propiedades del objeto resultante deben ser inmutables.
- Hay una gran cantidad de propiedades obligatorias, por ejemplo, muchos argumentos del constructor.
- Existe una relación compleja entre las propiedades en el momento de la construcción, por ejemplo, se requiere un paso de verificación. Ten en cuenta que este nivel de complejidad a menudo indica problemas con la usabilidad de la API.
Considera si necesitas un compilador. Los compiladores son útiles en una plataforma de API si se usan para lo siguiente:
- Configura solo algunos de un conjunto potencialmente grande de parámetros de creación opcionales
- Configurar muchos parámetros de creación opcionales o obligatorios diferentes, a veces de tipos similares o coincidentes, en los que los sitios de llamada podrían ser difíciles de leer o propensos a errores de escritura
- Configura la creación de un objeto de forma incremental, en la que varios fragmentos diferentes de código de configuración podrían realizar llamadas al compilador como detalles de implementación.
- Permite que un tipo crezca agregando parámetros de creación opcionales adicionales en versiones futuras de la API.
Si tienes un tipo con tres o menos parámetros obligatorios y sin parámetros opcionales, casi siempre puedes omitir un compilador y usar un constructor simple.
Las clases de origen de Kotlin deben preferir los constructores con anotaciones @JvmOverloads
con argumentos predeterminados en lugar de los compiladores, pero pueden optar por mejorar la usabilidad para los clientes de Java si también proporcionan compiladores en los casos descritos anteriormente.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
Las clases de compilador deben mostrar el compilador
Las clases de compilador deben habilitar la encadenación de métodos devolviendo el objeto de compilador (como this
) desde todos los métodos, excepto build()
. Los objetos compilados adicionales se deben pasar como argumentos. No devuelvas el compilador de un objeto diferente.
Por ejemplo:
public static class Builder {
public void setDuration(long);
public void setFrequency(int);
public DtmfConfigBuilder addDtmfConfig();
public Tone build();
}
public class Tone {
public static class Builder {
public Builder setDuration(long);
public Builder setFrequency(int);
public Builder addDtmfConfig(DtmfConfig);
public Tone build();
}
}
En casos excepcionales en los que una clase de compilador base debe admitir la extensión, usa un tipo de valor que se muestra genérico:
public abstract class Builder<T extends Builder<T>> {
abstract T setValue(int);
}
public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
T setValue(int);
T setTypeSpecificValue(long);
}
Las clases de generador se deben crear a través de un constructor.
Para mantener una creación de compiladores coherente a través de la plataforma de la API de Android, todos los compiladores deben crearse a través de un constructor y no de un método de creador estático. En el caso de las APIs basadas en Kotlin, el Builder
debe ser público, incluso si se espera que los usuarios de Kotlin dependan implícitamente del compilador a través de un método de fábrica o un mecanismo de creación de estilo DSL. Las bibliotecas no deben usar @PublishedApi internal
para ocultar de forma selectiva el constructor de la clase Builder
a los clientes de Kotlin.
public class Tone {
public static Builder builder();
public static class Builder {
}
}
public class Tone {
public static class Builder {
public Builder();
}
}
Todos los argumentos de los constructores de generadores deben ser obligatorios (como @NonNull).
Los argumentos opcionales, por ejemplo, @Nullable
, se deben mover a los métodos set.
El constructor del compilador debe arrojar un NullPointerException
(considera usar Objects.requireNonNull
) si no se especifican los argumentos obligatorios.
Las clases de Builder deben ser clases internas estáticas finales de sus tipos compilados.
Por motivos de organización lógica dentro de un paquete, las clases de generadores suelen exponerse como clases internas finales de sus tipos compilados, por ejemplo, Tone.Builder
en lugar de ToneBuilder
.
Los compiladores pueden incluir un constructor para crear una instancia nueva a partir de una existente.
Los compiladores pueden incluir un constructor de copia para crear una instancia de compilador nueva a partir de un compilador existente o un objeto compilado. No deben proporcionar métodos alternativos para crear instancias de compiladores a partir de compiladores o objetos de compilación existentes.
public class Tone {
public static class Builder {
public Builder clone();
}
public Builder toBuilder();
}
public class Tone {
public static class Builder {
public Builder(Builder original);
public Builder(Tone original);
}
}
Los métodos set del compilador deben tomar argumentos @Nullable si el compilador tiene un constructor de copia.
El restablecimiento es esencial si se puede crear una instancia nueva de un compilador a partir de una instancia existente. Si no hay un constructor de copia disponible, el compilador puede tener argumentos @Nullable
o @NonNullable
.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
Los set de compilador pueden tomar argumentos @Nullable para propiedades opcionales.
A menudo, es más sencillo usar un valor nulo para la entrada de segundo grado, en especial en Kotlin, que usa argumentos predeterminados en lugar de compiladores y sobrecargas.
Además, los set de @Nullable
los harán coincidir con sus get, que deben ser @Nullable
para las propiedades opcionales.
Value createValue(@Nullable OptionalValue optionalValue) {
Value.Builder builder = new Value.Builder();
if (optionalValue != null) {
builder.setOptionalValue(optionalValue);
}
return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
return new Value.Builder()
.setOptionalValue(optionalValue);
.build();
}
// Or in other cases:
Value createValue() {
return new Value.Builder()
.setOptionalValue(condition ? new OptionalValue() : null);
.build();
}
Uso común en Kotlin:
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
El valor predeterminado (si no se llama al set) y el significado de null
deben documentarse correctamente en el set y el get.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Se pueden proporcionar métodos set del compilador para propiedades mutables en las que los métodos set están disponibles en la clase compilada.
Si tu clase tiene propiedades mutables y necesita una clase Builder
, primero pregúntate si tu clase realmente debería tener propiedades mutables.
A continuación, si tienes la certeza de que necesitas propiedades mutables, decide cuál de las siguientes situaciones funciona mejor para tu caso de uso esperado:
El objeto compilado debe poder usarse de inmediato, por lo que se deben proporcionar seters para todas las propiedades relevantes, ya sean mutables o inmutables.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Es posible que debas realizar algunas llamadas adicionales antes de que el objeto compilado pueda ser útil, por lo que no se deben proporcionar seters para propiedades mutables.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
No mezcles las dos situaciones.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
Los compiladores no deben tener getters.
El método get debe estar en el objeto compilado, no en el compilador.
Los métodos set del compilador deben tener los métodos get correspondientes en la clase compilada.
public class Tone {
public static class Builder {
public Builder setDuration(long);
public Builder setFrequency(int);
public Builder addDtmfConfig(DtmfConfig);
public Tone build();
}
}
public class Tone {
public static class Builder {
public Builder setDuration(long);
public Builder setFrequency(int);
public Builder addDtmfConfig(DtmfConfig);
public Tone build();
}
public long getDuration();
public int getFrequency();
public @NonNull List<DtmfConfig> getDtmfConfigs();
}
Nombres de métodos del compilador
Los nombres de los métodos del compilador deben usar el estilo setFoo()
, addFoo()
o clearFoo()
.
Se espera que las clases de compilador declaren un método build().
Las clases de generador deben declarar un método build()
que devuelva una instancia del objeto construido.
Los métodos build() del compilador deben mostrar objetos @NonNull
Se espera que el método build()
de un compilador devuelva una instancia no nula del objeto construido. En caso de que no se pueda crear el objeto debido a parámetros no válidos, la validación se puede aplazar al método de compilación y se debe generar un IllegalStateException
.
No expongas bloqueos internos
Los métodos de la API pública no deben usar la palabra clave synchronized
. Esta palabra clave hace que tu objeto o clase se use como bloqueo y, como está expuesta a otros, es posible que experimentes efectos secundarios inesperados si otro código fuera de tu clase comienza a usarlo con fines de bloqueo.
En su lugar, realiza el bloqueo necesario en un objeto interno y privado.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Los métodos de estilo de acceso deben seguir los lineamientos de propiedades de Kotlin
Cuando se vean desde fuentes de Kotlin, los métodos de estilo de acceso, es decir, los que usan los prefijos get
, set
o is
, también estarán disponibles como propiedades de Kotlin.
Por ejemplo, int getField()
definido en Java está disponible en Kotlin como la propiedad val field: Int
.
Por este motivo, y para satisfacer en general las expectativas de los desarrolladores en cuanto al comportamiento de los métodos de acceso, los métodos que usan prefijos de métodos de acceso deben comportarse de manera similar a los campos de Java. Evita usar prefijos de estilo de acceso en los siguientes casos:
- El método tiene efectos secundarios. Se prefiere un nombre de método más descriptivo.
- El método implica un trabajo costoso en términos de procesamiento. Se prefiere
compute
. - El método implica bloquear o realizar un trabajo de larga duración para mostrar un valor, como IPC o alguna otra E/S. Se prefiere
fetch
. - El método bloquea el subproceso hasta que puede mostrar un valor. Se prefiere
await
. - El método muestra una nueva instancia de objeto en cada llamada. Se prefiere
create
. - Es posible que el método no muestre un valor correctamente. Se prefiere
request
.
Ten en cuenta que realizar un trabajo costoso en términos de procesamiento una vez y almacenar en caché el valor para las llamadas posteriores sigue contando como realizar un trabajo costoso en términos de procesamiento. El bloqueo no se amortiza en los fotogramas.
Usa el prefijo is para los métodos de acceso booleanos
Esta es la convención de nombres estándar para los métodos y campos booleanos en Java. Por lo general, los nombres de métodos y variables booleanos deben escribirse como preguntas que responde el valor que se muestra.
Los métodos de acceso booleanos de Java deben seguir un esquema de nombres set
/is
, y los campos deben preferir is
, como en el siguiente ejemplo:
// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();
// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();
final boolean isAvailable;
El uso de set
/is
para los métodos de acceso de Java o is
para los campos de Java permitirá que se usen como propiedades desde Kotlin:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
Por lo general, las propiedades y los métodos de acceso deben usar nombres positivos, por ejemplo, Enabled
en lugar de Disabled
. El uso de terminología negativa invierte el significado de true
y false
, y dificulta la inferencia sobre el comportamiento.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
En los casos en que el valor booleano describe la inclusión o la propiedad de una propiedad, puedes usar has en lugar de is. Sin embargo, esto no funcionará con la sintaxis de propiedades de Kotlin:
// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();
Algunos prefijos alternativos que pueden ser más adecuados son can y should:
// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();
// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();
Los métodos que activan o desactivan comportamientos o funciones pueden usar el prefijo is y el sufijo Enabled:
// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()
Del mismo modo, los métodos que indican la dependencia de otros comportamientos o funciones pueden usar el prefijo is y el sufijo Supported o Required:
// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()
Por lo general, los nombres de los métodos deben escribirse como preguntas que el valor que se muestra responde.
Métodos de propiedades de Kotlin
Para una propiedad de clase var foo: Foo
, Kotlin generará métodos get
/set
con una regla coherente: antepone get
y escribe en mayúsculas el primer carácter del método get, y antepone set
y escribe en mayúsculas el primer carácter del método set. La declaración de la propiedad producirá métodos llamados public Foo getFoo()
y public void setFoo(Foo foo)
, respectivamente.
Si la propiedad es del tipo Boolean
, se aplica una regla adicional en la generación de nombres: si el nombre de la propiedad comienza con is
, no se agrega get
al nombre del método get, sino que se usa el nombre de la propiedad como el get.
Por lo tanto, prefieren nombrar las propiedades Boolean
con un prefijo is
para seguir los lineamientos de nombres:
var isVisible: Boolean
Si tu propiedad es una de las excepciones mencionadas anteriormente y comienza con un prefijo apropiado, usa la anotación @get:JvmName
en la propiedad para especificar manualmente el nombre adecuado:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
Accesores de máscara de bits
Consulta Cómo usar @IntDef
para marcas de máscara de bits para conocer los lineamientos de la API sobre la definición de marcas de máscara de bits.
Métodos set
Se deben proporcionar dos métodos set: uno que tome una cadena de bits completa y escriba sobre todas las marcas existentes, y otro que tome una máscara de bits personalizada para permitir más flexibilidad.
/**
* Sets the state of all scroll indicators.
* <p>
* See {@link #setScrollIndicators(int, int)} for usage information.
*
* @param indicators a bitmask of indicators that should be enabled, or
* {@code 0} to disable all indicators
* @see #setScrollIndicators(int, int)
* @see #getScrollIndicators()
*/
public void setScrollIndicators(@ScrollIndicators int indicators);
/**
* Sets the state of the scroll indicators specified by the mask. To change
* all scroll indicators at once, see {@link #setScrollIndicators(int)}.
* <p>
* When a scroll indicator is enabled, it will be displayed if the view
* can scroll in the direction of the indicator.
* <p>
* Multiple indicator types may be enabled or disabled by passing the
* logical OR of the specified types. If multiple types are specified, they
* will all be set to the same enabled state.
* <p>
* For example, to enable the top scroll indicator:
* {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
* <p>
* To disable the top scroll indicator:
* {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
*
* @param indicators a bitmask of values to set; may be a single flag,
* the logical OR of multiple flags, or 0 to clear
* @param mask a bitmask indicating which indicator flags to modify
* @see #setScrollIndicators(int)
* @see #getScrollIndicators()
*/
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);
Métodos get
Se debe proporcionar un método get para obtener la máscara de bits completa.
/**
* Returns a bitmask representing the enabled scroll indicators.
* <p>
* For example, if the top and left scroll indicators are enabled and all
* other indicators are disabled, the return value will be
* {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
* <p>
* To check whether the bottom scroll indicator is enabled, use the value
* of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
*
* @return a bitmask representing the enabled scroll indicators
*/
@ScrollIndicators
public int getScrollIndicators();
Usa público en lugar de protegido
Siempre prefiere public
a protected
en la API pública. El acceso protegido termina siendo problemático a largo plazo, ya que los implementadores deben anularlo para proporcionar accesores públicos en los casos en que el acceso externo de forma predeterminada habría sido igual de bueno.
Recuerda que la visibilidad de protected
no impide que los desarrolladores llamen a una API, solo la hace un poco más molesta.
No implementes equals() ni hashCode(), o bien implementa ambos.
Si anulas uno, debes anular el otro.
Implementa toString() para clases de datos
Se recomienda que las clases de datos anulen toString()
para ayudar a los desarrolladores a depurar
su código.
Documenta si el resultado es para el comportamiento del programa o la depuración
Decide si deseas que el comportamiento del programa dependa de tu implementación o no. Por ejemplo, UUID.toString() y File.toString() documentan su formato específico para que lo usen los programas. Si expones información solo para depuración, como Intent, infiere la herencia de documentos de la superclase.
No incluyas información adicional
Toda la información disponible de toString()
también debería estar disponible a través de la API pública del objeto. De lo contrario, estarás alentando a los desarrolladores a analizar y depender de tu salida de toString()
, lo que evitará cambios futuros. Una práctica recomendada es implementar toString()
solo con la API pública del objeto.
Desalentar la dependencia del resultado de la depuración
Si bien es imposible evitar que los desarrolladores dependan del resultado de la depuración, si incluyes el System.identityHashCode
de tu objeto en su resultado toString()
, será muy poco probable que dos objetos diferentes tengan un resultado toString()
igual.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
Esto puede disuadir de manera eficaz a los desarrolladores de escribir aserciones de prueba como assertThat(a.toString()).isEqualTo(b.toString())
en tus objetos.
Usa createFoo cuando devuelvas objetos recién creados
Usa el prefijo create
, no get
ni new
, para los métodos que crearán valores devueltos, por ejemplo, mediante la construcción de objetos nuevos.
Cuando el método cree un objeto para mostrar, hazlo claro en el nombre del método.
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
Los métodos que aceptan objetos File también deben aceptar flujos.
Las ubicaciones de almacenamiento de datos en Android no siempre son archivos en el disco. Por ejemplo, el contenido que se pasa a través de los límites del usuario se representa como content://
Uri
. Para permitir el procesamiento de varias fuentes de datos, las APIs que aceptan objetos File
también deben aceptar InputStream
, OutputStream
o ambos.
public void setDataSource(File file)
public void setDataSource(InputStream stream)
Toma y muestra primitivas sin procesar en lugar de versiones encuadradas
Si necesitas comunicar valores faltantes o nulos, considera usar -1
, Integer.MAX_VALUE
o Integer.MIN_VALUE
.
public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)
Evitar los equivalentes de clase de los tipos primitivos evita la sobrecarga de memoria de estas clases, el acceso de métodos a valores y, lo que es más importante, la conversión automática que proviene del lanzamiento entre tipos primitivos y de objetos. Evitar estos comportamientos ahorra memoria y asignaciones temporales que pueden generar reconstrucciones de elementos no usados costosas y más frecuentes.
Usa anotaciones para aclarar los parámetros válidos y los valores que se muestran
Se agregaron anotaciones para desarrolladores para ayudar a aclarar los valores permitidos en varias situaciones. Esto facilita que las herramientas ayuden a los desarrolladores cuando proporcionan valores incorrectos (por ejemplo, pasar un int
arbitrario cuando el framework requiere uno de un conjunto específico de valores constantes). Usa cualquiera de las siguientes anotaciones cuando corresponda:
Nulabilidad
Las anotaciones de nulabilidad explícitas son obligatorias para las APIs de Java, pero el concepto de nulabilidad es parte del lenguaje Kotlin, y las anotaciones de nulabilidad nunca deben usarse en las APIs de Kotlin.
@Nullable
: Indica que un valor que se muestra, un parámetro o un campo determinado puede ser nulo:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: Indica que un valor que se muestra, un parámetro o un campo determinado no puede ser nulo. Marcar elementos como @Nullable
es relativamente nuevo para Android, por lo que la mayoría de los métodos de la API de Android no se documentan de forma coherente. Por lo tanto, tenemos un estado de tres valores de "desconocido, @Nullable
, @NonNull
", por lo que @NonNull
forma parte de los lineamientos de la API:
@NonNull
public String getName()
public void setName(@NonNull String name)
En el caso de la documentación de la plataforma de Android, si anotas los parámetros de tu método, se generará automáticamente la documentación en el formato "Este valor puede ser nulo", a menos que se use "nulo" de forma explícita en otra parte de la documentación del parámetro.
Métodos existentes que no son realmente nulos: Los métodos existentes en la API sin una anotación @Nullable
declarada se pueden anotar como @Nullable
si el método puede mostrar null
en circunstancias específicas y obvias (como findViewById()
). Se deben agregar métodos @NotNull requireFoo()
complementarios que arrojen IllegalArgumentException
para los desarrolladores que no quieran realizar una verificación nula.
Métodos de interfaz: Las APIs nuevas deben agregar la anotación adecuada cuando implementan métodos de interfaz, como Parcelable.writeToParcel()
(es decir, ese método en la clase de implementación debe ser writeToParcel(@NonNull Parcel,
int)
, no writeToParcel(Parcel, int)
). Sin embargo, no es necesario que se "arreglen" las APIs existentes que no tienen las anotaciones.
Aplicación de nulabilidad
En Java, se recomienda usar métodos para realizar la validación de entradas de los parámetros @NonNull
con Objects.requireNonNull()
y arrojar un NullPointerException
cuando los parámetros sean nulos. Esto se realiza automáticamente en Kotlin.
Recursos
Identificadores de recursos: Los parámetros de número entero que denotan IDs para recursos específicos deben anotarse con la definición de tipo de recurso adecuada.
Hay una anotación para cada tipo de recurso, como @StringRes
, @ColorRes
y @AnimRes
, además de @AnyRes
de uso general. Por ejemplo:
public void setTitle(@StringRes int resId)
@IntDef para conjuntos de constantes
Constantes mágicas: Los parámetros String
y int
que están destinados a recibir uno
de un conjunto finito de valores posibles denotados por constantes públicas deben
annotarse de manera adecuada con @StringDef
o @IntDef
. Estas anotaciones te permiten crear una anotación nueva que funciona como un typedef para los parámetros permitidos. Por ejemplo:
/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
NAVIGATION_MODE_STANDARD,
NAVIGATION_MODE_LIST,
NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);
Se recomienda usar métodos para verificar la validez de los parámetros anotados y arrojar un IllegalArgumentException
si el parámetro no forma parte de @IntDef
.
@IntDef para marcas de máscara de bits
La anotación también puede especificar que las constantes son marcas y se pueden combinar con & y I:
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_USE_LOGO,
FLAG_SHOW_HOME,
FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}
@StringDef para conjuntos de constantes de cadenas
También está la anotación @StringDef
, que es exactamente como @IntDef
en la sección anterior, pero para constantes String
. Puedes incluir varios valores de “prefijo” que se usan para emitir automáticamente la documentación para todos los valores.
@SdkConstant para constantes de SDK
@SdkConstant Anota los campos públicos cuando sean uno de estos valores de SdkConstant
: ACTIVITY_INTENT_ACTION
, BROADCAST_INTENT_ACTION
, SERVICE_ACTION
, INTENT_CATEGORY
o FEATURE
.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";
Proporciona una nulidad compatible para las anulaciones
Para la compatibilidad con la API, la nulidad de las anulaciones debe ser compatible con la nulidad actual del elemento superior. En la siguiente tabla, se representan las expectativas de compatibilidad. En términos simples, las anulaciones solo deben ser tan restrictivas o más restrictivas que el elemento que anulan.
Tipo | Madre o padre | Hijo o hija |
---|---|---|
Tipo de datos que se muestra | Sin anotaciones | Sin anotaciones o no nulo |
Tipo de datos que se muestra | Anulable | Nullable o no |
Tipo de datos que se muestra | Nonnull | Nonnull |
Argumento divertido | Sin anotaciones | Sin anotaciones o con valores nulos |
Argumento divertido | Anulable | Anulable |
Argumento divertido | Nonnull | Nullable o no |
Siempre que sea posible, prefiere los argumentos no nulos (como @NonNull).
Cuando se sobrecargan los métodos, prefiere que todos los argumentos no sean nulos.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
Esta regla también se aplica a los set de propiedades sobrecargados. El argumento principal no debe ser nulo, y la limpieza de la propiedad debe implementarse como un método independiente. Esto evita llamadas "sin sentido" en las que el desarrollador debe establecer parámetros finales aunque no sean obligatorios.
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)
// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()
Prefiere tipos de retorno no nulos (como @NonNull) para los contenedores.
Para tipos de contenedores, como Bundle
o Collection
, muestra un contenedor vacío (y, cuando corresponda, inmutable). En los casos en que se usaría null
para distinguir la disponibilidad de un contenedor, considera proporcionar un método booleano independiente.
@NonNull
public Bundle getExtras() { ... }
Las anotaciones de anulabilidad para los pares get y set deben coincidir
Los pares de métodos get y set para una sola propiedad lógica siempre deben coincidir en sus anotaciones de anulabilidad. Si no sigues este lineamiento, se anulará la sintaxis de propiedades de Kotlin y, por lo tanto, agregar anotaciones de nulidad en desacuerdo a los métodos de propiedades existentes es un cambio que genera errores de origen para los usuarios de Kotlin.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Devuelve un valor en condiciones de falla o error
Todas las APIs deben permitir que las apps reaccionen a los errores. Devolver false
, -1
, null
o cualquier otro valor genérico de "se produjo un error" no le informa al desarrollador lo suficiente sobre la falla para establecer las expectativas del usuario o hacer un seguimiento preciso de la confiabilidad de su app en el campo. Cuando diseñes una API, imagina que estás compilando una app. Si encuentras un error, ¿la API te brinda suficiente información para presentarlo al usuario o reaccionar de manera apropiada?
- Está bien (y se recomienda) incluir información detallada en un mensaje de excepción, pero los desarrolladores no deberían tener que analizarla para controlar el error de manera adecuada. Los códigos de error detallados o cualquier otra información deben exponerse como métodos.
- Asegúrate de que la opción de manejo de errores que elijas te brinde la flexibilidad para introducir nuevos tipos de errores en el futuro. Para
@IntDef
, eso significa incluir un valorOTHER
oUNKNOWN
. Cuando devuelves un código nuevo, puedes verificar eltargetSdkVersion
del llamador para evitar mostrar un código de error que la app no conozca. Para las excepciones, ten una superclase común que implementen tus excepciones, de modo que cualquier código que controle ese tipo también detecte y controle los subtipos. - Para un desarrollador, debería ser difícil o imposible ignorar un error por accidente. Si tu error se comunica devolviendo un valor, anota tu método con
@CheckResult
.
Se prefiere lanzar un ? extends RuntimeException
cuando se alcanza una falla o una condición de error debido a algo que el desarrollador hizo mal, por ejemplo, ignorar las restricciones en los parámetros de entrada o no verificar el estado observable.
Los métodos set o de acción (por ejemplo, perform
) pueden mostrar un código de estado de número entero si la acción puede fallar como resultado de un estado actualizado de forma asíncrona o condiciones fuera del control del desarrollador.
Los códigos de estado deben definirse en la clase contenedora como campos public static final
, con el prefijo ERROR_
y enumerarse en una anotación @IntDef
@hide
.
Los nombres de los métodos siempre deben comenzar con el verbo, no con el sujeto.
El nombre del método siempre debe comenzar con el verbo (como get
, create
, reload
, etc.), no con el objeto en el que actúas.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Prefiere los tipos de Collection en lugar de los arrays como tipo de parámetro o devuelto
Las interfaces de colecciones con tipos genéricos proporcionan varias ventajas sobre los arrays, incluidos contratos de API más sólidos en torno a la unicidad y el orden, compatibilidad con genéricos y una serie de métodos de conveniencia fáciles de usar para los desarrolladores.
Excepción para primitivos
Si los elementos son primitivos, prefiere los arrays para evitar el costo del autoencapsulamiento. Consulta Cómo tomar y mostrar primitivas sin procesar en lugar de versiones en formato de cuadro.
Excepción para el código sensible al rendimiento
En ciertas situaciones, en las que la API se usa en código sensible al rendimiento (como gráficos o alguna otra API de medición, diseño o dibujo), es aceptable usar arrays en lugar de colecciones para reducir las asignaciones y la rotación de memoria.
Excepción para Kotlin
Los arrays de Kotlin son invariables y el lenguaje Kotlin proporciona amplias APIs de utilidad para los arrays, por lo que los arrays están a la par de List
y Collection
para las APIs de Kotlin a las que se pretende acceder desde Kotlin.
Prefiere colecciones @NonNull
Siempre prefiere @NonNull
para los objetos de colección. Cuando devuelvas una recopilación vacía, usa el método Collections.empty
adecuado para mostrar un objeto de recopilación inmutable, correctamente tipado y de bajo costo.
Cuando se admitan anotaciones de tipo, siempre prefiere @NonNull
para los elementos de la recopilación.
También debes preferir @NonNull
cuando uses arrays en lugar de colecciones (consulta el elemento anterior). Si te preocupa la asignación de objetos, crea una constante y pásala. Después de todo, un array vacío es inmutable. Ejemplo:
private static final int[] EMPTY_USER_IDS = new int[0];
@NonNull
public int[] getUserIds() {
int [] userIds = mService.getUserIds();
return userIds != null ? userIds : EMPTY_USER_IDS;
}
Mutabilidad de la colección
Las APIs de Kotlin deben preferir los tipos de objetos que se devuelven de solo lectura (no Mutable
) para las colecciones de forma predeterminada a menos que el contrato de la API requiera específicamente un tipo de objeto que se devuelve mutable.
Sin embargo, las APIs de Java deben preferir los tipos de retorno mutables de forma predeterminada, ya que la implementación de las APIs de Java en la plataforma de Android aún no proporciona una implementación conveniente de colecciones inmutables. La excepción a esta regla son los tipos de datos que se muestran de Collections.empty
, que son inmutables. En los casos en que los clientes puedan aprovechar la mutabilidad, ya sea de forma intencional o por error, para romper el patrón de uso previsto de la API, las APIs de Java deben considerar seriamente mostrar una copia superficial de la colección.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
Tipos de datos que se devuelven de forma explícita
Idealmente, las APIs que muestran colecciones no deberían modificar el objeto de la colección que se muestra después de mostrarlo. Si la colección que se muestra debe cambiar o reutilizarse de alguna manera (por ejemplo, una vista adaptada de un conjunto de datos mutable), el comportamiento preciso de cuándo puede cambiar el contenido debe documentarse de forma explícita o seguir las convenciones de nombres de API establecidas.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
La convención .asFoo()
de Kotlin se describe a continuación y permite que la colección que muestra .asList()
cambie si cambia la colección original.
Mutabilidad de los objetos de tipo de datos que se devuelven
Al igual que las APIs que muestran colecciones, las que muestran objetos de tipo de datos, idealmente, no deberían modificar las propiedades del objeto que se muestra después de mostrarlo.
val tempResult = DataContainer()
fun add(other: DataContainer): DataContainer {
tempResult.innerValue = innerValue + other.innerValue
return tempResult
}
fun add(other: DataContainer): DataContainer {
return DataContainer(innerValue + other.innerValue)
}
En casos extremadamente limitados, es posible que algunos códigos sensibles al rendimiento se beneficien de la agrupación o reutilización de objetos. No escribas tu propia estructura de datos del grupo de objetos ni expongas objetos reutilizados en APIs públicas. En cualquier caso, ten mucho cuidado a la hora de administrar el acceso simultáneo.
Uso del tipo de parámetro vararg
Se recomienda que las APIs de Kotlin y Java usen vararg
en los casos en que el desarrollador sea probable que cree un array en el sitio de llamada con el único propósito de pasar varios parámetros relacionados del mismo tipo.
public void setFeatures(Feature[] features) { ... }
// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }
// Developer code
setFeatures(Features.A, Features.B, Features.C);
Copias defensivas
Las implementaciones de Java y Kotlin de los parámetros vararg
se compilan en el mismo código de bytes con respaldo de array y, como resultado, se pueden llamar desde el código Java con un array mutable. Se recomienda a los diseñadores de la API que creen una copia superficial defensiva del parámetro de array en los casos en que se conservará en un campo o en una clase interna anónima.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
Ten en cuenta que crear una copia defensiva no proporciona ninguna protección contra la modificación simultánea entre la llamada al método inicial y la creación de la copia, ni protege contra la mutación de los objetos contenidos en el array.
Proporciona semántica correcta con parámetros de tipo de colección o tipos que se muestran
List<Foo>
es la opción predeterminada, pero considera otros tipos para proporcionar un significado adicional:
Usa
Set<Foo>
si tu API es indiferente al orden de los elementos y no permite duplicados o si los duplicados no tienen sentido.Collection<Foo>,
si tu API es indiferente al orden y permite duplicados.
Funciones de conversión de Kotlin
Kotlin usa con frecuencia .toFoo()
y .asFoo()
para obtener un objeto de un tipo diferente de un objeto existente, en el que Foo
es el nombre del tipo de datos que muestra la conversión. Esto es coherente con el JDK Object.toString()
familiar. Kotlin lleva esto más allá y lo usa para conversiones primitivas, como 25.toFloat()
.
La distinción entre las conversiones denominadas .toFoo()
y .asFoo()
es significativa:
Usa .toFoo() cuando crees un objeto nuevo e independiente.
Al igual que .toString()
, una conversión "a" muestra un objeto nuevo e independiente. Si el objeto original se modifica más adelante, el objeto nuevo no reflejará esos cambios.
Del mismo modo, si el objeto nuevo se modifica más adelante, el objeto anterior no reflejará esos cambios.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
Usa .asFoo() cuando crees un wrapper dependiente, un objeto decorado o una transmisión
La transmisión en Kotlin se realiza con la palabra clave as
. Refleja un cambio en la interfaz, pero no en la identidad. Cuando se usa como prefijo en una función de extensión, .asFoo()
decora el receptor. Una mutación en el objeto receptor original se reflejará en el objeto que devuelve asFoo()
.
Una mutación en el nuevo objeto Foo
puede reflejarse en el objeto original.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
Las funciones de conversión deben escribirse como funciones de extensión
Escribir funciones de conversión fuera de las definiciones del receptor y de la clase de resultado reduce la vinculación entre los tipos. Una conversión ideal solo necesita acceso a la API pública del objeto original. Esto demuestra con un ejemplo que un desarrollador también puede escribir conversiones análogas a sus propios tipos preferidos.
Lanza las excepciones específicas adecuadas
Los métodos no deben arrojar excepciones genéricas, como java.lang.Exception
o java.lang.Throwable
. En su lugar, se debe usar una excepción específica adecuada, como java.lang.NullPointerException
, para permitir que los desarrolladores manejen excepciones sin ser demasiado amplias.
Los errores que no están relacionados con los argumentos proporcionados directamente al método invocado públicamente deben arrojar java.lang.IllegalStateException
en lugar de java.lang.IllegalArgumentException
o java.lang.NullPointerException
.
Objetos de escucha y devoluciones de llamadas
Estas son las reglas sobre las clases y los métodos que se usan para los mecanismos de objetos de escucha y de devolución de llamada.
Los nombres de las clases de devolución de llamada deben ser singulares.
excepto porque se usa MyObjectCallback
en lugar de MyObjectCallbacks
.
Los nombres de los métodos de devolución de llamada deben tener el formato de
onFooEvent
indica que se está produciendo FooEvent
y que la devolución de llamada debe actuar en respuesta.
El tiempo pasado en comparación con el presente debe describir el comportamiento de los tiempos.
Los métodos de devolución de llamada relacionados con los eventos deben tener un nombre que indique si el evento ya ocurrió o está en proceso de hacerlo.
Por ejemplo, si se llama al método después de que se realiza una acción de clic, sucede lo siguiente:
public void onClicked()
Sin embargo, si el método es responsable de realizar la acción de clic, haz lo siguiente:
public boolean onClick()
Registro de devolución de llamada
Cuando se puede agregar o quitar un objeto de escucha o devolución de llamada de un objeto, los métodos asociados deben denominarse agregar y quitar o registrar y anular el registro. Sé coherente con la convención existente que usa la clase o otras clases del mismo paquete. Cuando no exista tal precedente, prefiere agregar y quitar.
Los métodos que implican el registro o la cancelación del registro de devoluciones de llamada deben especificar el nombre completo del tipo de devolución de llamada.
public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);
Evita los métodos get para las devoluciones de llamada
No agregues métodos getFooCallback()
. Esta es una salida tentadora para los casos en los que los desarrolladores pueden querer encadenar una devolución de llamada existente con su propio reemplazo, pero es inestable y dificulta el razonamiento sobre el estado actual para los desarrolladores de componentes. Por ejemplo:
- El desarrollador A llama a
setFooCallback(a)
. - El desarrollador B llama a
setFooCallback(new B(getFooCallback()))
. - El desarrollador A desea quitar su devolución de llamada
a
y no tiene forma de hacerlo sin conocer el tipo deB
, yB
se compiló para permitir esas modificaciones de su devolución de llamada unida.
Acepta el ejecutor para controlar el envío de devoluciones de llamada
Cuando se registran devoluciones de llamada que no tienen expectativas de subprocesos explícitas (casi en cualquier lugar fuera del kit de herramientas de la IU), se recomienda incluir un parámetro Executor
como parte del registro para permitir que el desarrollador especifique el subproceso en el que se invocarán las devoluciones de llamada.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Como excepción a nuestros lineamientos habituales sobre los parámetros opcionales, se acepta proporcionar una sobrecarga que omita el Executor
, aunque no sea el argumento final en la lista de parámetros. Si no se proporciona Executor
, se debe invocar la devolución de llamada en el subproceso principal con Looper.getMainLooper()
, y esto se debe documentar en el método sobrecargado asociado.
/**
* ...
* Note that the callback will be executed on the main thread using
* {@link Looper.getMainLooper()}. To specify the execution thread, use
* {@link registerFooCallback(Executor, FooCallback)}.
* ...
*/
public void registerFooCallback(
@NonNull FooCallback callback)
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Situaciones inesperadas en la implementación de Executor
: Ten en cuenta que el siguiente es un ejecutor válido.
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
Esto significa que, cuando implementes APIs que tengan este formato, la implementación de objetos de Binder entrantes en el lado del proceso de la app debe llamar a Binder.clearCallingIdentity()
antes de invocar la devolución de llamada de la app en el Executor
proporcionado por la app. De esta manera, cualquier código de app que use la identidad de Binder (como Binder.getCallingUid()
) para las verificaciones de permisos atribuye correctamente el código que se ejecuta a la app y no al proceso del sistema que llama a la app. Si los usuarios de tu API quieren la información de UID o PID del llamador, esta debe ser una parte explícita de la plataforma de tu API, en lugar de ser implícita según dónde se ejecutó el Executor
que proporcionaron.
Tu API debe admitir la especificación de un Executor
. En los casos en los que el rendimiento es fundamental, es posible que las apps deban ejecutar código de forma inmediata o síncrona con los comentarios de tu API. Aceptar un Executor
lo permite.
Crear de forma defensiva un HandlerThread
adicional o similar al trampolín desde la defensa derrota este caso de uso deseado.
Si una app va a ejecutar código costoso en algún lugar de su propio proceso, déjala. Las soluciones que encuentren los desarrolladores de apps para superar tus restricciones serán mucho más difíciles de admitir a largo plazo.
Excepción para una sola devolución de llamada: Cuando la naturaleza de los eventos que se informan requiere que solo se admita una instancia de devolución de llamada, usa el siguiente estilo:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
Usa Executor en lugar de Handler
En el pasado, Handler
de Android se usaba como estándar para redireccionar la ejecución de devolución de llamada a un subproceso Looper
específico. Este estándar se cambió para preferir Executor
, ya que la mayoría de los desarrolladores de apps administran sus propios grupos de subprocesos, lo que hace que el subproceso principal o de IU sea el único subproceso Looper
disponible para la app. Usa Executor
para darles a los desarrolladores el control que necesitan para volver a usar sus contextos de ejecución existentes o preferidos.
Las bibliotecas de simultaneidad modernas, como kotlinx.coroutines o RxJava, proporcionan sus propios mecanismos de programación que realizan su propio envío cuando es necesario, lo que hace que sea importante proporcionar la capacidad de usar un ejecutor directo (como Runnable::run
) para evitar la latencia de los saltos de subprocesos dobles. Por ejemplo, un salto para publicar en un subproceso Looper
con un Handler
seguido de otro salto desde el marco de trabajo de simultaneidad de la app.
Las excepciones a este lineamiento son poco frecuentes. Estas son algunas de las apelaciones comunes para obtener una excepción:
Tengo que usar un Looper
porque necesito un Looper
para epoll
para el evento.
Se otorga esta solicitud de excepción porque los beneficios de Executor
no se pueden lograr en esta situación.
No quiero que el código de la app bloquee mi subproceso que publica el evento. Por lo general, no se otorga esta solicitud de excepción para el código que se ejecuta en un proceso de app. Las apps que se equivocan solo se perjudican a sí mismas, no afectan el estado general del sistema. Las apps que lo hacen bien o usan un framework de concurrencia común no deberían pagar penalizaciones de latencia adicionales.
Handler
es coherente de forma local con otras APIs similares de la misma clase.
Esta solicitud de excepción se otorga según la situación. Se prefiere que se agreguen sobrecargas basadas en Executor
y que se migren las implementaciones de Handler
para usar la nueva implementación de Executor
. (myHandler::post
es un Executor
válido). Según el tamaño de la clase, la cantidad de métodos Handler
existentes y la probabilidad de que los desarrolladores deban usar métodos existentes basados en Handler
junto con el método nuevo, se puede otorgar una excepción para agregar un método nuevo basado en Handler
.
Simetría en el registro
Si hay una forma de agregar o registrar algo, también debería haber una forma de quitarlo o anular su registro. El método
registerThing(Thing)
debe tener un
unregisterThing(Thing)
Proporciona un identificador de solicitud
Si es razonable que un desarrollador reutilice una devolución de llamada, proporciona un objeto identificador para vincular la devolución de llamada a la solicitud.
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
Objetos de devolución de llamada de varios métodos
Las devoluciones de llamada de varios métodos deben preferir interface
y usar métodos default
cuando se agregan a interfaces publicadas anteriormente. Anteriormente, este lineamiento recomendaba abstract class
debido a la falta de métodos default
en Java 7.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
Usa android.os.OutcomeReceiver cuando modeles una llamada a función no bloqueante.
OutcomeReceiver<R,E>
informa un valor de resultado R
cuando se realiza correctamente o E : Throwable
de lo contrario, lo mismo que puede hacer una llamada de método simple. Usa OutcomeReceiver
como el tipo de devolución de llamada cuando conviertas un método de bloqueo que muestra un resultado o arroja una excepción a un método asíncrono no bloqueador:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
Los métodos asíncronos convertidos de esta manera siempre muestran void
. En su lugar, cualquier resultado que mostraría requestFoo
se informa a OutcomeReceiver.onResult
del parámetro callback
de requestFooAsync
llamándolo en el executor
proporcionado.
Cualquier excepción que genere requestFoo
se informará al método OutcomeReceiver.onError
de la misma manera.
El uso de OutcomeReceiver
para informar los resultados de métodos asíncronos también proporciona un wrapper suspend fun
de Kotlin para métodos asíncronos que usa la extensión Continuation.asOutcomeReceiver
de androidx.core:core-ktx
:
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
Extensiones como esta permiten que los clientes de Kotlin llamen a métodos asíncronos no bloqueantes con la comodidad de una llamada a función simple sin bloquear el subproceso de llamada. Estas extensiones 1 a 1 para las APIs de la plataforma se pueden ofrecer como parte del artefacto androidx.core:core-ktx
en Jetpack cuando se combinan con las verificaciones y consideraciones de compatibilidad de versiones estándar. Consulta la documentación de asOutcomeReceiver para obtener más información, consideraciones de cancelación y muestras.
Los métodos asíncronos que no coinciden con la semántica de un método que muestra un resultado o arroja una excepción cuando se completa su trabajo no deben usar OutcomeReceiver
como tipo de devolución de llamada. En su lugar, considera una de las otras opciones que se enumeran en la siguiente sección.
Prefiere las interfaces funcionales en lugar de crear nuevos tipos de método único abstracto (SAM).
El nivel de API 24 agregó los tipos java.util.function.*
(documentos de referencia), que ofrecen interfaces genéricas de SAM, como Consumer<T>
, que son adecuadas para usar como lambdas de devolución de llamada. En muchos casos, crear nuevas interfaces de SAM proporciona poco valor en términos de seguridad de tipos o comunicación de intents, mientras que expande innecesariamente el área de la superficie de la API de Android.
Considera usar estas interfaces genéricas, en lugar de crear otras nuevas:
Runnable
:() -> Unit
Supplier<R>
:() -> R
Consumer<T>
:(T) -> Unit
Function<T,R>
:(T) -> R
Predicate<T>
:(T) -> Boolean
- muchos más disponibles en los documentos de referencia
Ubicación de los parámetros de SAM
Los parámetros de SAM deben colocarse en último lugar para habilitar el uso idiomático desde Kotlin, incluso si el método se sobrecarga con parámetros adicionales.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
Documentos
Estas son reglas sobre la documentación pública (Javadoc) de las APIs.
Todas las APIs públicas deben estar documentadas
Todas las APIs públicas deben tener documentación suficiente para explicar cómo un desarrollador usaría la API. Supongamos que el desarrollador encontró el método con la función de autocompletado o mientras exploraba la documentación de referencia de la API y tiene una cantidad mínima de contexto de la plataforma de la API adyacente (por ejemplo, la misma clase).
Métodos
Los parámetros y los valores que muestra el método deben documentarse con las anotaciones de @param
y @return
, respectivamente. Dale formato al cuerpo de Javadoc como si estuviera precedido por "Este método…".
En los casos en que un método no tome parámetros, no tenga consideraciones especiales y muestre lo que dice el nombre del método, puedes omitir @return
y escribir documentos similares a los siguientes:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Usa siempre vínculos en Javadoc
Los documentos deben vincularse a otros para obtener constantes, métodos y otros elementos relacionados. Usa etiquetas Javadoc (por ejemplo, @see
y {@link foo}
), no solo
palabras de texto sin formato.
En el siguiente ejemplo de fuente:
public static final int FOO = 0;
public static final int BAR = 1;
No uses texto sin procesar ni fuente de código:
/**
* Sets value to one of FOO or <code>BAR</code>.
*
* @param value the value being set, one of FOO or BAR
*/
public void setValue(int value) { ... }
En su lugar, usa vínculos:
/**
* Sets value to one of {@link #FOO} or {@link #BAR}.
*
* @param value the value being set
*/
public void setValue(@ValueType int value) { ... }
Ten en cuenta que usar una anotación IntDef
, como @ValueType
, en un parámetro genera automáticamente documentación que especifica los tipos permitidos. Consulta la guía sobre anotaciones para obtener más información sobre IntDef
.
Ejecuta update-api o el objetivo de documentos cuando agregues Javadoc
Esta regla es muy importante cuando se agregan etiquetas @link
o @see
, y asegúrate de que el resultado se vea como se espera. El resultado de ERROR en Javadoc suele deberse a vínculos incorrectos. El objetivo de Make update-api
o docs
realiza esta verificación, pero el objetivo docs
puede ser más rápido si solo cambias Javadoc y, de lo contrario, no necesitas ejecutar el objetivo update-api
.
Usa {@code foo} para distinguir valores de Java
Une valores de Java, como true
, false
y null
, con {@code...}
para distinguirlos del texto de la documentación.
Cuando escribas documentación en fuentes de Kotlin, puedes unir el código con acentos graves, como lo harías con Markdown.
Los resúmenes de @param y @return deben ser un fragmento de una sola oración.
Los resúmenes de parámetros y valores que se devuelven deben comenzar con un carácter en minúscula y solo deben contener un fragmento de oración. Si tienes información adicional que se extiende más allá de una sola oración, muévela al cuerpo de Javadoc del método:
/**
* @param e The element to be appended to the list. This must not be
* null. If the list contains no entries, this element will
* be added at the beginning.
* @return This method returns true on success.
*/
Debe cambiarse a lo siguiente:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
Las anotaciones de documentos necesitan explicaciones
Documenta por qué las anotaciones @hide
y @removed
están ocultas de la API pública.
Incluye instrucciones para reemplazar los elementos de la API marcados con la anotación @deprecated
.
Usa @throws para documentar excepciones
Si un método arroja una excepción verificada, por ejemplo, IOException
, documenta la
excepción con @throws
. En el caso de las APIs de origen de Kotlin que están destinadas al uso de clientes de Java, anota las funciones con @Throws
.
Si un método arroja una excepción no verificada que indica un error evitable, por
ejemplo, IllegalArgumentException
o IllegalStateException
, documenta la
excepción con una explicación de por qué se arroja. La excepción thrown también debe indicar por qué se arrojó.
Ciertos casos de excepción no verificada se consideran implícitos y no es necesario documentarlos, como NullPointerException
o IllegalArgumentException
, en los que un argumento no coincide con una anotación @IntDef
o similar que incorpora el contrato de la API en la firma del método:
/**
* ...
* @throws IOException If it cannot find the schema for {@code toVersion}
* @throws IllegalStateException If the schema validation fails
*/
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
boolean validateDroppedTables, Migration... migrations) throws IOException {
// ...
if (!dbPath.exists()) {
throw new IllegalStateException("Cannot find the database file for " + name
+ ". Before calling runMigrations, you must first create the database "
+ "using createDatabase.");
}
// ...
O bien, en Kotlin:
/**
* ...
* @throws IOException If something goes wrong reading the file, such as a bad
* database header or missing permissions
*/
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
// ...
val read = input.read(buffer)
if (read != 4) {
throw IOException("Bad database header, unable to read 4 bytes at " +
"offset 60")
}
}
// ...
Si el método invoca código asíncrono que podría generar excepciones, considera cómo el desarrollador se entera de esas excepciones y responde a ellas. Por lo general, esto implica reenviar la excepción a una devolución de llamada y documentar las excepciones que se arrojan en el método que las recibe. Las excepciones asíncronas
no deben documentarse con @throws
, a menos que se vuelvan a lanzar desde
el método anotado.
Finaliza la primera oración de los documentos con un punto
La herramienta Doclava analiza los documentos de forma sencilla y finaliza el documento de sinopsis (la primera oración, que se usa en la descripción rápida en la parte superior de los documentos de la clase) en cuanto ve un punto (.) seguido de un espacio. Esto genera dos problemas:
- Si un documento breve no termina con un punto y ese miembro tiene documentos heredados que la herramienta detecta, el resumen también los detecta. Por ejemplo, consulta
actionBarTabStyle
en la documentación deR.attr
, que tiene la descripción de la dimensión agregada al resumen. - Evita usar “p.ej.” en la primera oración por el mismo motivo, ya que Doclava finaliza los documentos del resumen después de “g.”. Por ejemplo, consulta
TEXT_ALIGNMENT_CENTER
enView.java
. Ten en cuenta que Metalava corrige automáticamente este error insertando un espacio sin separación después del punto. Sin embargo, no cometas este error en primer lugar.
Aplica formato a los documentos para que se rendericen en HTML
Javadoc se renderiza en HTML, por lo que debes dar formato a estos documentos de la siguiente manera:
Los saltos de línea deben usar una etiqueta
<p>
explícita. No agregues una etiqueta</p>
de cierre.No uses ASCII para renderizar listas o tablas.
Las listas deben usar
<ul>
o<ol>
para las listas sin ordenar y ordenadas, respectivamente. Cada elemento debe comenzar con una etiqueta<li>
, pero no necesita una etiqueta</li>
de cierre. Se requiere una etiqueta de cierre</ul>
o</ol>
después del último elemento.Las tablas deben usar
<table>
,<tr>
para las filas,<th>
para los encabezados y<td>
para las celdas. Todas las etiquetas de tabla requieren etiquetas de cierre coincidentes. Puedes usarclass="deprecated"
en cualquier etiqueta para indicar que está obsoleta.Para crear una fuente de código intercalado, usa
{@code foo}
.Para crear bloques de código, usa
<pre>
.El navegador analiza todo el texto dentro de un bloque
<pre>
, así que ten cuidado con los corchetes<>
. Puedes escaparlos con las entidades HTML<
y>
.Como alternativa, puedes dejar corchetes sin procesar
<>
en el fragmento de código si unes las secciones infractoras en{@code foo}
. Por ejemplo:<pre>{@code <manifest>}</pre>
Sigue la guía de estilo de la referencia de la API
Para brindar coherencia en el estilo de los resúmenes de clases, las descripciones de métodos, las descripciones de parámetros y otros elementos, sigue las recomendaciones de los lineamientos oficiales del lenguaje Java en Cómo escribir comentarios doc para la herramienta Javadoc.
Reglas específicas del framework de Android
Estas reglas se relacionan con las APIs, los patrones y las estructuras de datos que son específicos de las APIs y los comportamientos integrados en el framework de Android (por ejemplo, Bundle
o Parcelable
).
Los creadores de intents deben usar el patrón create*Intent().
Los creadores de intents deben usar métodos llamados createFooIntent()
.
Usa Bundle en lugar de crear nuevas estructuras de datos de uso general
Evita crear nuevas estructuras de datos de uso general para representar asignaciones arbitrarias de claves a valores escritos. En su lugar, considera usar Bundle
.
Esto suele ocurrir cuando se escriben APIs de plataforma que funcionan como canales de comunicación entre apps y servicios que no son de la plataforma, en los que la plataforma no lee los datos que se envían a través del canal y el contrato de la API puede definirse parcialmente fuera de la plataforma (por ejemplo, en una biblioteca de Jetpack).
En los casos en que la plataforma lee los datos, evita usar Bundle
y prefiere una clase de datos con tipado fuerte.
Las implementaciones de Parcelable deben tener un campo CREATOR público.
El aumento de Parcelable se expone a través de CREATOR
, no de constructores sin procesar. Si una clase implementa Parcelable
, su campo CREATOR
también debe ser una API pública, y el constructor de la clase que toma un argumento Parcel
debe ser privado.
Usa CharSequence para las cadenas de IU
Cuando se presenta una cadena en una interfaz de usuario, usa CharSequence
para permitir instancias de Spannable
.
Si solo es una clave o alguna otra etiqueta o valor que no es visible para los usuarios, String
es adecuada.
Evita usar enums
Se debe usar IntDef
en lugar de enums en todas las APIs de la plataforma y se debe tener en cuenta en las APIs de bibliotecas sin empaquetar. Usa enums solo cuando tengas la seguridad de que no se agregarán valores nuevos.
Beneficios deIntDef
:
- Permite agregar valores a lo largo del tiempo.
- Las sentencias
when
de Kotlin pueden fallar en el tiempo de ejecución si dejan de ser exhaustivas debido a un valor de enumeración agregado en la plataforma.
- Las sentencias
- No se usan clases ni objetos en el tiempo de ejecución, solo primitivas.
- Si bien R8 o la reducción pueden evitar este costo para las APIs de bibliotecas sin agrupar, esta optimización no puede afectar las clases de API de la plataforma.
Beneficios de Enum
- Función idiomática del lenguaje de Java y Kotlin
- Habilita el interruptor exhaustivo y el uso de sentencias
when
.- Nota: Los valores no deben cambiar con el tiempo, consulta la lista anterior.
- Nombres detectables y con un alcance claro
- Habilita la verificación del tiempo de compilación.
- Por ejemplo, una sentencia
when
en Kotlin que muestra un valor
- Por ejemplo, una sentencia
- Es una clase funcional que puede implementar interfaces, tener ayudantes estáticos, exponer métodos de miembros o extensiones, y exponer campos.
Sigue la jerarquía de capas de paquetes de Android
La jerarquía de paquetes android.*
tiene un orden implícito, en el que los paquetes de nivel inferior no pueden depender de paquetes de nivel superior.
Evita hacer referencia a Google, a otras empresas y a sus productos.
La plataforma de Android es un proyecto de código abierto y su objetivo es no depender de proveedores. La API debe ser genérica y ser igualmente utilizable por los integradores de sistemas o las apps con los permisos necesarios.
Las implementaciones parcelables deben ser finales
Las clases parcelables que define la plataforma siempre se cargan desde framework.jar
, por lo que no es válido que una app intente anular una implementación de Parcelable
.
Si la app de envío extiende un Parcelable
, la app receptora no tendrá la implementación personalizada del remitente para descomprimir. Nota sobre la compatibilidad con versiones anteriores: Si, históricamente, tu clase no era final, pero no tenía un constructor disponible públicamente, puedes marcarla como final
.
Los métodos que llaman al proceso del sistema deben volver a lanzar RemoteException como RuntimeException.
Por lo general, AIDL interno arroja RemoteException
y, además, indica que finalizó el proceso del sistema o que la app intenta enviar demasiados datos. En ambos casos, la API pública debe volver a lanzarse como RuntimeException
para evitar que las apps persistan en las decisiones de seguridad o políticas.
Si sabes que el otro lado de una llamada a Binder
es el proceso del sistema, este código de plantilla es la práctica recomendada:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Cómo generar excepciones específicas para cambios en la API
Los comportamientos de las APIs públicas pueden cambiar entre los niveles de API y provocar fallas en la app (por ejemplo, para aplicar nuevas políticas de seguridad).
Cuando la API necesite arrojar una solicitud que antes era válida, arroja una nueva excepción específica en lugar de una genérica. Por ejemplo, ExportedFlagRequired
en lugar de SecurityException
(y ExportedFlagRequired
puede extender SecurityException
).
Esto ayudará a los desarrolladores de apps y a las herramientas a detectar cambios en el comportamiento de las APIs.
Implementa el constructor de copia en lugar de clonar
Se desaconseja el uso del método clone()
de Java debido a la falta de contratos de API que proporciona la clase Object
y las dificultades inherentes a la extensión de clases que usan clone()
. En su lugar, usa un constructor de copia que tome un objeto
del mismo tipo.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
Las clases que dependen de un generador para la construcción deben considerar agregar un constructor de copia de generador para permitir modificaciones en la copia.
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
Usa ParcelFileDescriptor en lugar de FileDescriptor
El objeto java.io.FileDescriptor
tiene una definición deficiente de la propiedad, lo que puede generar errores ocultos de uso después del cierre. En su lugar, las APIs deben mostrar o aceptar instancias de ParcelFileDescriptor
. El código heredado puede convertir entre PFD y FD si es necesario con dup() o getFileDescriptor().
Evita usar valores numéricos de tamaño impar
Evita usar valores de short
o byte
directamente, ya que, a menudo, limitan la forma en que podrías hacer evolucionar la API en el futuro.
Evita usar BitSet
java.util.BitSet
es excelente para la implementación, pero no para la API pública. Es mutable, requiere una asignación para llamadas a métodos de alta frecuencia y no proporciona un significado semántico para lo que representa cada bit.
Para situaciones de alto rendimiento, usa un int
o long
con @IntDef
. Para situaciones de bajo rendimiento, considera un Set<EnumType>
. Para los datos binarios sin procesar, usa byte[]
.
Prefiere android.net.Uri
android.net.Uri
es el encapsulamiento preferido para los URIs en las APIs de Android.
Evita java.net.URI
, ya que es demasiado estricto en el análisis de URIs, y nunca uses java.net.URL
, ya que su definición de igualdad está muy dañada.
Se ocultan las anotaciones marcadas como @IntDef, @LongDef o @StringDef.
Las anotaciones marcadas como @IntDef
, @LongDef
o @StringDef
denotan un conjunto de constantes válidas que se pueden pasar a una API. Sin embargo, cuando se exportan como APIs, el compilador intercala las constantes y solo los valores (ahora inútiles) permanecen en el stub de la API de la anotación (para la plataforma) o en el archivo JAR (para las bibliotecas).
Por lo tanto, el uso de estas anotaciones debe marcarse con la anotación de documentos @hide
en la plataforma o la anotación de código @RestrictTo.Scope.LIBRARY)
en las bibliotecas. Deben marcarse como @Retention(RetentionPolicy.SOURCE)
en ambos casos para evitar que aparezcan en los stubs o JAR de la API.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
Cuando se compilan los AAR del SDK y la biblioteca de la plataforma, una herramienta extrae las anotaciones y las agrupa por separado de las fuentes compiladas. Android Studio lee este formato empaquetado y aplica las definiciones de tipo.
No agregues nuevas claves de proveedor de configuración
No expongas claves nuevas de Settings.Global
, Settings.System
ni Settings.Secure
.
En su lugar, agrega una API de Java de get y set adecuada en una clase relevante, que suele ser una clase de "administrador". Agrega un mecanismo de objeto de escucha o una transmisión para notificar a los clientes sobre los cambios según sea necesario.
La configuración de SettingsProvider
tiene varios problemas en comparación con los métodos get y set:
- No hay seguridad de tipo.
- No hay una forma unificada de proporcionar un valor predeterminado.
- No hay una forma adecuada de personalizar los permisos.
- Por ejemplo, no es posible proteger tu configuración con un permiso personalizado.
- No hay una forma adecuada de agregar lógica personalizada.
- Por ejemplo, no es posible cambiar el valor del parámetro de configuración A según el valor del parámetro de configuración B.
Ejemplo: Settings.Secure.LOCATION_MODE
existe desde hace mucho tiempo, pero el equipo de ubicación lo dio de baja por una API de Java adecuada LocationManager.isLocationEnabled()
y la transmisión MODE_CHANGED_ACTION
, lo que le brindó al equipo mucha más flexibilidad, y la semántica de las APIs ahora es mucho más clara.
No extiendan Activity ni AsyncTask.
AsyncTask
es un detalle de implementación. En su lugar, expone un objeto de escucha o, en androidx, una API de ListenableFuture
.
Las subclases de Activity
son imposibles de componer. Si extiendes la actividad de tu función, esta se vuelve incompatible con otras funciones que requieren que los usuarios hagan lo mismo. En su lugar, usa herramientas como LifecycleObserver para aprovechar la composición.
Usa getUser() del contexto
Las clases vinculadas a un Context
, como todo lo que se muestra desde Context.getSystemService()
, deben usar el usuario vinculado al Context
en lugar de exponer miembros que se segmentan para usuarios específicos.
class FooManager {
Context mContext;
void fooBar() {
mIFooBar.fooBarForUser(mContext.getUser());
}
}
class FooManager {
Context mContext;
Foobar getFoobar() {
// Bad: doesn't appy mContext.getUserId().
mIFooBar.fooBarForUser(Process.myUserHandle());
}
Foobar getFoobar() {
// Also bad: doesn't appy mContext.getUserId().
mIFooBar.fooBar();
}
Foobar getFoobarForUser(UserHandle user) {
mIFooBar.fooBarForUser(user);
}
}
Excepción: Un método puede aceptar un argumento de usuario si acepta valores que no representan a un solo usuario, como UserHandle.ALL
.
Usa UserHandle en lugar de ints simples
Se prefiere UserHandle
para proporcionar seguridad de tipo y evitar la confusión de los IDs de usuario con los UID.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Cuando no se pueda evitar, un int
que represente un ID de usuario se debe anotar con @UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Prefiere objetos de escucha o devoluciones de llamada para transmitir intents
Los intents de transmisión son muy potentes, pero generan comportamientos emergentes que pueden afectar negativamente el estado del sistema, por lo que se deben agregar nuevos intents de transmisión con prudencia.
Estas son algunas de las preocupaciones específicas que nos llevan a desaconsejar la introducción de nuevos intents de transmisión:
Cuando se envían transmisiones sin la marca
FLAG_RECEIVER_REGISTERED_ONLY
, se inician de manera forzosa las apps que aún no se están ejecutando. Si bien, a veces, esto puede ser un resultado deseado, puede provocar una estampida de decenas de apps, lo que afecta negativamente el estado del sistema. Te recomendamos que uses estrategias alternativas, comoJobScheduler
, para coordinar mejor cuando se cumplan varias condiciones previas.Cuando se envían transmisiones, hay poca capacidad para filtrar o ajustar el contenido que se entrega a las apps. Esto dificulta o imposibilita responder a futuras inquietudes sobre la privacidad o implementar cambios de comportamiento en función del SDK de destino de la app receptora.
Dado que las colas de transmisión son un recurso compartido, pueden sobrecargarse y es posible que no se publiquen tus eventos a tiempo. Observamos varias filas de transmisión en vivo que tienen una latencia de extremo a extremo de 10 minutos o más.
Por estos motivos, recomendamos que las funciones nuevas consideren usar objetos de escucha, callbacks o otras funciones, como JobScheduler
, en lugar de intents de transmisión.
En los casos en que los intents de transmisión sigan siendo el diseño ideal, estas son algunas prácticas recomendadas que se deben tener en cuenta:
- Si es posible, usa
Intent.FLAG_RECEIVER_REGISTERED_ONLY
para limitar la transmisión a las apps que ya se están ejecutando. Por ejemplo,ACTION_SCREEN_ON
usa este diseño para evitar activar apps. - Si es posible, usa
Intent.setPackage()
oIntent.setComponent()
para segmentar la transmisión en una app específica de interés. Por ejemplo,ACTION_MEDIA_BUTTON
usa este diseño para enfocarse en la app actual que controla los controles de reproducción. - Si es posible, define tu transmisión como un
<protected-broadcast>
para evitar que las apps maliciosas se hagan pasar por el SO.
Intents en servicios para desarrolladores vinculados al sistema
Los servicios que el desarrollador debe extender y que el sistema vincula, por ejemplo, los servicios abstractos como NotificationListenerService
, pueden responder a una acción Intent
del sistema. Estos servicios deben cumplir con los siguientes criterios:
- Define una constante de cadena
SERVICE_INTERFACE
en la clase que contiene el nombre de clase completamente calificado del servicio. Esta constante se debe anotar con@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Documento sobre la clase en la que un desarrollador debe agregar un
<intent-filter>
a suAndroidManifest.xml
para recibir intents de la plataforma. - Considera agregar un permiso a nivel del sistema para evitar que las apps no autorizadas envíen
Intent
a los servicios de desarrolladores.
Interoperabilidad de Kotlin y Java
Consulta la Guía de interoperabilidad de Kotlin-Java oficial de Android para obtener una lista completa de los lineamientos. Se copiaron algunos lineamientos en esta guía para mejorar la visibilidad.
Visibilidad de la API
Algunas APIs de Kotlin, como las suspend fun
, no están diseñadas para que las usen los desarrolladores de Java. Sin embargo, no intentes controlar la visibilidad específica del lenguaje con @JvmSynthetic
, ya que tiene efectos secundarios en la forma en que se presenta la API en los depuradores, lo que dificulta la depuración.
Consulta la guía de interoperabilidad de Kotlin-Java o la guía de asincronismo para obtener orientación específica.
Objetos complementarios
Kotlin usa companion object
para exponer miembros estáticos. En algunos casos, estos aparecerán desde Java en una clase interna llamada Companion
en lugar de en la clase contenedora. Las clases Companion
pueden mostrarse como clases vacías en los archivos de texto de la API, lo que funciona según lo previsto.
Para maximizar la compatibilidad con Java, anota los campos no constantes de los objetos complementarios con @JvmField
y las funciones públicas con @JvmStatic
para exponerlos directamente en la clase contenedora.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Evolución de las APIs de la plataforma de Android
En esta sección, se describen las políticas sobre los tipos de cambios que puedes realizar en las APIs de Android existentes y cómo debes implementar esos cambios para maximizar la compatibilidad con las apps y bases de código existentes.
Cambios rotundos del objeto binario
Evita los cambios rotundos en las plataformas de API públicas finalizadas. Estos tipos de cambios suelen generar errores cuando se ejecuta make update-api
, pero puede haber casos extremos que la verificación de la API de Metalava no detecte. Cuando tengas dudas, consulta la guía Evolving Java-based APIs de la Eclipse Foundation para obtener una explicación detallada de los tipos de cambios de API que son compatibles con Java. Los cambios rotundos en las APIs ocultas (por ejemplo, del sistema) deben seguir el ciclo de obsolecencia o reemplazo.
Cambios rotundos en la fuente
No recomendamos realizar cambios que rompan la fuente, incluso si no son rotundos. Un ejemplo de un cambio compatible con objetos binarios, pero que rompe la fuente, es agregar un elemento genérico a una clase existente, que es compatible con objetos binarios, pero puede generar errores de compilación debido a la herencia o referencias ambiguas.
Los cambios que generan errores de origen no generarán errores cuando se ejecute make update-api
, por lo que debes tener cuidado de comprender el impacto de los cambios en las firmas de API existentes.
En algunos casos, se vuelven necesarios los cambios que rompen la fuente para mejorar la experiencia del desarrollador o la exactitud del código. Por ejemplo, agregar anotaciones de nulabilidad a las fuentes de Java mejora la interoperabilidad con el código de Kotlin y reduce la probabilidad de errores, pero a menudo requiere cambios, a veces significativos, en el código fuente.
Cambios en las APIs privadas
Puedes cambiar las APIs con anotaciones @TestApi
en cualquier momento.
Debes conservar las APIs con anotaciones @SystemApi
durante tres años. Debes quitar o refactorizar una API del sistema según el siguiente cronograma:
- API y: Se agregó
- API de y+1: Obsolecencia
- Marca el código con
@Deprecated
. - Agrega reemplazos y vincula al reemplazo en el Javadoc del código obsoleto con la anotación de documentos
@deprecated
. - Durante el ciclo de desarrollo, informa errores a los usuarios internos y diles que la API dejará de estar disponible. Esto ayuda a validar que las APIs de reemplazo sean adecuadas.
- Marca el código con
- API de y+2: Eliminación no definitiva
- Marca el código con
@removed
. - De manera opcional, arroja o no realiza ninguna acción para las apps que se orientan al nivel de SDK actual de la versión.
- Marca el código con
- API de y+3: Eliminación definitiva
- Quita por completo el código del árbol de origen.
Baja
Consideramos que la baja es un cambio en la API y puede ocurrir en una versión principal (como una letra). Usa la anotación de fuente @Deprecated
y la anotación de documentos @deprecated
<summary>
en conjunto cuando desactives las APIs. Tu resumen debe incluir una estrategia de migración. Esta estrategia puede vincular a una API de reemplazo o explicar por qué no debes usar la API:
/**
* Simple version of ...
*
* @deprecated Use the {@link androidx.fragment.app.DialogFragment}
* class with {@link androidx.fragment.app.FragmentManager}
* instead.
*/
@Deprecated
public final void showDialog(int id)
También debes dar de baja las APIs definidas en XML y expuestas en Java, incluidos los atributos y las propiedades personalizables expuestas en la clase android.R
, con un resumen:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Cuándo dar de baja una API
Las bajas son más útiles para desalentar la adopción de una API en código nuevo.
También exigimos que marques las APIs como @deprecated
antes de que sean @removed
, pero esto no proporciona una motivación sólida para que los desarrolladores migren de una API que ya están usando.
Antes de dar de baja una API, considera el impacto en los desarrolladores. Estos son algunos de los efectos de dar de baja una API:
javac
emite una advertencia durante la compilación.- Las advertencias de baja no se pueden suprimir de forma global ni establecer como referencia, por lo que los desarrolladores que usan
-Werror
deben corregir o suprimir de forma individual cada uso de una API obsoleta antes de poder actualizar la versión del SDK de compilación. - No se pueden suprimir las advertencias de baja sobre las importaciones de clases obsoletas. Como resultado, los desarrolladores deben intercalar el nombre de la clase totalmente calificado para cada uso de una clase obsoleta antes de poder actualizar la versión del SDK de compilación.
- Las advertencias de baja no se pueden suprimir de forma global ni establecer como referencia, por lo que los desarrolladores que usan
- La documentación sobre
d.android.com
muestra un aviso de baja. - Los IDEs como Android Studio muestran una advertencia en el sitio de uso de la API.
- Los IDEs pueden bajar la clasificación de la API o ocultarla del autocompletado.
Como resultado, dar de baja una API puede disuadir a los desarrolladores que más se preocupan por el estado del código (los que usan -Werror
) de adoptar nuevos SDKs.
Es probable que los desarrolladores que no se preocupan por las advertencias en su código existente ignoren por completo las bajas.
Un SDK que presenta una gran cantidad de baja empeora ambos casos.
Por este motivo, recomendamos dar de baja las APIs solo en los siguientes casos:
- Planeamos
@remove
la API en una versión futura. - El uso de la API genera un comportamiento incorrecto o no definido que no podemos corregir sin romper la compatibilidad.
Cuando descatalogas una API y la reemplazas por una nueva, te recomendamos que agregues una API de compatibilidad correspondiente a una biblioteca de Jetpack, como androidx.core
, para simplificar la compatibilidad con dispositivos nuevos y antiguos.
No recomendamos dar de baja las APIs que funcionan según lo previsto en las versiones actuales y futuras:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
La baja es adecuada en los casos en que las APIs ya no pueden mantener sus comportamientos documentados:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Cambios en las APIs obsoletas
Debes mantener el comportamiento de las APIs obsoletas. Esto significa que las implementaciones de pruebas deben permanecer iguales, y las pruebas deben seguir pasando después de que desactives la API. Si la API no tiene pruebas, debes agregarlas.
No expandas las plataformas de API obsoletas en versiones futuras. Puedes agregar anotaciones de corrección de lint (por ejemplo, @Nullable
) a una API existente que dejó de estar disponible, pero no debes agregar APIs nuevas.
No agregues APIs nuevas como obsoletas. Si se agregaron APIs y, posteriormente, se dieron de baja dentro de un ciclo de versión preliminar (por lo que, en un principio, ingresarían a la plataforma de la API pública como obsoletas), debes quitarlas antes de finalizar la API.
Eliminación suave
La eliminación no definitiva es un cambio rotundo en la fuente, y debes evitarla en las APIs públicas, a menos que el Consejo de la API la apruebe de forma explícita.
En el caso de las APIs del sistema, debes dar de baja la API durante la vigencia de una versión principal antes de una eliminación gradual. Quita todas las referencias de los documentos a las APIs y usa la anotación de documentos @removed <summary>
cuando quites las APIs de forma no destructiva. El resumen debe incluir el motivo de la eliminación y puede incluir una estrategia de migración, como explicamos en Obsolescencia.
El comportamiento de las APIs que se quitaron de forma temporal puede mantenerse tal como está, pero lo más importante es que se debe conservar para que los llamadores existentes no fallen cuando llamen a la API. En algunos casos, eso podría significar preservar el comportamiento.
Se debe mantener la cobertura de pruebas, pero es posible que el contenido de las pruebas deba cambiar para adaptarse a los cambios de comportamiento. Las pruebas aún deben validar que los llamadores existentes no fallen durante el tiempo de ejecución. Puedes mantener el comportamiento de las APIs quitadas de forma suave tal como está, pero lo más importante es que debes conservarlo de modo que los llamadores existentes no fallen cuando llamen a la API. En algunos casos, eso podría significar preservar el comportamiento.
Debes mantener la cobertura de pruebas, pero es posible que el contenido de las pruebas deba cambiar para adaptarse a los cambios de comportamiento. Las pruebas aún deben validar que los llamadores existentes no fallen durante el tiempo de ejecución.
A nivel técnico, quitamos la API del stub JAR del SDK y de la ruta de acceso del tiempo de compilación con la anotación Javadoc @remove
, pero aún existe en la ruta de acceso del tiempo de ejecución, similar a las APIs de @hide
:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
Desde la perspectiva de un desarrollador de apps, la API ya no aparece en la función de autocompletar, y el código fuente que hace referencia a la API no se compilará cuando compileSdk
sea igual o posterior al SDK en el que se quitó la API. Sin embargo, el código fuente sigue compilándose correctamente con SDKs anteriores, y los objetos binarios que hacen referencia a la API siguen funcionando.
Ciertas categorías de APIs no deben eliminarse de forma no definitiva. No debes quitar de forma silenciosa ciertas categorías de API.
Métodos abstractos
No debes quitar de forma no definitiva los métodos abstractos de las clases que los desarrolladores podrían extender. De esta manera, es imposible que los desarrolladores extiendan correctamente la clase en todos los niveles del SDK.
En casos excepcionales en los que nunca fue posible para los desarrolladores extender una clase y no lo será, puedes quitar de forma diferida los métodos abstractos.
Eliminación definitiva
La eliminación forzada es un cambio que genera una ruptura binaria y nunca debe ocurrir en las APIs públicas.
Anotación no recomendada
Usamos la anotación @Discouraged
para indicar que no recomendamos una API en la mayoría de los casos (>95%). Las APIs no recomendadas difieren de las obsoletas en que existe un caso de uso crítico limitado que evita la baja. Cuando marcas una API como no recomendada, debes proporcionar una explicación y una solución alternativa:
@Discouraged(message = "Use of this function is discouraged because resource
reflection makes it harder to perform build
optimizations and compile-time verification of code. It
is much more efficient to retrieve resources by
identifier (such as `R.foo.bar`) than by name (such as
`getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
No deberías agregar APIs nuevas, ya que no se recomienda.
Cambios en el comportamiento de las APIs existentes
En algunos casos, es posible que desees cambiar el comportamiento de implementación de una API existente. Por ejemplo, en Android 7.0, mejoramos DropBoxManager
para comunicarnos con claridad cuando los desarrolladores intentaron publicar eventos demasiado grandes para enviar a través de Binder
.
Sin embargo, para evitar problemas en las apps existentes, te recomendamos que conserves un comportamiento seguro para las apps más antiguas. Históricamente, hemos protegido estos
cambios de comportamiento en función de la ApplicationInfo.targetSdkVersion
de la app, pero
recientemente migramos para exigir el uso del framework de compatibilidad de apps. A continuación, se muestra un ejemplo de cómo implementar un cambio de comportamiento con este nuevo framework:
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
public class MyClass {
@ChangeId
// This means the change will be enabled for target SDK R and higher.
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
// Use a bug number as the value, provide extra detail in the bug.
// FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
static final long FOO_NOW_DOES_X = 123456789L;
public void doFoo() {
if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
// do the new thing
} else {
// do the old thing
}
}
}
El uso de este diseño del framework de compatibilidad de apps permite a los desarrolladores inhabilitar temporalmente cambios de comportamiento específicos durante las versiones preliminares y beta como parte de la depuración de sus apps, en lugar de forzarlos a adaptarse a docenas de cambios de comportamiento de forma simultánea.
Compatibilidad con versiones posteriores
La compatibilidad con versiones posteriores es una característica de diseño que permite que un sistema acepte entradas destinadas a una versión posterior de sí mismo. En el caso del diseño de la API, debes prestar especial atención al diseño inicial y a los cambios futuros, ya que los desarrolladores esperan escribir código una vez, probarlo una vez y que se ejecute en todas partes sin problemas.
Los siguientes elementos causan los problemas de retrocompatibilidad más comunes en Android:
- Agregar constantes nuevas a un conjunto (como
@IntDef
oenum
) que antes se suponía que estaba completo (por ejemplo, cuandoswitch
tiene undefault
que arroja una excepción) - Se agregó compatibilidad con una función que no se captura directamente en la plataforma de la API (por ejemplo, compatibilidad para asignar recursos de tipo
ColorStateList
en XML, donde antes solo se admitían recursos<color>
). - Se flexibilizaron las restricciones en las verificaciones del entorno de ejecución, por ejemplo, se quitó una verificación de
requireNotNull()
que estaba presente en versiones anteriores.
En todos estos casos, los desarrolladores descubren que algo está mal solo durante el tiempo de ejecución. Peor aún, es posible que se enteren como resultado de los informes de fallas de los dispositivos más antiguos en el campo.
Además, estos casos son cambios técnicos válidos en la API. No rompen la compatibilidad binaria ni de la fuente, y lint de la API no detectará ninguno de estos problemas.
Como resultado, los diseñadores de APIs deben prestar especial atención cuando modifican las clases existentes. Haz la pregunta: "¿Este cambio hará que el código que se escribió y probó solo en la versión más reciente de la plataforma falle en versiones anteriores?".
Esquemas XML
Si un esquema XML sirve como interfaz estable entre componentes, ese esquema debe especificarse de manera explícita y debe evolucionar de manera retrocompatible, similar a otras APIs de Android. Por ejemplo, la estructura de los elementos y atributos XML debe conservarse de manera similar a la forma en que se mantienen los métodos y las variables en otras plataformas de la API de Android.
Baja de XML
Si deseas dar de baja un elemento o atributo XML, puedes agregar el marcador xs:annotation
, pero debes seguir admitiendo los archivos XML existentes siguiendo el ciclo de vida de evolución típico de @SystemApi
.
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string">
<xs:annotation name="Deprecated"/>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
Se deben conservar los tipos de elementos
Los esquemas admiten los elementos sequence
, choice
y all
como elementos secundarios del elemento complexType
. Sin embargo, estos elementos secundarios difieren en la cantidad y el orden de sus elementos secundarios, por lo que modificar un tipo existente sería un cambio incompatible.
Si deseas modificar un tipo existente, la práctica recomendada es dar de baja el tipo anterior y presentar uno nuevo para reemplazarlo.
<!-- Original "sequence" value -->
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string">
<xs:annotation name="Deprecated"/>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- New "choice" value -->
<xs:element name="fooChoice">
<xs:complexType>
<xs:choice>
<xs:element name="name" type="xs:string"/>
</xs:choice>
</xs:complexType>
</xs:element>
Patrones específicos de la línea principal
Mainline es un proyecto que permite actualizar los subsistemas ("módulos principales") del SO Android de forma individual, en lugar de actualizar toda la imagen del sistema.
Los módulos principales deben “desvincularse” de la plataforma principal, lo que significa que todas las interacciones entre cada módulo y el resto del mundo deben realizarse con APIs formales (públicas o del sistema).
Hay ciertos patrones de diseño que los módulos principales deben seguir. En esta sección, se describen.
El patrón <Module>FrameworkInitializer
Si un módulo principal necesita exponer clases @SystemService
(por ejemplo, JobScheduler
), usa el siguiente patrón:
Expone una clase
<YourModule>FrameworkInitializer
desde tu módulo. Esta clase debe estar en$BOOTCLASSPATH
. Ejemplo: StatsFrameworkInitializerMárcalo con
@SystemApi(client = MODULE_LIBRARIES)
.Agrega un método
public static void registerServiceWrappers()
.Usa
SystemServiceRegistry.registerContextAwareService()
para registrar una clase de administrador de servicios cuando necesite una referencia a unContext
.Usa
SystemServiceRegistry.registerStaticService()
para registrar una clase de administrador de servicios cuando no necesite una referencia a unContext
.Llama al método
registerServiceWrappers()
desde el inicializador estático deSystemServiceRegistry
.
El patrón <Module>ServiceManager
Por lo general, para registrar objetos de Binder del servicio del sistema o obtener referencias a ellos, se usa ServiceManager
, pero los módulos principales no pueden usarlo porque está oculto. Esta clase está oculta porque los módulos principales no deben registrar ni hacer referencia a objetos de Binder de servicios del sistema que expone la plataforma estática o otros módulos.
En su lugar, los módulos de la línea principal pueden usar el siguiente patrón para poder registrar y obtener referencias a los servicios de Binder que se implementan dentro del módulo.
Crea una clase
<YourModule>ServiceManager
siguiendo el diseño de TelephonyServiceManager.Expón la clase como
@SystemApi
. Si solo necesitas acceder a él desde las clases$BOOTCLASSPATH
o las clases del servidor del sistema, puedes usar@SystemApi(client = MODULE_LIBRARIES)
. De lo contrario,@SystemApi(client = PRIVILEGED_APPS)
funcionaría.Esta clase constaría de lo siguiente:
- Un constructor oculto, de modo que solo el código de plataforma estático pueda crear una instancia de él
- Métodos get públicos que muestran una instancia de
ServiceRegisterer
para un nombre específico Si tienes un objeto Binder, necesitas un método get. Si tienes dos, necesitas dos métodos get. - En
ActivityThread.initializeMainlineModules()
, crea una instancia de esta clase y pásala a un método estático que exponga tu módulo. Por lo general, agregas una API@SystemApi(client = MODULE_LIBRARIES)
estática en tu claseFrameworkInitializer
que la toma.
Este patrón evitaría que otros módulos principales accedan a estas APIs, ya que no hay forma de que otros módulos obtengan una instancia de <YourModule>ServiceManager
, aunque las APIs de get()
y register()
sean visibles para ellos.
A continuación, se muestra cómo la telefonía obtiene una referencia al servicio de telefonía: vínculo de búsqueda de código.
Si implementas un objeto de Binder de servicios en código nativo, debes usar las APIs nativas de AServiceManager
.
Estas APIs corresponden a las APIs de Java ServiceManager
, pero las nativas se exponen directamente a los módulos principales. No los uses para registrar o hacer referencia a objetos de Binder que no sean propiedad de tu módulo. Si expones un objeto Binder desde nativo, tu <YourModule>ServiceManager.ServiceRegisterer
no necesita un método register()
.
Definiciones de permisos en los módulos de Mainline
Los módulos principales que contienen APKs pueden definir permisos (personalizados) en su AndroidManifest.xml
de APK de la misma manera que un APK normal.
Si el permiso definido solo se usa de forma interna dentro de un módulo, su nombre debe tener el prefijo del nombre del paquete del APK, por ejemplo:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
Si el permiso definido se debe proporcionar como parte de una API de plataforma actualizable a otras apps, su nombre debe tener el prefijo "android.permission". (como cualquier permiso de plataforma estático) más el nombre del paquete del módulo para indicar que se trata de una API de la plataforma desde un módulo y evitar conflictos de nombres, por ejemplo:
<permission
android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
android:label="@string/active_calories_burned_read_content_description"
android:protectionLevel="dangerous"
android:permissionGroup="android.permission-group.HEALTH" />
Luego, el módulo puede exponer este nombre de permiso como una constante de API en su superficie de API, por ejemplo, HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.