El objetivo de esta página es servir como guía para que los desarrolladores comprendan los principios generales que el Consejo de APIs aplica en las revisiones de APIs.
Además de seguir estos lineamientos cuando escriben APIs, los desarrolladores deben ejecutar la herramienta API Lint, que codifica muchas de estas reglas en verificaciones que ejecuta en las APIs.
Piensa en esto como la guía de las reglas que cumple 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 API Lint
API Lint se integra en la herramienta de análisis estático Metalava y se ejecuta automáticamente durante la validación en la CI. Puedes ejecutarlo de forma manual desde una confirmación de la plataforma local con m
checkapi
o una confirmación local de AndroidX 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 APIs si hay preguntas difíciles sobre una API que deben resolverse o lineamientos que deben actualizarse.
Conceptos básicos de la API
Esta categoría se relaciona con los aspectos centrales de una API de Android.
Se deben implementar todas las APIs
Independientemente del público de una API (por ejemplo, pública o @SystemApi
), todas las superficies 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 superficies de API sin implementaciones tienen varios problemas:
- No se garantiza que se haya expuesto una superficie adecuada o completa. Hasta que los clientes no prueben o usen una API, no hay forma de verificar que tengan las APIs adecuadas para 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 el CTS.
Se deben probar todas las APIs
Esto se alinea con los requisitos de CTS de la plataforma, las políticas de AndroidX y, en general, la idea de que las APIs deben implementarse.
Probar las superficies de la API proporciona una garantía básica de que la superficie de la API es utilizable y de que abordamos los casos de uso esperados. No es suficiente probar la existencia, sino que se debe probar el comportamiento de la API en sí.
Un cambio que agrega una API nueva debe incluir las pruebas correspondientes en el mismo tema de CL o Gerrit.
Las APIs también deben ser comprobables. Deberías 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 fundamental de la usabilidad de la API. Si bien la sintaxis de una superficie 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 generadas por herramientas deben seguir los mismos lineamientos de APIs que el código escrito a mano.
Herramientas que no se recomiendan para generar APIs:
AutoValue
: Incumple los lineamientos de varias maneras. Por ejemplo, no hay forma de implementar clases de valores finales 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, en especial cuando escriben APIs públicas.
Sigue las convenciones de codificación estándar, excepto donde se indique lo contrario.
Las convenciones de codificación de Android se documentan para los colaboradores externos aquí:
https://source.android.com/source/code-style.html
En general, tendemos a seguir las convenciones de codificación estándar de Java y Kotlin.
Los acrónimos no deben escribirse con mayúscula 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 detalles de la implementación, por lo que debes evitarlo.
Clases
En esta sección, se describen las reglas sobre clases, interfaces y herencia.
Hereda nuevas clases públicas 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
más 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 te sientes tentado a anular métodos de la clase base para arrojar UnsupportedOperationException
, reconsidera qué clase base estás usando.
Usa las clases de colecciones base
Ya sea que tomes una colección como argumento o la muestres como valor, siempre prefiere la clase base a la implementación específica (por ejemplo, muestra 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, se prefieren las colecciones inmutables. Consulta Mutabilidad de la colección para obtener más detalles.
Clases abstractas en comparación con interfaces
Java 8 agrega compatibilidad con los métodos de interfaz predeterminados, lo que permite a los diseñadores de API 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 tener como objetivo Java 8 o versiones posteriores.
En los casos en que la implementación predeterminada no tiene estado, los diseñadores de API deben preferir las interfaces a 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 APIs pueden optar por dejar un solo método abstracto para simplificar el uso como 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
deberían llamarse FooService
para mayor claridad:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Sufijos genéricos
Evita usar sufijos genéricos de nombres de clase, como Helper
y Util
, para colecciones de métodos de utilidad. En cambio, coloca los métodos directamente en las clases asociadas o en funciones de extensión de Kotlin.
En los casos en los que los métodos conectan varias clases, asígnale a la clase contenedora un nombre significativo que explique lo que hace.
En casos muy limitados, podría 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 los mensajes emergentes de la versión anterior requieren conservar el estado asociado a un View
y llamar a varios métodos en el View
para instalar la versión anterior, TooltipHelper
sería un nombre de clase aceptable.
No expongas el código generado por IDL como APIs públicas directamente
Mantén el código generado por IDL como detalles de implementación. Esto incluye protobuf, sockets, FlatBuffers o cualquier otra superficie de API que no sea de Java ni del 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 de la guía de estilo de la API (por ejemplo, no pueden usar sobrecarga), 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 cambio, agrega una capa de API pública sobre la interfaz de AIDL, incluso si, en un principio, es un wrapper superficial.
Interfaces de Binder
Si la interfaz Binder
es un detalle de implementación, se puede cambiar libremente en el futuro, ya que la capa pública permite mantener la compatibilidad con versiones anteriores necesaria. Por ejemplo, es posible que debas agregar argumentos nuevos a las llamadas internas o bien optimizar el tráfico de IPC con el uso de lotes o transmisiones, la memoria compartida o recursos similares. Ninguna de estas acciones se puede realizar si tu interfaz de 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, encapsula 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 wrapper para controlar otros problemas de compatibilidad con versiones anteriores a medida que evoluciona la implementación:
/**
* @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 de 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 las apps la usen), el requisito de una interfaz de IPC estable, publicada y con versiones significa que es mucho más difícil desarrollar la interfaz en sí. Sin embargo, sigue siendo conveniente tener una capa de wrapper a su alrededor para que coincida con otros lineamientos de la API y para que sea más fácil usar la misma API pública para una nueva versión de la interfaz de IPC, si alguna vez es 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 debe usarse en la API pública. Un caso de uso común es usar un Binder
o un IBinder
como 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 finales
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, por lo que se declara como final
.
No uses CompletableFuture ni Future
java.util.concurrent.CompletableFuture
tiene una gran superficie de API que permite la mutación arbitraria del valor del futuro y tiene valores predeterminados propensos a errores.
Por el contrario, java.util.concurrent.Future
no tiene escucha sin bloqueo, 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 tanto Kotlin como Java, se 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 segmentas para Kotlin, prefiere las funciones suspend
.
suspend fun asyncLoadFoo(): Foo
En Bibliotecas de integración específicas de Java, puedes usar ListenableFuture
de Guava.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
No usar Optional
Si bien Optional
puede tener ventajas en algunas superficies de API, no es coherente con el área de superficie 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 los elementos opcionales, usa los métodos has
y get
combinados. Si el valor no está establecido (has
devuelve false
), el método get
debe arrojar 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 instanciables
Las clases que solo pueden crearse con Builder
s, las clases que solo contienen constantes o métodos estáticos, o las clases que no se pueden instanciar de otro modo deben incluir al menos un constructor privado para evitar la instanciación con el constructor predeterminado sin argumentos.
public final class Log {
// Not instantiable.
private Log() {}
}
Singletons
No se recomienda usar singletons porque tienen los siguientes inconvenientes relacionados con las pruebas:
- La construcción se administra mediante la clase, lo que impide el uso de simulaciones.
- 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 devolver el mismo objeto en las llamadas posteriores.
El objeto que devuelve 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 inyección de dependencias para administrar la implementación sin tener que crear un wrapper, o bien la biblioteca puede proporcionar su propio elemento falso 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 usen un bloque try-with-resources
.
Evita introducir nuevas subclases de View en android.*
No introduzcas clases nuevas que hereden directa 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 es prioriza Compose. Las nuevas funciones de IU expuestas por la plataforma deben exponerse como APIs de nivel inferior que se puedan usar para implementar Jetpack Compose y, de manera opcional, componentes de IU basados en View para los desarrolladores en las bibliotecas de Jetpack. Ofrecer estos componentes en bibliotecas brinda oportunidades para implementar versiones anteriores cuando las funciones de la plataforma no están disponibles.
Campos
Estas reglas se relacionan con 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 debe 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 especificación o recuperación de un campo. En esos casos, los campos deben nombrarse 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 procesar (consulta No expongas campos sin procesar). Sin embargo, en la rara situación en la que un campo se expone como público, márcalo como final
.
No se deben exponer los campos internos
No hagas referencia a nombres de campos internos en la API pública.
public int mFlags;
Usa public en lugar de protected
@see Use public instead of protected
Constantes
Estas son reglas sobre constantes públicas.
Las constantes de marcas 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 o 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 obtener más información sobre las marcas de máscara de bits para obtener más información sobre cómo definir constantes de marcas públicas.
Las constantes finales estáticas deben usar la convención de nombres con todas las letras en mayúsculas y separadas por guiones bajos.
Todas las palabras de la constante deben estar en mayúsculas y las palabras múltiples deben separarse con _
. 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, claves y acciones. Estas constantes deben tener prefijos estándar para que sean más identificables como tales.
Por ejemplo, los elementos adicionales del intent deben comenzar con EXTRA_
. Las acciones de intents 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 las constantes de cadena deben ser coherentes con el nombre de la constante y, por lo general, deben limitarse al 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 las constantes de cadena con alcance están reservados para el Proyecto de código abierto de Android.
Las acciones y los extras de intents, así como las entradas de Bundle, deben tener un espacio de nombres que utilice 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 public en lugar de protected
@see Use public instead of protected
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 marcas:
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 Use standard prefixes for constants
Usa nombres de recursos coherentes
Los identificadores, atributos y valores públicos deben nombrarse con la convención de nomenclatura camelCase, por ejemplo, @id/accessibilityActionPageUp
o @attr/textAppearance
, de manera 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 nomenclatura jerárquica PascalCase, por ejemplo, @style/Theme.Material.Light.DarkActionBar
o @style/Widget.Material.SearchView.ActionBar
, de manera similar a las clases anidadas en Java.
Los recursos de diseño y de elementos gráficos no deben exponerse como APIs públicas. Sin embargo, si deben exponerse, los diseños y los elementos de diseño públicos deben nombrarse con la convención de nombres under_score, por ejemplo, layout/simple_list_item_1.xml
o drawable/title_bar_tall.xml
.
Cuando las constantes podrían cambiar, hazlas dinámicas
El compilador podría insertar 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 convertirlas en métodos dinámicos.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Considera la compatibilidad con versiones posteriores para las devoluciones de llamada
Las constantes definidas en versiones futuras de la API no son conocidas por las apps que se segmentan para APIs anteriores. 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 las 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 devuelven constantes pueden controlar de forma segura casos como este restringiendo 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 prever si una lista de constantes podría cambiar en el futuro. Si defines una API con una constante UNKNOWN
o UNSPECIFIED
que parece ser un comodín, los desarrolladores suponen que las constantes publicadas cuando escribieron su app son exhaustivas. Si no quieres establecer esta expectativa, reconsidera si una constante general es una buena idea para tu API.
Además, las bibliotecas no pueden especificar su propio targetSdkVersion
separado de la app, y el control de los cambios de comportamiento de targetSdkVersion
desde el código de la biblioteca es complicado y propenso a errores.
Constante de número entero o cadena
Usa constantes de números enteros y @IntDef
si el espacio de nombres para 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 compatibilidad binaria o de la API del lenguaje 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 pueden 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 se proporciona una función copy()
en Kotlin, los argumentos deben coincidir con el constructor de la clase y los valores predeterminados se deben propagar 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 cada propiedad debe tenerse en cuenta 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 detalles específicos en los métodos, en torno a 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.
Prefiere los tipos java.time.* cuando sea posible
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 desazucaración y se deben preferir cuando se expresa el tiempo en los parámetros de la API o los valores que se muestran.
Prefiere exponer solo las variantes de una API que acepten o devuelvan java.time.Duration
o java.time.Instant
, y omite las 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 duration
Si un valor de tiempo expresa la duración involucrada, denomina el parámetro "duration", no "time".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Excepciones:
"timeout" es adecuado cuando la duración se aplica específicamente a un valor de tiempo de espera.
"time" con un tipo de java.time.Instant
es adecuado cuando se hace referencia a un momento específico, no a una duración.
Los métodos que expresan duraciones o tiempo como un elemento primitivo deben nombrarse con su unidad de tiempo y usar long.
Los métodos que aceptan o devuelven duraciones como un tipo primitivo deben agregar al nombre del método el sufijo 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 que se mide como la cantidad de milisegundos desde 1970-01-01T00:00:00Z.@CurrentTimeSecondsLong
: El valor es una marca de tiempo no negativa que se mide 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 o valores de devolución de tiempo primitivos deben usar long
, no int
.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
Los métodos que expresan unidades de tiempo deben preferir la abreviatura 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);
Anota argumentos de tiempo largos
La plataforma incluye varias anotaciones para proporcionar una escritura más sólida para las unidades de tiempo de tipo long
:
@CurrentTimeMillisLong
: El valor es una marca de tiempo no negativa que se mide como la cantidad de milisegundos desde1970-01-01T00:00:00Z
, por lo que se encuentra en la base de tiempoSystem.currentTimeMillis()
.@CurrentTimeSecondsLong
: El valor es una marca de tiempo no negativa que se mide como la cantidad de segundos transcurridos 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, se prefieren los prefijos de unidades del SI en formato CamelCase.
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 un orden coherente con los demás 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 agregues sobrecargas para argumentos opcionales, el comportamiento de los métodos más simples debe ser exactamente el mismo que si se hubieran proporcionado argumentos predeterminados a los métodos más elaborados.
Corolario: No sobrecargues los métodos, a menos que sea para agregar argumentos opcionales o 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 en Kotlin).
Los métodos y constructores con parámetros predeterminados deben anotarse con @JvmOverloads
para mantener la compatibilidad binaria.
Consulta Sobrecarga 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 quites los valores de parámetros predeterminados (solo para Kotlin)
Si un método se lanzó con un parámetro que tiene un valor predeterminado, quitar ese valor es un cambio que rompe la compatibilidad con el código fuente.
Los parámetros del método más distintivos y de identificación deben ir primero.
Si tienes un método con varios parámetros, coloca los más relevantes primero. 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: Coloca los parámetros opcionales al final en las sobrecargas
Compiladores
Se recomienda el patrón Builder para crear objetos Java complejos y se usa comúnmente 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 suele indicar problemas con la usabilidad de la API.
Considera si necesitas un compilador. Los compiladores son útiles en una superficie de API si se usan para lo siguiente:
- Configurar solo algunos de un conjunto potencialmente grande de parámetros de creación opcionales
- Configurar muchos parámetros de creación diferentes, opcionales o obligatorios, a veces de tipos similares o coincidentes, en los que los sitios de llamada podrían resultar confusos de leer o propensos a errores al escribirlos
- Configurar la creación de un objeto de forma incremental, en la que varios fragmentos diferentes de código de configuración pueden realizar llamadas al compilador como detalles de implementación
- Permitir 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 ningún parámetro opcional, casi siempre puedes omitir un compilador y usar un constructor simple.
Las clases provenientes de Kotlin deben preferir los constructores anotados con @JvmOverloads
con argumentos predeterminados en lugar de los compiladores, pero pueden optar por mejorar la usabilidad para los clientes de Java proporcionando también 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 devolver el compilador
Las clases de compilador deben habilitar el encadenamiento de métodos devolviendo el objeto Builder (como this
) desde cada método, excepto build()
. Los objetos compilados adicionales se deben pasar como argumentos. No devuelvas el compilador de otro objeto.
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 los casos excepcionales en los que una clase de compilador base debe admitir la extensión, usa un tipo de devolución 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 compilador se deben crear a través de un constructor
Para mantener la creación coherente de compiladores a través de la superficie de la API de Android, todos los compiladores deben crearse a través de un constructor y no de un método de creación 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 de forma implícita 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
de 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 para los constructores de compiladores deben ser obligatorios (como @NonNull).
Los argumentos opcionales, por ejemplo, @Nullable
, se deben mover a los métodos setter.
El constructor del compilador debe arrojar un NullPointerException
(considera usar Objects.requireNonNull
) si no se especifica ningún argumento obligatorio.
Las clases de compilador deben ser clases internas estáticas finales de sus tipos compilados
Para una organización lógica dentro de un paquete, las clases de compilador 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 nueva instancia del compilador 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 copias.
El restablecimiento es fundamental 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 métodos set del compilador pueden tomar argumentos @Nullable para propiedades opcionales
A menudo, es más simple usar un valor anulable para la entrada de segundo grado, en especial en Kotlin, que utiliza argumentos predeterminados en lugar de compiladores y sobrecargas.
Además, los métodos de configuración de @Nullable
coincidirán con sus métodos de obtención, 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 setter) y el significado de null
deben estar correctamente documentados en el setter y el getter.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Se pueden proporcionar métodos set de 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.
Luego, 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 métodos setter para todas las propiedades pertinentes, ya sean mutables o inmutables.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Es posible que se deban realizar algunas llamadas adicionales antes de que el objeto compilado sea útil, por lo que no se deben proporcionar setters para las 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 métodos get
El captador debe estar en el objeto compilado, no en el compilador.
Los métodos set del compilador deben tener 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 de compilación
Los nombres de los métodos de compilación deben usar el estilo setFoo()
, addFoo()
o clearFoo()
.
Se espera que las clases de compilador declaren un método build()
Las clases de compilador deben declarar un método build()
que devuelva una instancia del objeto compilado.
Los métodos build() del compilador deben devolver objetos @NonNull
Se espera que el método build()
de un compilador devuelva una instancia no nula del objeto compilado. En el caso de que el objeto no se pueda crear debido a parámetros no válidos, la validación se puede diferir al método de compilación y se debe arrojar un IllegalStateException
.
No expongas los 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 el bloqueo y, debido a que está expuesta a otros, es posible que encuentres efectos secundarios inesperados si otro código fuera de tu clase comienza a usarlo para fines de bloqueo.
En su lugar, realiza cualquier bloqueo requerido 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 las propiedades de Kotlin
Cuando se ven desde fuentes de Kotlin, los métodos con estilo de acceso (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 las expectativas de los desarrolladores en general sobre el 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 descriptor 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 otro trabajo de larga duración para devolver un valor, como IPC o E/S, por lo que se prefiere
fetch
. - El método bloquea el subproceso hasta que puede devolver un valor. Se prefiere
await
. - El método devuelve una nueva instancia de objeto en cada llamada. Se prefiere
create
. - Es posible que el método no devuelva un valor correctamente. Se recomienda usar
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. Los bloqueos no se amortizan 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 se responden con el valor de devolución.
Los métodos de acceso booleanos de Java deben seguir un esquema de nomenclatura 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;
Usar 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
En 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 comprensión del 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 propiedad 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 puede y debe:
// "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 se responden con el valor de devolución.
Métodos de propiedad de Kotlin
Para una propiedad de clase var foo: Foo
, Kotlin generará métodos get
/set
con una regla coherente: antepone get
y pon en mayúscula el primer carácter para el getter, y antepone set
y pon en mayúscula el primer carácter para el setter. La declaración de la propiedad producirá métodos llamados public Foo getFoo()
y public void setFoo(Foo foo)
, respectivamente.
Si la propiedad es de tipo Boolean
, se aplica una regla adicional en la generación de nombres: si el nombre de la propiedad comienza con is
, no se antepone get
para el nombre del método getter, sino que se usa el nombre de la propiedad como getter.
Por lo tanto, prefiere nombrar las propiedades Boolean
con un prefijo is
para seguir el lineamiento de nomenclatura:
var isVisible: Boolean
Si tu propiedad es una de las excepciones mencionadas y comienza con un prefijo adecuado, 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
Accesorios de máscara de bits
Consulta Usa @IntDef
para marcas de máscara de bits para obtener instrucciones de la API sobre cómo definir marcas de máscara de bits.
Establecedores
Se deben proporcionar dos métodos setter: uno que tome una cadena de bits completa y sobrescriba 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);
Getters
Se debe proporcionar un getter 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 public en lugar de protected
Siempre se debe preferir public
a protected
en la API pública. A la larga, el acceso protegido resulta doloroso, ya que los implementadores deben anularlo para proporcionar accesores públicos en los casos en que el acceso externo predeterminado habría sido igual de bueno.
Recuerda que la visibilidad protected
no impide que los desarrolladores llamen a una API, solo la hace un poco más molesta.
Implementa equals() y hashCode() o ninguno de los dos.
Si anulas uno, debes anular el otro.
Implementa toString() para las 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. Por ejemplo, UUID.toString() y File.toString() documentan su formato específico para que los programas lo usen. Si expones información solo para la depuración, como Intent, implica que heredas la documentación de la superclase.
No incluyas información adicional
Toda la información disponible en toString()
también debe estar disponible a través de la API pública del objeto. De lo contrario, estarás alentando a los desarrolladores a analizar y confiar en el resultado de toString()
, lo que impedirá cambios futuros. Una buena práctica 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 de la salida de depuración, incluir el System.identityHashCode
de tu objeto en su salida de toString()
hará que sea muy poco probable que dos objetos diferentes tengan la misma salida de toString()
.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
Esto puede disuadir a los desarrolladores de escribir aserciones de prueba como assertThat(a.toString()).isEqualTo(b.toString())
en tus objetos.
Usa createFoo cuando devuelvas objetos creados recientemente
Usa el prefijo create
, no get
ni new
, para los métodos que crearán valores de devolución, por ejemplo, construyendo objetos nuevos.
Cuando el método cree un objeto para devolver, indícalo claramente 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 deberían aceptar transmisiones
Las ubicaciones de almacenamiento de datos en Android no siempre son archivos en el disco. Por ejemplo, el contenido que se pasa entre los límites del usuario se representa como content://
Uri
s. 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 devuelve primitivos 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 a los valores de los métodos y, lo que es más importante, la conversión automática que se produce a partir de la conversión entre tipos primitivos y de objetos. Evitar estos comportamientos ahorra memoria y asignaciones temporales que pueden generar recolecciones de elementos no usados costosas y más frecuentes.
Usa anotaciones para aclarar los valores válidos de parámetros y devoluciones
Se agregaron anotaciones para desarrolladores para ayudar a aclarar los valores permitidos en diversas 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 sea apropiado:
Nulabilidad
Las anotaciones de nulabilidad explícitas son obligatorias para las APIs de Java, pero el concepto de nulabilidad forma parte del lenguaje Kotlin, y las anotaciones de nulabilidad nunca se deben usar en las APIs de Kotlin.
@Nullable
: Indica que un valor que se muestra, un parámetro o un campo determinados pueden ser nulos:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: Indica que un valor de retorno, un parámetro o un campo determinados no pueden ser nulos. Marcar elementos como @Nullable
es relativamente nuevo en Android, por lo que la mayoría de los métodos de la API de Android no están documentados de manera coherente. Por lo tanto, tenemos un estado de tres valores: "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 la documentación de la plataforma de Android, anotar los parámetros del método generará automáticamente documentación en la forma "Este valor puede ser nulo", a menos que "nulo" se use explícitamente en otro lugar de la documentación del parámetro.
Métodos existentes "no realmente anulables": Los métodos existentes en la API sin una anotación @Nullable
declarada se pueden anotar como @Nullable
si el método puede devolver 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 de nulos.
Métodos de interfaz: Las APIs nuevas deben agregar la anotación adecuada cuando implementen 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 "corregir" las APIs existentes que no tienen las anotaciones.
Aplicación de nulabilidad
En Java, se recomienda que los métodos realicen la validación de entrada para los parámetros @NonNull
con Objects.requireNonNull()
y arrojen un NullPointerException
cuando los parámetros sean nulos. Esto se realiza automáticamente en Kotlin.
Recursos
Identificadores de recursos: Los parámetros de números enteros 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 la anotación general @AnyRes
. Por ejemplo:
public void setTitle(@StringRes int resId)
@IntDef para conjuntos de constantes
Constantes mágicas: Los parámetros String
y int
que deben recibir uno de un conjunto finito de valores posibles denotados por constantes públicas deben anotarse de forma adecuada con @StringDef
o @IntDef
. Estas anotaciones te permiten crear una nueva anotación que puedes usar y 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 que los métodos verifiquen la validez de los parámetros anotados y arrojen 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 cadena
También existe la anotación @StringDef
, que es exactamente igual a @IntDef
de la sección anterior, pero para las constantes String
. Puedes incluir varios valores de "prefijo" que se usan para emitir automáticamente la documentación de todos los valores.
@SdkConstant para constantes del 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
, FEATURE
.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";
Proporciona compatibilidad con valores nulos para las anulaciones
Para la compatibilidad de la API, la nulabilidad de las anulaciones debe ser compatible con la nulabilidad actual del elemento superior. En la siguiente tabla, se representan las expectativas de compatibilidad. En términos sencillos, 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 devuelve | Sin anotaciones | Sin anotar o no nulo |
Tipo de datos que se devuelve | Anulable | Puede ser nulo o no |
Tipo de datos que se devuelve | Nonnull | Nonnull |
Argumento divertido | Sin anotaciones | Sin anotaciones o anulable |
Argumento divertido | Anulable | Anulable |
Argumento divertido | Nonnull | Puede ser nulo o no |
Siempre que sea posible, prefiere los argumentos que no admitan valores nulos (como @NonNull).
Cuando los métodos están sobrecargados, es preferible 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 establecedores de propiedades sobrecargados. El argumento principal no debe ser nulo, y el borrado de la propiedad debe implementarse como un método independiente. Esto evita las llamadas "sin sentido" en las que el desarrollador debe establecer parámetros finales aunque no sean necesarios.
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 anulables (como @NonNull) para los contenedores
Para los tipos de contenedores, como Bundle
o Collection
, devuelve un contenedor vacío y, cuando corresponda, inmutable. En los casos en los 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 nulabilidad para los pares de 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 nulabilidad. Si no se sigue este lineamiento, se anulará la sintaxis de propiedades de Kotlin, por lo que agregar anotaciones de nulabilidad que no coincidan con los métodos de propiedades existentes es un cambio que interrumpe el código fuente para los usuarios de Kotlin.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Valor de devolución en condiciones de error o falla
Todas las APIs deben permitir que las apps reaccionen ante los errores. Devolver false
, -1
, null
o cualquier otro valor genérico de "algo salió mal" no le brinda al desarrollador suficiente información 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 creando una app. Si encuentras un error, ¿la API te brinda suficiente información para presentárselo al usuario o reaccionar de manera adecuada?
- Está bien (y se recomienda) incluir información detallada en un mensaje de excepción, pero los desarrolladores no deberían tener que analizarlo para controlar el error de forma adecuada. Los códigos de error detallados o cualquier otra información se deben exponer como métodos.
- Asegúrate de que la opción de administración de errores que elijas te brinde la flexibilidad necesaria para introducir nuevos tipos de errores en el futuro. Para
@IntDef
, eso significa incluir un valor deOTHER
oUNKNOWN
. Cuando devuelvas un código nuevo, puedes verificar eltargetSdkVersion
de la entidad que realiza la llamada para evitar devolver un código de error que la app no conoce. En el caso de 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. - Debería ser difícil o imposible que un desarrollador ignore accidentalmente un error. Si el error se comunica devolviendo un valor, anota tu método con
@CheckResult
.
Es preferible arrojar un ? extends RuntimeException
cuando se alcanza una condición de falla o 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 de acción o de configuración (por ejemplo, perform
) pueden devolver un código de estado entero si la acción puede fallar como resultado de un estado o condiciones actualizados de forma asíncrona 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 enumerados en una anotación @hide
@IntDef
.
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étera), no con el objeto sobre 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 de devolución
Las interfaces de colección con tipos genéricos proporcionan varias ventajas en comparación con los arrays, incluidos contratos de API más sólidos en torno a la unicidad y el orden, compatibilidad con genéricos y varios métodos de conveniencia fáciles de usar para los desarrolladores.
Excepción para primitivas
Si los elementos son primitivos, sí prefiere los arrays para evitar el costo del autoboxing. Consulta Toma y devuelve primitivas sin procesar en lugar de versiones empaquetadas
Excepción para el código sensible al rendimiento
En ciertos casos, cuando la API se usa en código sensible al rendimiento (como gráficos o otras APIs 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 invariantes, y el lenguaje Kotlin proporciona una gran cantidad de APIs de utilidad en torno a 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 las colecciones @NonNull
Siempre se prefiere @NonNull
para los objetos de colección. Cuando devuelvas una colección vacía, usa el método Collections.empty
adecuado para devolver un objeto de colección inmutable, con el tipo correcto y de bajo costo.
Cuando se admitan anotaciones de tipo, siempre se debe preferir @NonNull
para los elementos de la colección.
También debes preferir @NonNull
cuando uses arrays en lugar de colecciones (consulta el elemento anterior). Si la asignación de objetos es un problema, crea una constante y pásala, ya que 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 devolución 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 devolución mutable.
Sin embargo, las APIs de Java deberían preferir los tipos de devolución mutables de forma predeterminada, ya que la implementación de la plataforma de Android de las APIs de Java aún no proporciona una implementación conveniente de las colecciones inmutables. La excepción a esta regla son los tipos de devolución Collections.empty
, que son inmutables. En los casos en que los clientes podrían aprovechar la mutabilidad, ya sea a propósito o por error, para romper el patrón de uso previsto de la API, las APIs de Java deberían considerar seriamente devolver 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 devolución explícitamente mutables
Idealmente, las APIs que devuelven colecciones no deberían modificar el objeto de colección devuelto después de la devolución. Si la colección que se devuelve debe cambiar o reutilizarse de alguna manera (por ejemplo, una vista adaptada de un conjunto de datos mutable), el comportamiento preciso de cuándo pueden cambiar los contenidos debe documentarse de forma explícita o seguir las convenciones de nomenclatura de la 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 devuelve .asList()
cambie si cambia la colección original.
Mutabilidad de los objetos de tipo de datos devueltos
Al igual que las APIs que devuelven colecciones, las APIs que devuelven objetos de tipo de datos no deberían modificar las propiedades del objeto devuelto después de devolverlo.
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 algún código sensible al rendimiento se beneficie de la reducción o la reutilización de objetos. No escribas tu propia estructura de datos de grupo de objetos y no expongas objetos reutilizados en las APIs públicas. En cualquier caso, ten mucho cuidado al 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 es probable que el desarrollador cree un array en el sitio de la 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 de vararg
se compilan en el mismo código de bytes respaldado por un array y, como resultado, se pueden llamar desde el código Java con un array mutable. Se recomienda enfáticamente a los diseñadores de APIs que creen una copia superficial defensiva del parámetro de array en los casos en que se persistirá en un campo o 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 brinda 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 la semántica correcta con parámetros de tipo de colección o tipos devueltos
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 estos 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 a partir de un objeto existente, en el que Foo
es el nombre del tipo de datos que devuelve la conversión. Esto es coherente con el JDK conocido Object.toString()
. Kotlin va más allá y lo usa para conversiones primitivas, como 25.toFloat()
.
La distinción entre las conversiones denominadas .toFoo()
y .asFoo()
es importante:
Usa .toFoo() cuando crees un objeto nuevo e independiente
Al igual que .toString()
, una conversión "a" devuelve 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 antiguo 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 conversión
La conversió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 objeto Foo
nuevo 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 de la clase del receptor y del resultado reduce el acoplamiento 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.
Arroja excepciones específicas adecuadas
Los métodos no deben arrojar excepciones genéricas, como java.lang.Exception
o java.lang.Throwable
. En cambio, se debe usar una excepción específica adecuada, como java.lang.NullPointerException
, para permitir que los desarrolladores controlen las excepciones sin ser demasiado generales.
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 devolución de llamada y de escucha.
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 on .
onFooEvent
indica que FooEvent
está sucediendo y que la devolución de llamada debe actuar en respuesta.
El tiempo pasado y el presente deben describir el comportamiento de los tiempos.
Los métodos de devolución de llamada relacionados con eventos deben nombrarse de manera que indiquen si el evento ya ocurrió o está en proceso de ocurrir.
Por ejemplo, si se llama al método después de que se realizó una acción de clic:
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 una devolución de llamada de un objeto, los métodos asociados deben llamarse add y remove o register y unregister. Sé coherente con la convención existente que usa la clase o las otras clases del mismo paquete. Cuando no existe tal precedente, se recomienda 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 tentadora vía de escape para los casos en los que los desarrolladores pueden querer encadenar una devolución de llamada existente con su propio reemplazo, pero es frágil y dificulta que los desarrolladores de componentes razonen sobre el estado actual. 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 creó para permitir tales modificaciones de su devolución de llamada encapsulada.
Acepta Executor para controlar el envío de devoluciones de llamada
Cuando se registran devoluciones de llamada que no tienen expectativas explícitas de subprocesos (casi en cualquier lugar fuera del kit de herramientas de 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 el 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)
Errores comunes de 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 adopten esta forma, la implementación del objeto Binder entrante 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 la app que use la identidad del vinculador (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 desean la información del UID o PID de la persona que llama, esto debe ser una parte explícita de la superficie de tu API, en lugar de implícita según dónde se ejecutó el Executor
que proporcionaron.
Tu API debe admitir la especificación de un Executor
. En casos críticos para el rendimiento, es posible que las apps deban ejecutar código de inmediato o de forma síncrona con los comentarios de tu API. Aceptar un Executor
permite esto.
Crear de forma defensiva un HandlerThread
adicional o similar a un trampolín frustra este caso de uso deseable.
Si una app ejecutará código costoso en algún lugar de su propio proceso, permítelo. Las soluciones alternativas que encontrarán 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, el Handler
de Android se usaba como estándar para redireccionar la ejecución de la devolución de llamada a un subproceso de 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 brindarles a los desarrolladores el control que necesitan para reutilizar 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, por lo que es 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 de Looper
con un Handler
seguido de otro salto desde el framework de simultaneidad de la app.
Las excepciones a este lineamiento son poco frecuentes. Entre las apelaciones comunes para una excepción, se incluyen las siguientes:
Debo usar un Looper
porque necesito un Looper
para epoll
para el evento.
Se otorga esta solicitud de excepción, ya que los beneficios de Executor
no se pueden obtener en esta situación.
No quiero que el código de la app bloquee mi subproceso que publica el evento. Por lo general, esta solicitud de excepción no se otorga para el código que se ejecuta en un proceso de la app. Las apps que no lo hacen bien solo se perjudican a sí mismas, no afectan el estado general del sistema. Las apps que lo hacen bien o usan un framework de simultaneidad común no deberían pagar penalizaciones de latencia adicionales.
Handler
es coherente a nivel 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 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 necesiten 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 con varios métodos
Las devoluciones de llamada de varios métodos deben preferir interface
y usar métodos default
cuando se agreguen a interfaces lanzadas 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
en otros casos, es decir, lo mismo que puede hacer una llamada a un método simple. Usa OutcomeReceiver
como el tipo de devolución de llamada cuando conviertas un método de bloqueo que devuelve un resultado o arroja una excepción a un método asíncrono que no es de bloqueo:
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 devuelven void
. Cualquier resultado que devolvería requestFoo
se informa al OutcomeReceiver.onResult
del parámetro callback
de requestFooAsync
llamándolo en el executor
proporcionado.
Cualquier excepción que arrojaría requestFoo
se informa al método OutcomeReceiver.onError
de la misma manera.
Usar OutcomeReceiver
para informar los resultados de los métodos asíncronos también proporciona un wrapper de suspend fun
de Kotlin para los métodos asíncronos que usan 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())
}
Las extensiones como esta permiten que los clientes de Kotlin llamen a métodos asíncronos sin bloqueo con la comodidad de una llamada de 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ándares. Consulta la documentación de asOutcomeReceiver para obtener más información, consideraciones sobre la cancelación y muestras.
Los métodos asíncronos que no coinciden con la semántica de un método que devuelve 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 indican 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 SAM genéricas, como Consumer<T>
, que son adecuadas para usarse como expresiones lambda de devolución de llamada. En muchos casos, crear nuevas interfaces de SAM aporta poco valor en términos de seguridad de tipos o comunicación de la intención, a la vez que expande innecesariamente el área de la API de Android.
Considera usar estas interfaces genéricas en lugar de crear 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 al final para permitir 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) para 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. Supón que el desarrollador encontró el método con la función de autocompletar o mientras navegaba por la documentación de referencia de la API, y tiene una cantidad mínima de contexto de la superficie de la API adyacente (por ejemplo, la misma clase).
Métodos
Los parámetros y los valores de devolución de los métodos se deben documentar con las anotaciones de documentación @param
y @return
, respectivamente. Formatea el cuerpo de Javadoc como si estuviera precedido por "Este método…".
En los casos en que un método no toma parámetros, no tiene consideraciones especiales y devuelve lo que dice el nombre del método, puedes omitir @return
y escribir documentación similar a la siguiente:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Siempre usa vínculos en Javadoc
Los documentos deben vincularse a otros documentos para las constantes, los métodos y otros elementos relacionados. Usa etiquetas de 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 formato ni fuentes 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 el destino update-api o docs cuando agregues Javadoc
Esta regla es especialmente importante cuando agregas etiquetas @link
o @see
, y asegúrate de que el resultado se vea como se espera. El resultado ERROR en Javadoc suele deberse a vínculos incorrectos. El destino update-api
o docs
de Make realiza esta verificación, pero el destino docs
podría ser más rápido si solo cambias Javadoc y no necesitas ejecutar el destino update-api
.
Usa {@code foo} para distinguir los valores de Java
Encierra los valores de Java, como true
, false
y null
, entre {@code...}
para distinguirlos del texto de la documentación.
Cuando escribes documentación en fuentes de Kotlin, puedes incluir código entre comillas inversas, como lo harías en Markdown.
Los resúmenes de @param y @return deben ser un solo fragmento de oración.
Los resúmenes de los parámetros y los valores de devolución deben comenzar con un carácter en minúscula y contener solo 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 del 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.
*/
En ese caso, debería cambiarse por 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 los documentos necesitan explicaciones
Documenta por qué las anotaciones @hide
y @removed
están ocultas en 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 provenientes de Kotlin y destinadas a 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 que se arroja también debe indicar por qué se arrojó.
Algunos 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 un @IntDef
o una anotación similar que incorpora el contrato de 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, 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 se deben documentar con @throws
, a menos que se vuelvan a arrojar desde el método anotado.
Finaliza la primera oración de los documentos con un punto.
La herramienta Doclava analiza los documentos de forma simplificada 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 clase) en cuanto ve un punto (.) seguido de un espacio. Esto genera dos problemas:
- Si un documento breve no termina con un punto y si ese miembro heredó documentos que la herramienta detecta, la sinopsis también detecta esos documentos heredados. Por ejemplo, consulta
actionBarTabStyle
en los documentos deR.attr
, que tiene la descripción de la dimensión agregada a la sinopsis. - Evita usar "p.ej." en la primera oración por el mismo motivo, ya que Doclava finaliza la documentación de la sinopsis después de "ej.". Por ejemplo, consulta
TEXT_ALIGNMENT_CENTER
enView.java
. Ten en cuenta que Metalava corrige automáticamente este error insertando un espacio sin interrupción después del punto. Sin embargo, no cometas este error en primer lugar.
Cómo dar formato a los documentos para que se rendericen en HTML
Javadoc se renderiza en HTML, por lo que debes darle el formato adecuado a estos documentos:
Los saltos de línea deben usar una etiqueta
<p>
explícita. No agregues una etiqueta de cierre</p>
.No uses ASCII para renderizar listas o tablas.
Las listas deben usar
<ul>
o<ol>
para listas sin ordenar y ordenadas, respectivamente. Cada elemento debe comenzar con una etiqueta<li>
, pero no necesita una etiqueta de cierre</li>
. 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á en desuso.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 marcarlos como escape con las entidades HTML<
y>
.También puedes dejar corchetes sin procesar
<>
en tu fragmento de código si encierras 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 proporcionar 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 compiladores 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 clave a valor con tipo. En su lugar, considera usar Bundle
.
Esto suele ocurrir cuando se escriben APIs de la plataforma que sirven 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 API puede definirse parcialmente fuera de la plataforma (por ejemplo, en una biblioteca de Jetpack).
En los casos en que la plataforma sí lee los datos, evita usar Bundle
y prefiere una clase de datos con escritura segura.
Las implementaciones de Parcelable deben tener un campo CREATOR público
La expansión 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 se trata de una clave o alguna otra etiqueta o valor que no es visible para los usuarios, String
es suficiente.
Evita usar enumeraciones
IntDef
se debe usar en lugar de enumeraciones en todas las APIs de la plataforma y se debe considerar seriamente en las APIs de bibliotecas no agrupadas. Usa enumeraciones solo cuando tengas la certeza de que no se agregarán valores nuevos.
Beneficios deIntDef
:
- Permite agregar valores con el tiempo.
- Las instrucciones
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 instrucciones
- No se usan clases ni objetos en el tiempo de ejecución, solo tipos primitivos.
- Si bien R8 o la minificación pueden evitar este costo para las APIs de bibliotecas no agrupadas, esta optimización no puede afectar las clases de las APIs de la plataforma.
Beneficios de la enumeración
- Función idiomática del lenguaje de Java y Kotlin
- Habilita el uso exhaustivo de la instrucción
when
.- Nota: Los valores no deben cambiar con el tiempo. Consulta la lista anterior.
- Nombres claros y fáciles de encontrar
- Habilita la verificación en tiempo de compilación.
- Por ejemplo, una instrucción
when
en Kotlin que devuelve un valor
- Por ejemplo, una instrucción
- Es una clase funcional que puede implementar interfaces, tener asistentes estáticos, exponer métodos de extensión o miembros, y exponer campos.
Sigue la jerarquía de capas del paquete 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 los 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 tiene como objetivo ser independiente del proveedor. La API debe ser genérica y los integradores de sistemas o las apps con los permisos necesarios deben poder usarla por igual.
Las implementaciones de Parcelable deben ser finales
Las clases Parcelable definidas por 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 de recepción no tendrá la implementación personalizada del remitente para desempaquetar. Nota sobre la compatibilidad con versiones anteriores: Si tu clase históricamente no era final, pero no tenía un constructor disponible públicamente, aún puedes marcarla como final
.
Los métodos que llaman al proceso del sistema deben volver a lanzar RemoteException como RuntimeException
Por lo general, RemoteException
se arroja por AIDL interno y indica que el proceso del sistema finalizó o que la app está intentando enviar demasiados datos. En ambos casos, la API pública debe volver a arrojar una excepción como RuntimeException
para evitar que las apps conserven decisiones de seguridad o políticas.
Si sabes que el otro extremo de una llamada de Binder
es el proceso del sistema, este código estándar es la práctica recomendada:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Lanza excepciones específicas para los cambios en la API
El comportamiento de las APIs públicas puede cambiar entre los distintos niveles de API y provocar fallas en las apps (por ejemplo, para aplicar nuevas políticas de seguridad).
Cuando la API necesite arrojar una excepción para 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 copias en lugar de clonar
No se recomienda usar el método clone()
de Java debido a la falta de contratos de API que proporciona la clase Object
y a las dificultades inherentes para extender las 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 compilador para la construcción deben considerar agregar un constructor de copia del compilador 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 oscuros de uso después del cierre. En cambio, las APIs deberían devolver 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 impares
Evita usar valores de short
o byte
directamente, ya que suelen limitar la forma en que podrías desarrollar 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 las 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 un long
con @IntDef
. En situaciones de bajo rendimiento, considera usar un Set<EnumType>
. Para los datos binarios sin procesar, usa byte[]
.
Prefiere android.net.Uri
android.net.Uri
es la encapsulación preferida 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.
Oculta 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 inserta 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, los usos de estas anotaciones deben marcarse con la anotación de documentación @hide
en la plataforma o la anotación de código @RestrictTo.Scope.LIBRARY)
en las bibliotecas. En ambos casos, deben marcarse como @Retention(RetentionPolicy.SOURCE)
para evitar que aparezcan en los stubs de la API o en los archivos JAR.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
Cuando se compilan el SDK de la plataforma y los archivos AAR de la biblioteca, una herramienta extrae las anotaciones y las agrupa por separado de las fuentes compiladas. Android Studio lee este formato incluido y aplica las definiciones de tipo.
No agregues claves de proveedores de configuración nuevas
No expongas claves nuevas desde Settings.Global
, Settings.System
o Settings.Secure
.
En cambio, agrega una API de Java de getter y setter adecuada en una clase relevante, que suele ser una clase de "administrador". Agrega un mecanismo 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 tipos.
- 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 correctamente.
- 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
existió durante mucho tiempo, pero el equipo de ubicación la marcó como obsoleta para una
API de Java adecuada
LocationManager.isLocationEnabled()
y la transmisión
MODE_CHANGED_ACTION
, que le brindó al equipo mucha más flexibilidad, y la semántica de las
APIs ahora es mucho más clara.
No extiendas Activity y AsyncTask
AsyncTask
es un detalle de implementación. En su lugar, expón un objeto de escucha o, en androidx, una API de ListenableFuture
.
Las subclases de Activity
son imposibles de componer. Extender la actividad de tu función la hace incompatible con otras funciones que requieren que los usuarios hagan lo mismo. En su lugar, usa la composición con herramientas como LifecycleObserver.
Usa el método getUser() de Context
Las clases vinculadas a un Context
, como cualquier elemento que se devuelva desde Context.getSystemService()
, deben usar el usuario vinculado al Context
en lugar de exponer miembros que se segmenten 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 del usuario si acepta valores que no representan a un solo usuario, como UserHandle.ALL
.
Usa UserHandle en lugar de números enteros simples
Se prefiere UserHandle
para proporcionar seguridad de tipos y evitar confundir los IDs de usuario con los UIDs.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Cuando sea inevitable, un int
que represente un ID de usuario debe anotarse con @UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Prefiere los objetos de escucha o las devoluciones de llamada a las intents de transmisión
Los intents de transmisión son muy potentes, pero han generado comportamientos emergentes que pueden afectar negativamente el estado del sistema, por lo que los nuevos intents de transmisión se deben agregar con prudencia.
Estas son algunas preocupaciones específicas que nos llevan a desaconsejar la introducción de nuevos intents de transmisión:
Cuando se envían emisiones sin la marca
FLAG_RECEIVER_REGISTERED_ONLY
, se fuerzan a iniciar las apps que aún no se están ejecutando. Si bien a veces este puede ser un resultado deseado, puede provocar una avalancha de decenas de apps, lo que afecta negativamente el estado del sistema. Te recomendamos que uses estrategias alternativas, comoJobScheduler
, para coordinar mejor cuándo se cumplen 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 introducir cambios en el comportamiento según el SDK objetivo de la app receptora.
Dado que las colas de transmisión son un recurso compartido, pueden sobrecargarse y es posible que no se entregue tu evento 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 el uso de objetos de escucha o devoluciones de llamada, o bien otras herramientas, como JobScheduler
, en lugar de intents de transmisión.
En los casos en los que las intents de transmisión siguen 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 tu transmisión a las apps que ya se están ejecutando. Por ejemplo,ACTION_SCREEN_ON
usa este diseño para evitar activar las apps. - Si es posible, usa
Intent.setPackage()
oIntent.setComponent()
para segmentar la transmisión hacia una app específica de interés. Por ejemplo,ACTION_MEDIA_BUTTON
usa este diseño para enfocarse en la app actual que controla la reproducción. - Si es posible, define tu emisión como
<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 pretende 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 debe anotarse con@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Documento sobre la clase a la que un desarrollador debe agregar un
<intent-filter>
a suAndroidManifest.xml
para recibir intents de la plataforma. - Considera seriamente agregar un permiso a nivel del sistema para evitar que las apps no autorizadas envíen
Intent
s a los servicios para desarrolladores.
Interoperabilidad de Kotlin y Java
Consulta la guía oficial de interoperabilidad de Kotlin y Java 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 suspend fun
s, 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 Async para obtener orientación específica.
Objetos complementarios
Kotlin usa companion object
para exponer miembros estáticos. En algunos casos, estos se mostrará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 relacionadas con 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 las bases de código existentes.
Cambios rotundos del objeto binario
Evita los cambios que rompan la compatibilidad binaria en las superficies de la API pública finalizadas. Por lo general, estos tipos de cambios generan errores cuando se ejecuta make update-api
, pero puede haber casos extremos que la verificación de la API de Metalava no detecte. En caso de duda, consulta la guía Evolving Java-based APIs de la Eclipse Foundation para obtener una explicación detallada de qué tipos de cambios en la API son compatibles en Java. Los cambios que rompen la compatibilidad binaria en las APIs ocultas (por ejemplo, del sistema) deben seguir el ciclo de obsolescencia/reemplazo.
Cambios que afectan el código fuente
Desaconsejamos los cambios que rompen el código fuente, incluso si no rompen el código binario. Un ejemplo de cambio compatible con objetos binarios, pero que interrumpe la fuente, es agregar un elemento genérico a una clase existente, que es compatible con objetos binarios, pero puede introducir errores de compilación debido a la herencia o a referencias ambiguas.
Los cambios que interrumpen el código fuente no generarán errores cuando se ejecute make update-api
, por lo que debes tener cuidado para comprender el impacto de los cambios en las firmas de API existentes.
En algunos casos, los cambios que interrumpen el código fuente son necesarios para mejorar la experiencia del desarrollador o la corrección 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 anotadas con @TestApi
en cualquier momento.
Debes conservar las APIs anotadas con @SystemApi
durante tres años. Debes quitar o refactorizar una API del sistema según el siguiente cronograma:
- API y - Added
- API y+1: Obsolescencia
- Marca el código con
@Deprecated
. - Agrega reemplazos y vincula el reemplazo en el Javadoc del código en desuso con la anotación de documentos
@deprecated
. - Durante el ciclo de desarrollo, registra errores contra los usuarios internos para informarles que la API dejará de estar disponible. Esto ayuda a validar que las APIs de reemplazo sean adecuadas.
- Marca el código con
- API y+2: Eliminación no definitiva
- Marca el código con
@removed
. - De forma opcional, se puede lanzar una excepción o no hacer nada para las apps que se segmentan para el nivel de SDK actual de la versión.
- Marca el código con
- API y+3: Eliminación definitiva
- Quita por completo el código del árbol de origen.
Baja
Consideramos que la obsolescencia 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 documentación @deprecated
<summary>
juntas cuando dejes de usar APIs. Tu resumen debe incluir una estrategia de migración. Esta estrategia puede vincularse 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 desaprobar las APIs definidas en XML y expuestas en Java, incluidos los atributos y las propiedades de diseño 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 se debe desaprobar 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 desaprobar una API, considera el impacto en los desarrolladores. Los efectos de la baja de una API incluyen lo siguiente:
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 en desuso antes de poder actualizar la versión del SDK de compilación. - No se pueden suprimir las advertencias de baja en las importaciones de clases obsoletas. Como resultado, los desarrolladores deben insertar el nombre de clase completamente 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 IDE, como Android Studio, muestran una advertencia en el sitio de uso de la API.
- Es posible que los IDEs reduzcan la clasificación o oculten la API en la función de autocompletar.
Como resultado, la baja de una API puede desalentar a los desarrolladores que se preocupan más por la calidad del código (los que usan -Werror
) a adoptar nuevos SDKs.
Es probable que los desarrolladores que no se preocupan por las advertencias en su código existente ignoren las bajas por completo.
Un SDK que introduce una gran cantidad de obsolescencias empeora ambos casos.
Por este motivo, recomendamos que se dejen de usar 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 indefinido que no podemos corregir sin romper la compatibilidad.
Cuando dejas de usar una API y la reemplazas por una nueva, te recomendamos agregar una API de compatibilidad correspondiente a una biblioteca de Jetpack, como androidx.core
, para simplificar la compatibilidad con dispositivos antiguos y nuevos.
No recomendamos que se dejen de usar 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 que se dejaron de usar. Esto significa que las implementaciones de prueba deben seguir siendo las mismas y las pruebas deben seguir pasando después de que hayas dejado de usar la API. Si la API no tiene pruebas, debes agregarlas.
No expandir las superficies de API obsoletas en versiones futuras Puedes agregar anotaciones de corrección de lint (por ejemplo, @Nullable
) a una API existente que esté obsoleta, pero no debes agregar APIs nuevas.
No agregues APIs nuevas como obsoletas. Si se agregaron APIs y, luego, se dejaron de usar dentro de un ciclo de versión preliminar (por lo que inicialmente ingresarían a la superficie 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 que afecta el código fuente, por lo que debes evitarla en las APIs públicas, a menos que el Consejo de APIs la apruebe de forma explícita.
En el caso de las APIs del sistema, debes marcar la API como obsoleta durante el período de una versión principal antes de quitarla de forma gradual. Quita todas las referencias de la documentación a las APIs y usa la anotación de documentación @removed <summary>
cuando quites APIs de forma temporal. Tu 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 que es más importante, debe conservarse de modo 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 las pruebas, pero es posible que el contenido de las pruebas deba cambiar para adaptarse a los cambios de comportamiento. Las pruebas deben validar que las personas que llaman existentes no fallen durante el tiempo de ejecución. Puedes mantener el comportamiento de las APIs que se quitaron de forma temporal tal como está, pero, lo que es más importante, 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 las pruebas, pero es posible que el contenido de las pruebas deba cambiar para adaptarse a los cambios de comportamiento. Las pruebas deben validar que las personas que llaman existentes no fallen durante el tiempo de ejecución.
A nivel técnico, quitamos la API del JAR de código auxiliar del SDK y de la ruta de acceso de clase en tiempo de compilación con la anotación @remove
de Javadoc, pero sigue existiendo en la ruta de acceso de clase en tiempo de ejecución, de manera 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 el autocompletado, 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 se sigue compilando correctamente en SDKs anteriores, y los archivos binarios que hacen referencia a la API siguen funcionando.
Ciertas categorías de APIs no deben quitarse de forma no definitiva. No debes quitar de forma temporal ciertas categorías de APIs.
Métodos abstractos
No debes quitar de forma no definitiva los métodos abstractos en las clases que los desarrolladores podrían extender. Si lo hacen, los desarrolladores no podrán extender la clase correctamente en todos los niveles del SDK.
En los casos excepcionales en los que nunca fue y no será posible que los desarrolladores extiendan una clase, aún puedes quitar de forma parcial los métodos abstractos.
Eliminación definitiva
La eliminación definitiva es un cambio que interrumpe la compatibilidad binaria y nunca debe ocurrir en las APIs públicas.
Anotación desaconsejada
Usamos la anotación @Discouraged
para indicar que no recomendamos una API en la mayoría de los casos (más del 95%). Las APIs desaconsejadas se diferencian de las APIs obsoletas en que existe un caso de uso crítico y limitado que impide 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 agregues 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 comunicar claramente cuando los desarrolladores intentaban publicar eventos demasiado grandes para enviarlos a través de Binder
.
Sin embargo, para evitar problemas en las apps existentes, recomendamos conservar un comportamiento seguro para las apps más antiguas. Históricamente, protegimos estos cambios de comportamiento en función de la ApplicationInfo.targetSdkVersion
de la app, pero recientemente migramos para exigir el uso del marco de compatibilidad de la app. 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 marco 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 obligarlos 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 el código una vez, probarlo una vez y que se ejecute en todas partes sin problemas.
Los siguientes son los problemas de compatibilidad con versiones posteriores 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 superficie de la API (por ejemplo, compatibilidad para asignar recursos de tipo
ColorStateList
en XML, donde antes solo se admitían recursos de tipo<color>
). - Se flexibilizaron las restricciones en las verificaciones de tiempo 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 en el tiempo de ejecución. Peor aún, es posible que se enteren a través de los informes de fallas de dispositivos más antiguos en el campo.
Además, todos estos casos son cambios en la API técnicamente válidos. No interrumpen la compatibilidad binaria ni de código fuente, y lint de API no detectará ninguno de estos problemas.
Por lo tanto, los diseñadores de APIs deben prestar mucha atención cuando modifican clases existentes. Haz la siguiente pregunta: "¿Este cambio provocará que el código escrito y probado 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 los componentes, ese esquema debe especificarse de forma explícita y debe evolucionar de manera retrocompatible, de forma similar a otras APIs de Android. Por ejemplo, la estructura de los atributos y elementos XML debe conservarse de manera similar a la forma en que se mantienen los métodos y las variables en otras superficies de la API de Android.
Baja de XML
Si deseas desaprobar 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 marcar el tipo anterior como obsoleto y presentar un tipo 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 mainline") del SO Android de forma individual, en lugar de actualizar toda la imagen del sistema.
Los módulos de Mainline deben "desvincularse" de la plataforma principal, lo que significa que todas las interacciones entre cada módulo y el resto del mundo deben realizarse a través de APIs formales (públicas o del sistema).
Existen ciertos patrones de diseño que deben seguir los módulos de la línea principal. En esta sección, se describen.
El patrón <Module>FrameworkInitializer
Si un módulo de la línea principal necesita exponer clases @SystemService
(por ejemplo, JobScheduler
), usa el siguiente patrón:
Expón una clase
<YourModule>FrameworkInitializer
desde tu módulo. Esta clase debe estar en$BOOTCLASSPATH
. Ejemplo: StatsFrameworkInitializerMárcalo con
@SystemApi(client = MODULE_LIBRARIES)
.Agrégale 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
Normalmente, para registrar objetos de vinculador de servicios del sistema o obtener referencias a ellos, se usaría ServiceManager
, pero los módulos de la línea principal no pueden usarlo porque está oculto. Esta clase está oculta porque los módulos de la línea principal no deben registrar ni hacer referencia a objetos de vinculador de servicios del sistema expuestos por la plataforma estática o por otros módulos.
Los módulos de Mainline pueden usar el siguiente patrón para 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:
- Constructor oculto, por lo que solo el código de la plataforma estática puede crear instancias de él.
- Métodos getter públicos que devuelven una instancia de
ServiceRegisterer
para un nombre específico. Si tienes un objeto vinculador, necesitas un método getter. 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 expuesto por tu módulo. Normalmente, agregas una API de@SystemApi(client = MODULE_LIBRARIES)
estática en tu claseFrameworkInitializer
que la toma.
Este patrón evitaría que otros módulos de la línea principal 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.
Así es como la telefonía obtiene una referencia al servicio de telefonía: vínculo de búsqueda de código.
Si implementas un objeto vinculador de servicio 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 de la línea principal. No los uses para registrar o hacer referencia a objetos de vinculador que no sean propiedad de tu módulo. Si expones un objeto de vinculador desde código nativo, tu <YourModule>ServiceManager.ServiceRegisterer
no necesita un método register()
.
Definiciones de permisos en módulos de línea principal
Los módulos de Mainline que contienen APKs pueden definir permisos (personalizados) en su APK AndroidManifest.xml
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 como prefijo el 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 proporcionará como parte de una API de plataforma actualizable para otras apps, su nombre debe tener el prefijo "android.permission.". (como cualquier permiso estático de la plataforma) más el nombre del paquete del módulo, para indicar que es una API de la plataforma de 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
.