Esta página é um guia para que os desenvolvedores entendam os princípios gerais que o Conselho de API aplica nas análises de API.
Além de seguir essas diretrizes ao escrever APIs, os desenvolvedores precisam executar a ferramenta API Lint, que codifica muitas dessas regras em verificações que são executadas em APIs.
Pense nisso como o guia das regras que são obedecidas pela ferramenta Lint, além de conselhos gerais sobre regras que não podem ser codificadas com alta precisão.
Ferramenta API Lint
O API Lint
está integrado à ferramenta de análise estática do Metalava e é executado automaticamente
durante a validação no CI. É possível executá-lo manualmente em um
checkout de plataforma local usando m
checkapi
ou um checkout local do AndroidX usando
./gradlew :path:to:project:checkApi
.
Regras de API
A plataforma Android e muitas bibliotecas do Jetpack existiam antes da criação desse conjunto de diretrizes, e as políticas definidas mais adiante nesta página estão sempre evoluindo para atender às necessidades do ecossistema Android.
Por isso, algumas APIs atuais podem não seguir as diretrizes. Em outros casos, a experiência do usuário pode ser melhor para os desenvolvedores de apps se uma nova API for consistente com as APIs existentes, em vez de aderir estritamente às diretrizes.
Use seu julgamento e entre em contato com o Conselho de API se houver dúvidas difíceis sobre uma API que precisa ser resolvida ou diretrizes que precisam ser atualizadas.
Noções básicas de API
Essa categoria se refere aos aspectos principais de uma API do Android.
Todas as APIs precisam ser implementadas
Independentemente do público-alvo de uma API (por exemplo, público ou @SystemApi
), todas as plataformas
da API precisam ser implementadas quando mescladas ou expostas como API. Não mescle stubs de API
com implementação para ser feita em uma data posterior.
As plataformas de API sem implementações têm vários problemas:
- Não há garantia de que uma superfície adequada ou completa tenha sido exposta. Até que uma API seja testada ou usada por clientes, não há como verificar se um cliente tem as APIs adequadas para usar o recurso.
- APIs sem implementação não podem ser testadas nas prévias para desenvolvedores.
- APIs sem implementação não podem ser testadas no CTS.
Todas as APIs precisam ser testadas
Isso está de acordo com os requisitos do CTS da plataforma, as políticas do AndroidX e, de modo geral, a ideia de que as APIs precisam ser implementadas.
O teste de superfícies de API oferece uma garantia básica de que a superfície de API é utilizável e que abordamos os casos de uso esperados. Testar a existência não é suficiente. O comportamento da própria API precisa ser testado.
Uma mudança que adiciona uma nova API precisa incluir os testes correspondentes no mesmo tópico de CL ou Gerrit.
As APIs também precisam ser testáveis. Você precisa ser capaz de responder à pergunta "Como um desenvolvedor de apps vai testar o código que usa sua API?"
Todas as APIs precisam ser documentadas
A documentação é uma parte importante da usabilidade da API. Embora a sintaxe de uma plataforma de API possa parecer óbvia, novos clientes não vão entender a semântica, o comportamento ou o contexto por trás dela.
Todas as APIs geradas precisam estar em conformidade com as diretrizes
As APIs geradas por ferramentas precisam seguir as mesmas diretrizes da API que o código escrito manualmente.
Ferramentas que não são recomendadas para gerar APIs:
AutoValue
: viola as diretrizes de várias maneiras, por exemplo, não há como implementar classes de valor final nem builders finais com a forma como o AutoValue funciona.
Estilo de código
Essa categoria se refere ao estilo de código geral que os desenvolvedores precisam usar, principalmente ao criar APIs públicas.
Seguir as convenções de programação padrão, exceto quando indicado
As convenções de programação do Android estão documentadas para colaboradores externos aqui:
https://source.android.com/source/code-style.html
Em geral, seguimos as convenções de programação padrão do Java e do Kotlin.
Os acrônimos não precisam ser escritos em letras maiúsculas nos nomes dos métodos
Por exemplo: o nome do método precisa ser runCtsTests
, e não runCTSTests
.
Os nomes não podem terminar com "Impl"
Isso expõe detalhes de implementação. Evite isso.
Classes
Esta seção descreve regras sobre classes, interfaces e herança.
Herdar novas classes públicas da classe base apropriada
A herança expõe elementos da API na subclasse que podem não ser apropriados.
Por exemplo, uma nova subclasse pública de FrameLayout
se parece com FrameLayout
mais os novos comportamentos e elementos da API. Se a API herdada não for adequada
para seu caso de uso, herde de uma classe mais acima na árvore, por exemplo,
ViewGroup
ou View
.
Se você tiver a tentação de substituir métodos da classe base para gerar
UnsupportedOperationException
, reconsidere qual classe base você está usando.
Usar as classes de coleções básicas
Sempre prefira a classe base à implementação específica, seja para usar uma coleção como argumento ou retornar como um valor, como retornar
List<Foo>
em vez de ArrayList<Foo>
.
Use uma classe base que expresse restrições adequadas para a API. Por
exemplo, use List
para uma API em que a coleção precisa ser ordenada e Set
para uma API em que a coleção precisa consistir em elementos exclusivos.
Em Kotlin, prefira coleções imutáveis. Consulte Mutabilidade de coleções para mais detalhes.
Classes abstratas x interfaces
O Java 8 adiciona suporte a métodos de interface padrão, o que permite que designers de API adicionem métodos a interfaces, mantendo a compatibilidade binária. O código da plataforma e todas as bibliotecas do Jetpack precisam ser direcionadas para o Java 8 ou mais recente.
Nos casos em que a implementação padrão não tem estado, os designers de API devem preferir interfaces a classes abstratas. Ou seja, os métodos de interface padrão podem ser implementados como chamadas para outros métodos de interface.
Nos casos em que um construtor ou estado interno é necessário pela implementação padrão, as classes abstratas precisam ser usadas.
Em ambos os casos, os designers de API podem deixar um único método abstrato para simplificar o uso como uma 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) { }
}
Os nomes das classes precisam refletir o que elas estendem
Por exemplo, as classes que estendem Service
precisam ser nomeadas FooService
para
clareza:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Sufixos genéricos
Evite usar sufixos de nome de classe genéricos, como Helper
e Util
, para coleções
de métodos utilitários. Em vez disso, coloque os métodos diretamente nas classes associadas
ou em funções de extensão do Kotlin.
Nos casos em que os métodos estão conectando várias classes, dê à classe que contém um nome significativo que explique o que ela faz.
Em muitos casos limitados, o uso do sufixo Helper
pode ser adequado:
- Usado para a composição do comportamento padrão
- Pode envolver a delegação de comportamento atual para novas classes
- Pode exigir estado persistente
- Normalmente envolve
View
Por exemplo, se a backport de dicas exigir a persistência do estado associado
a um View
e a chamada de vários métodos no View
para instalar a backport,
TooltipHelper
será um nome de classe aceitável.
Não exponha o código gerado por IDL como APIs públicas diretamente
Mantenha o código gerado pelo IDL como detalhes de implementação. Isso inclui protobuf, sockets, FlatBuffers ou qualquer outra superfície de API que não seja Java ou NDK. No entanto, a maioria dos IDLs no Android está na AIDL. Portanto, esta página se concentra na AIDL.
As classes AIDL geradas não atendem aos requisitos do guia de estilo da API (por exemplo, elas não podem usar sobrecarga) e a ferramenta AIDL não foi projetada explicitamente para manter a compatibilidade da API de linguagem. Portanto, não é possível incorporá-las a uma API pública.
Em vez disso, adicione uma camada de API pública sobre a interface AIDL, mesmo que ela inicialmente seja um wrapper superficial.
Interfaces do Binder
Se a interface Binder
for um detalhe de implementação, ela poderá ser alterada livremente
no futuro, com a camada pública permitindo que a compatibilidade
necessária seja mantida. Por exemplo, talvez seja necessário adicionar novos
argumentos às chamadas internas ou otimizar o tráfego de IPC usando
batch ou streaming, memória compartilhada ou algo semelhante. Isso não será possível
se a interface AIDL também for a API pública.
Por exemplo, não exponha FooService
como uma API pública diretamente:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
Em vez disso, envolva a interface Binder
em um gerenciador ou outra classe:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
Se, mais tarde, um novo argumento for necessário para essa chamada, a interface interna poderá ser mínima e sobrecargas convenientes adicionadas à API pública. É possível usar a camada de acondicionamento para processar outras questões de compatibilidade com versões anteriores à medida que a implementação evolui:
/**
* @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);
}
}
Para interfaces Binder
que não fazem parte da plataforma Android (por exemplo,
uma interface de serviço exportada pelo Google Play Services para uso em apps), o
requisito de uma interface IPC estável, publicada e com versão significa que é
muito mais difícil evoluir a própria interface. No entanto, ainda vale a pena
ter uma camada de wrapper ao redor dela para corresponder a outras diretrizes de API e facilitar
o uso da mesma API pública para uma nova versão da interface IPC, se
isso for necessário.
Não use objetos Binder brutos na API pública
Um objeto Binder
não tem nenhum significado por si só e, portanto, não pode ser
usado na API pública. Um caso de uso comum é usar um Binder
ou IBinder
como um
token porque ele tem semântica de identidade. Em vez de usar um objeto Binder
bruto, use uma classe 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() {...}
}
As classes de adm precisam ser finais
As classes de gerenciador precisam ser declaradas como final
. As classes de gerente se comunicam com os serviços
do sistema e são o único ponto de interação. Não é necessário fazer
personalização, então declare como final
.
Não use CompletableFuture ou Future
java.util.concurrent.CompletableFuture
tem uma grande plataforma de API que permite
a mutação arbitrária do valor do futuro e tem padrões padrão propensos a erros
.
Por outro lado, o java.util.concurrent.Future
não tem a escuta não bloqueante,
o que dificulta o uso com código assíncrono.
No código da plataforma e nas APIs de biblioteca de baixo nível consumidas pelo Kotlin e
Java, prefira uma combinação de um callback de conclusão, Executor
, e se a
API oferece suporte ao cancelamento CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
Se você usar o Kotlin, prefira as funções suspend
.
suspend fun asyncLoadFoo(): Foo
Em bibliotecas de integração específicas do Java, é possível usar o
ListenableFuture
do Guava.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
Não usar "Opcional"
Embora Optional
possa ter vantagens em algumas plataformas de API, ele é inconsistente
com a área de plataforma de API do Android. @Nullable
e @NonNull
fornecem
assistência de ferramentas para a segurança do null
, e o Kotlin aplica contratos de nulidade
no nível do compilador, tornando o Optional
desnecessário.
Para primitivos opcionais, use métodos has
e get
pareados. Se o valor não estiver
definido (has
retorna false
), o método get
vai gerar uma
IllegalStateException
.
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
Usar construtores privados para classes não instantiáveis
Classes que só podem ser criadas por Builder
s, classes que contêm apenas
constantes ou métodos estáticos ou classes não instantiáveis precisam
incluir pelo menos um construtor privado para evitar a instanciação usando o
construtor sem argumentos padrão.
public final class Log {
// Not instantiable.
private Log() {}
}
Singletons
O uso de singleton é desencorajado porque eles têm as seguintes desvantagens relacionadas a testes:
- A construção é gerenciada pela classe, evitando o uso de falsificações
- Os testes não podem ser herméticos devido à natureza estática de um singleton
- Para contornar esses problemas, os desenvolvedores precisam conhecer os detalhes internos do singleton ou criar um wrapper.
Prefira o padrão de instância única, que depende de uma classe base abstrata para resolver esses problemas.
Instância única
As classes de instância única usam uma classe base abstrata com um construtor private
ou
internal
e fornecem um método getInstance()
estático para receber uma
instância. O método getInstance()
precisa retornar o mesmo objeto em
chamadas subsequentes.
O objeto retornado por getInstance()
precisa ser uma implementação particular da
classe base abstrata.
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
}
}
}
A instância única difere do singleton porque os desenvolvedores
podem criar uma versão falsa de SingleInstance
e usar o próprio framework de injeção
de dependências para gerenciar a implementação sem precisar criar um
wrapper. Ou a biblioteca pode fornecer o próprio falso em um artefato -testing
.
As classes que liberam recursos precisam implementar a classe AutoCloseable
As classes que liberam recursos por métodos close
, release
, destroy
ou semelhantes
precisam implementar java.lang.AutoCloseable
para permitir que os desenvolvedores
limpem esses recursos automaticamente ao usar um bloco try-with-resources
.
Evite introduzir novas subclasses de visualização no Android.*
Não apresente novas classes que herdam diretamente ou indiretamente de
android.view.View
na API pública da plataforma (ou seja, em android.*
).
O kit de ferramentas de interface do Android agora é focado no Compose. Os novos recursos de interface expostos pela plataforma precisam ser expostos como APIs de nível inferior que podem ser usadas para implementar o Jetpack Compose e, opcionalmente, componentes de interface baseados em visualizações para desenvolvedores nas bibliotecas do Jetpack. Oferecer esses componentes em bibliotecas oferece oportunidades para implementações de backport quando os recursos da plataforma não estão disponíveis.
Campos
Essas regras são sobre campos públicos em classes.
Não exponha campos brutos
As classes Java não podem expor campos diretamente. Os campos precisam ser privados e acessíveis apenas usando getters e setters públicos, independentemente de serem finais ou não.
As exceções raras incluem estruturas de dados básicas em que não é necessário melhorar
o comportamento de especificar ou recuperar um campo. Nesses casos, os campos precisam
ser nomeados usando convenções de nomenclatura de variáveis padrão, por exemplo, Point.x
e
Point.y
.
As classes Kotlin podem expor propriedades.
Os campos expostos precisam ser marcados como finais
Não é recomendável usar campos brutos (@link
Não exponha campos brutos). No entanto, em raras situações em que um campo é exposto como público, marque-o como final
.
Os campos internos não podem ser expostos
Não faça referência a nomes de campos internos na API pública.
public int mFlags;
Use "Público" em vez de "Protegido"
@consulte Usar "Público" em vez de "Protegido"
Constantes
Estas são regras sobre constantes públicas.
As constantes de flag não podem se sobrepor a valores int ou long.
Flags implica bits que podem ser combinados em algum valor de união. Caso contrário, não chame a variável ou 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;
Consulte @IntDef
para flags de máscara de bits para mais
informações sobre como definir constantes de flag públicas.
As constantes finais estáticas precisam usar a convenção de nomenclatura com todas as letras maiúsculas e sublinhados.
Todas as palavras na constante precisam estar em letras maiúsculas, e várias palavras precisam ser
separadas por _
. Exemplo:
public static final int fooThing = 5
public static final int FOO_THING = 5
Usar prefixos padrão para constantes
Muitas das constantes usadas no Android são para coisas padrão, como flags, chaves e ações. Essas constantes precisam ter prefixos padrão para que sejam mais identificáveis como essas coisas.
Por exemplo, os extras de intent precisam começar com EXTRA_
. As ações de intent precisam
começar com ACTION_
. As constantes usadas com Context.bindService()
precisam começar
com BIND_
.
Nomes e escopos de constantes de chave
Os valores de constantes de string precisam ser consistentes com o nome da constante e, geralmente, precisam estar no escopo do pacote ou domínio. Exemplo:
public static final String FOO_THING = "foo"
não tem um nome consistente nem um escopo adequado. Em vez disso, considere:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Os prefixos de android
em constantes de string com escopo são reservados para o Android
Open Source Project.
As ações e os extras de intent, assim como as entradas de pacote, precisam ter um namespace usando o nome do pacote em que são definidos.
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"
}
Use "Público" em vez de "Protegido"
@consulte Usar "Público" em vez de "Protegido"
Usar prefixos consistentes
As constantes relacionadas precisam começar com o mesmo prefixo. Por exemplo, para um conjunto de constantes a serem usadas com valores de sinalização:
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 Usar prefixos padrão para constantes
Usar nomes de recursos consistentes
Os identificadores, atributos e valores públicos precisam ser nomeados usando a convenção de nomenclatura
camelCase, por exemplo, @id/accessibilityActionPageUp
ou
@attr/textAppearance
, semelhante a campos públicos em Java.
Em alguns casos, um identificador ou atributo público inclui um prefixo comum separado por um sublinhado:
- Valores de configuração da plataforma, como
@string/config_recentsComponentName
em config.xml - Atributos de visualização específicos do layout, como
@attr/layout_marginStart
em attrs.xml.
Os temas e estilos públicos precisam seguir a convenção de nomenclatura
hierárquica PascalCase, por exemplo, @style/Theme.Material.Light.DarkActionBar
ou
@style/Widget.Material.SearchView.ActionBar
, semelhante a classes aninhadas em
Java.
Os recursos de layout e drawable não podem ser expostos como APIs públicas. No entanto, se eles
precisarem ser expostos, os layouts e drawables públicos precisam ser nomeados
usando a convenção de nomenclatura under_score, por exemplo,
layout/simple_list_item_1.xml
ou drawable/title_bar_tall.xml
.
Quando as constantes podem mudar, torne-as dinâmicas
O compilador pode incorporar valores constantes. Portanto, manter os valores iguais é
considerado parte do contrato da API. Se o valor de uma constante MIN_FOO
ou MAX_FOO
poder mudar no futuro, considere torná-los métodos dinâmicos.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Considere a compatibilidade com versões futuras para callbacks
As constantes definidas em versões futuras da API não são conhecidas por apps destinados a APIs mais antigas. Por esse motivo, as constantes enviadas aos apps precisam considerar a versão da API de destino do app e mapear as constantes mais recentes para um valor consistente. Considere o seguinte cenário:
Fonte hipotética do 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ético com targetSdkVersion="22"
:
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
Nesse caso, o app foi projetado dentro das restrições do nível 22 da API e
fez uma suposição (um tanto) razoável de que havia apenas dois estados
possíveis. No entanto, se o app receber o STATUS_FAILURE_RETRY
recém-adicionado, ele
vai interpretar isso como sucesso.
Os métodos que retornam constantes podem processar casos como esse com segurança, restringindo a saída para corresponder ao nível da API segmentado pelo 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;
}
Os desenvolvedores não podem prever se uma lista de constantes pode mudar no
futuro. Se você definir uma API com uma constante UNKNOWN
ou UNSPECIFIED
que
parece ser uma pilha, os desenvolvedores vão presumir que as constantes publicadas quando
eles criaram o app são exaustivas. Se você não quiser definir essa expectativa,
reconsidere se uma constante catch-all é uma boa ideia para sua API.
Além disso, as bibliotecas não podem especificar o próprio targetSdkVersion
separado do
app, e o processamento de mudanças de comportamento do targetSdkVersion
do código da biblioteca é
complicado e propenso a erros.
Constante de número inteiro ou string
Use constantes de números inteiros e @IntDef
se o namespace de valores não puder ser
estendido fora do pacote. Use constantes de string se o namespace for
compartilhado ou puder ser estendido por código fora do pacote.
Classes de dados
As classes de dados representam um conjunto de propriedades imutáveis e fornecem um conjunto pequeno e bem definido de funções utilitárias para interagir com esses dados.
Não use data class
em APIs públicas do Kotlin, porque o compilador do Kotlin não
garante a API da linguagem ou a compatibilidade binária do código gerado. Em vez disso,
implemente manualmente as funções necessárias.
Instanciação
Em Java, as classes de dados precisam fornecer um construtor quando há poucas propriedades
ou usar o padrão Builder
quando há muitas propriedades.
No Kotlin, as classes de dados precisam fornecer um construtor com argumentos padrão, independente do número de propriedades. As classes de dados definidas em Kotlin também podem se beneficiar de um builder ao segmentar clientes Java.
Modificação e cópia
Nos casos em que os dados precisam ser modificados, forneça uma
classe Builder
com um construtor de cópia (Java) ou uma função membro
copy()
(Kotlin) que retorna um novo objeto.
Ao fornecer uma função copy()
no Kotlin, os argumentos precisam corresponder ao construtor
da classe, e os padrões precisam ser preenchidos usando os valores atuais do 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
)
}
Outros comportamentos
As classes de dados precisam implementar
equals()
e hashCode()
, e todas as propriedades precisam ser
consideradas nas implementações desses métodos.
As classes de dados podem implementar toString()
com um formato recomendado
que corresponde à implementação da classe de dados do Kotlin,
por exemplo, User(var1=Alex, var2=42)
.
Métodos
Essas são regras sobre várias especificações em métodos, em relação a parâmetros, nomes de método, tipos de retorno e especificadores de acesso.
Hora
Essas regras abrangem como conceitos de tempo, como datas e duração, devem ser expressos em APIs.
Prefira os tipos java.time.* sempre que possível.
java.time.Duration
, java.time.Instant
e muitos outros tipos de java.time.*
estão
disponíveis em todas as versões da plataforma por meio de
simplificação e
devem ser preferidos ao expressar o tempo em parâmetros de API ou valores de retorno.
Prefira expor apenas variantes de uma API que aceite ou retorne
java.time.Duration
ou java.time.Instant
e omita variantes primitivas com o
mesmo caso de uso, a menos que o domínio da API seja um em que a alocação de objetos em padrões de uso
intencionais tenha um impacto de desempenho proibitivo.
Os métodos que expressam durações devem ser nomeados como "duration"
Se um valor de tempo expressar a duração do tempo envolvido, nomeie o parâmetro como "duration", não "time".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Exceções:
"timeout" é apropriado quando a duração se aplica especificamente a um valor de tempo limite.
"time" com um tipo de java.time.Instant
é apropriado quando se refere a um
ponto específico no tempo, não a uma duração.
Os métodos que expressam durações ou tempo como uma primitiva precisam ser nomeados com a unidade de tempo e usar long
Os métodos que aceitam ou retornam durações como uma primitiva precisam ter o sufixo do nome
do método com as unidades de tempo associadas (como Millis
, Nanos
, Seconds
) para
reservar o nome não decorado para uso com java.time.Duration
. Consulte
Tempo.
Os métodos também precisam ser anotados adequadamente com a unidade e a base de tempo:
@CurrentTimeMillisLong
: o valor é um carimbo de data/hora não negativo medido como o número de milissegundos desde 1970-01-01T00:00:00Z.@CurrentTimeSecondsLong
: o valor é um carimbo de data/hora não negativo medido como o número de segundos desde 1970-01-01T00:00:00Z.@DurationMillisLong
: o valor é uma duração não negativa em milissegundos.@ElapsedRealtimeLong
: o valor é um carimbo de data/hora não negativo na base de tempoSystemClock.elapsedRealtime()
.@UptimeMillisLong
: o valor é um carimbo de data/hora não negativo na base de tempoSystemClock.uptimeMillis()
.
Parâmetros de tempo primitivos ou valores de retorno precisam usar long
, não int
.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
Os métodos que expressam unidades de tempo devem preferir abreviações não abreviadas para nomes de unidades
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
Anotar argumentos de tempo longo
A plataforma inclui várias anotações para fornecer uma tipagem mais forte para
unidades de tempo do tipo long
:
@CurrentTimeMillisLong
: o valor é um carimbo de data/hora não negativo medido como o número de milissegundos desde1970-01-01T00:00:00Z
, ou seja, na base de tempoSystem.currentTimeMillis()
.@CurrentTimeSecondsLong
: o valor é um carimbo de data/hora não negativo medido como o número de segundos desde1970-01-01T00:00:00Z
.@DurationMillisLong
: o valor é uma duração não negativa em milissegundos.@ElapsedRealtimeLong
: o valor é um carimbo de data/hora não negativo na base de tempoSystemClock#elapsedRealtime()
.@UptimeMillisLong
: o valor é um carimbo de data/hora não negativo na base de tempoSystemClock#uptimeMillis()
.
Unidades de medida
Para todos os métodos que expressam uma unidade de medida diferente de tempo, prefira prefixos de unidade do SI em letras maiúsculas.
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
Colocar parâmetros opcionais no final das sobrecargas
Se você tiver sobrecargas de um método com parâmetros opcionais, mantenha esses parâmetros no final e mantenha a ordem consistente com os outros 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);
Ao adicionar sobrecargas para argumentos opcionais, o comportamento dos métodos mais simples precisa ser exatamente o mesmo que se argumentos padrão tivessem sido fornecidos aos métodos mais elaborados.
Corolário: não sobrecarregue métodos, exceto para adicionar argumentos opcionais ou aceitar diferentes tipos de argumentos se o método for polimórfico. Se o método sobrecarregado fizer algo fundamentalmente diferente, dê um novo nome a ele.
Os métodos com parâmetros padrão precisam ser anotados com @JvmOverloads (somente Kotlin)
Métodos e construtores com parâmetros padrão precisam ser anotados com
@JvmOverloads
para manter a compatibilidade binária.
Consulte Sobrecargas de função para padrões no guia oficial de interoperabilidade entre Kotlin e Java para mais detalhes.
class Greeting @JvmOverloads constructor(
loudness: Int = 5
) {
@JvmOverloads
fun sayHello(prefix: String = "Dr.", name: String) = // ...
}
Não remover valores de parâmetro padrão (somente Kotlin)
Se um método foi enviado com um parâmetro com um valor padrão, a remoção do valor padrão é uma mudança de origem.
Os parâmetros de método mais distintos e de identificação devem ser os primeiros
Se você tiver um método com vários parâmetros, coloque os mais relevantes primeiro. Os parâmetros que especificam flags e outras opções são menos importantes do que aqueles que descrevem o objeto que está sendo acessado. Se houver um callback de conclusão, coloque-o por último.
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);
Consulte também: Colocar parâmetros opcionais no final das sobrecargas
Construtores
O padrão Builder é recomendado para criar objetos Java complexos e é comumente usado no Android em casos em que:
- As propriedades do objeto resultante precisam ser imutáveis
- Há um grande número de propriedades obrigatórias, por exemplo, muitos argumentos do construtor
- Há uma relação complexa entre as propriedades no momento da construção. Por exemplo, uma etapa de verificação é necessária. Esse nível de complexidade muitas vezes indica problemas com a usabilidade da API.
Considere se você precisa de um criador. Os builders são úteis em uma plataforma de API se forem usados para:
- Configurar apenas alguns de um conjunto potencialmente grande de parâmetros de criação opcionais
- Configurar muitos parâmetros de criação opcionais ou obrigatórios diferentes, às vezes de tipos semelhantes ou correspondentes, em que os sites de chamada podem se tornar confusos para ler ou propensos a erros para escrever
- Configure a criação de um objeto de forma incremental, em que vários códigos de configuração diferentes podem fazer chamadas no builder como detalhes de implementação.
- Permitir que um tipo cresça adicionando outros parâmetros de criação opcionais em versões futuras da API
Se você tiver um tipo com três ou menos parâmetros obrigatórios e nenhum opcional, quase sempre poderá pular um builder e usar um construtor simples.
As classes de origem Kotlin devem preferir construtores com anotação @JvmOverloads
com
argumentos padrão em vez de builders, mas podem optar por melhorar a usabilidade para clientes
Java também fornecendo builders nos casos descritos anteriormente.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
As classes builder precisam retornar o builder
As classes Builder precisam ativar a vinculação de métodos retornando o objeto Builder
(como this
) de todos os métodos, exceto build()
. Outros objetos criados
precisam ser transmitidos como argumentos. Não retorne o builder de um objeto diferente.
Exemplo:
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();
}
}
Em casos raros em que uma classe de builder base precisa oferecer suporte à extensão, use um tipo de retorno 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);
}
As classes Builder precisam ser criadas por um construtor
Para manter a criação consistente de builders na plataforma da API do Android, todos os
builders precisam ser criados usando um construtor, e não um método
criador estático. Para APIs baseadas em Kotlin, o Builder
precisa ser público, mesmo que os usuários do Kotlin
dependam implicitamente do builder por meio de um mecanismo de criação de estilo
de método de fábrica/DSL. As bibliotecas não podem usar @PublishedApi internal
para
ocultar seletivamente o construtor da classe Builder
dos clientes do Kotlin.
public class Tone {
public static Builder builder();
public static class Builder {
}
}
public class Tone {
public static class Builder {
public Builder();
}
}
Todos os argumentos para construtores de builder precisam ser obrigatórios (como @NonNull)
Opcional, por exemplo, @Nullable
, os argumentos precisam ser movidos para métodos de setter.
O construtor do builder precisa gerar uma NullPointerException
(use
Objects.requireNonNull
) se algum argumento obrigatório não for especificado.
As classes do Builder precisam ser classes internas estáticas finais dos tipos de build
Para fins de organização lógica em um pacote, as classes Builder geralmente precisam
ser expostas como classes internas finais dos tipos criados, por exemplo,
Tone.Builder
em vez de ToneBuilder
.
Os builders podem incluir um construtor para criar uma nova instância a partir de uma instância existente
Os builders podem incluir um construtor de cópia para criar uma nova instância de builder a partir de um builder ou objeto criado. Eles não devem fornecer métodos alternativos para criar instâncias de builder a partir de builders ou objetos de build atuais.
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);
}
}
Os setters do builder precisam usar argumentos @Nullable se o builder tiver um construtor de cópia
A redefinição é essencial se uma nova instância de um builder puder ser criada a partir de uma
instância existente. Se nenhum construtor de cópia estiver disponível, o builder poderá
ter argumentos @Nullable
ou @NonNullable
.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
Os setters do Builder podem usar argumentos @Nullable para propriedades opcionais
Muitas vezes, é mais simples usar um valor nullable para entrada de segundo grau, especialmente no Kotlin, que usa argumentos padrão em vez de builders e sobrecargas.
Além disso, os setters @Nullable
vão corresponder a eles com os getters, que precisam
ser @Nullable
para propriedades opcionais.
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 comum em Kotlin:
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
O valor padrão (se o setter não for chamado) e o significado de null
precisam
ser documentados corretamente no setter e no getter.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Os setters do builder podem ser fornecidos para propriedades mutáveis em que os setters estão disponíveis na classe criada
Se a classe tiver propriedades mutáveis e precisar de uma classe Builder
, primeiro pergunte
a si mesmo se ela realmente precisa ter propriedades mutáveis.
Em seguida, se você tiver certeza de que precisa de propriedades mutáveis, decida qual dos seguintes cenários funciona melhor para seu caso de uso:
O objeto criado precisa ser imediatamente utilizável. Portanto, os setters precisam ser fornecidos para todas as propriedades relevantes, mutáveis ou imutáveis.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Talvez seja necessário fazer outras chamadas antes que o objeto criado possa ser útil. Por isso, os setters não devem ser fornecidos para propriedades mutáveis.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
Não misture os dois cenários.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
Os formadores não podem ter getters.
O getter precisa estar no objeto criado, não no builder.
Os setters do builder precisam ter getters correspondentes na classe criada
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();
}
Nomeação de métodos do builder
Os nomes dos métodos do builder precisam usar o estilo setFoo()
, addFoo()
ou clearFoo()
.
As classes Builder precisam declarar um método build()
As classes Builder precisam declarar um método build()
que retorne uma instância do
objeto criado.
Os métodos builder build() precisam retornar objetos @NonNull
O método build()
de um builder precisa retornar uma instância não nula do
objeto construído. Caso o objeto não possa ser criado devido a parâmetros
inválidos, a validação pode ser adiada para o método de build, e um
IllegalStateException
precisa ser gerado.
Não expor bloqueios internos
Os métodos na API pública não podem usar a palavra-chave synchronized
. Essa
palavra-chave faz com que seu objeto ou classe seja usado como bloqueio. Como ela é
exposta a outros, você pode encontrar efeitos colaterais inesperados se outro código
fora da sua classe começar a usá-la para bloqueio.
Em vez disso, execute qualquer bloqueio necessário em um objeto interno e particular.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Os métodos com estilo de accessor precisam seguir as diretrizes de propriedade do Kotlin
Quando visualizados em origens do Kotlin, métodos com estilo de acessador, que usam os prefixos
get
, set
ou is
, também ficam disponíveis como propriedades do Kotlin.
Por exemplo, int getField()
definido em Java está disponível no Kotlin como a
propriedade val field: Int
.
Por esse motivo, e para atender às expectativas dos desenvolvedores em relação ao comportamento do método de acesso, os métodos que usam prefixos de método de acesso precisam se comportar de maneira semelhante aos campos Java. Evite usar prefixos do tipo de acessório quando:
- O método tem efeitos colaterais. Use um nome de método mais descritivo.
- O método envolve trabalho computacionalmente caro. Use
compute
- O método envolve trabalho de bloqueio ou de longa duração para retornar um
valor, como IPC ou outra E/S. Use
fetch
. - O método bloqueia a linha de execução até que possa retornar um valor. Use
await
. - O método retorna uma nova instância de objeto em cada chamada. Use
create
- O método pode não retornar um valor. Use
request
.
A execução de um trabalho computacionalmente caro uma vez e o armazenamento em cache do valor para chamadas subsequentes ainda conta como a execução de um trabalho computacionalmente caro. O jank não é amortizado entre os frames.
Usar o prefixo is para métodos de acesso booleanos
Essa é a convenção de nomenclatura padrão para campos e métodos booleanos em Java. Em geral, os nomes de método e de variável booleanos precisam ser escritos como perguntas que são respondidas pelo valor de retorno.
Os métodos de acessor booleanos Java precisam seguir um esquema de nomenclatura set
/is
e
os campos precisam preferir is
, como em:
// 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;
O uso de set
/is
para métodos de acesso Java ou is
para campos Java permite
que eles sejam usados como propriedades do Kotlin:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
Propriedades e métodos de acesso geralmente precisam usar nomes positivos, por
exemplo, Enabled
em vez de Disabled
. O uso de terminologia negativa inverte o
significado de true
e false
e dificulta a compreensão do
comportamento.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
Nos casos em que o booleano descreve a inclusão ou a propriedade de uma propriedade, use has em vez de is. No entanto, isso não funciona com a sintaxe de propriedade do 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();
Alguns prefixos alternativos que podem ser mais adequados incluem can e should:
// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();
// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();
Os métodos que alternam comportamentos ou recursos podem usar o prefixo is e o sufixo 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()
Da mesma forma, métodos que indicam a dependência de outros comportamentos ou recursos podem usar o prefixo is e o sufixo Supported ou 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()
Em geral, os nomes de métodos precisam ser escritos como perguntas que são respondidas pelo valor de retorno.
Métodos de propriedade do Kotlin
Para uma propriedade de classe var foo: Foo
, o Kotlin vai gerar métodos get
/set
usando uma regra consistente: antecipe get
e torne o primeiro caractere maiúsculo para o
getter e antecipe set
e torne o primeiro caractere maiúsculo para o setter. A
declaração de propriedade vai produzir métodos com os nomes public Foo getFoo()
e
public void setFoo(Foo foo)
, respectivamente.
Se a propriedade for do tipo Boolean
, uma regra adicional será aplicada na geração
de nomes: se o nome da propriedade começar com is
, get
não será adicionado
ao nome do método getter. O nome da propriedade será usado como o getter.
Portanto, prefira nomear propriedades Boolean
com um prefixo is
para
seguir a diretriz de nomenclatura:
var isVisible: Boolean
Se a sua propriedade for uma das exceções mencionadas e começar com um
prefixo adequado, use a anotação @get:JvmName
na propriedade para
especificar manualmente o nome adequado:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
Acessórios de máscara de bits
Consulte Usar @IntDef
para flags de máscara de bits para ver diretrizes da API
sobre como definir flags de máscara de bits.
Setters
Dois métodos de setter precisam ser fornecidos: um que recebe um bitstring completo e substitui todas as flags existentes e outro que recebe uma máscara de bits personalizada para permitir mais flexibilidade.
/**
* 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
Um getter precisa ser fornecido para conseguir a 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();
Use "Público" em vez de "Protegido"
Sempre dê preferência a public
em vez de protected
na API pública. O acesso protegido acaba
sendo doloroso a longo prazo, porque os implementadores precisam substituir para fornecer
acessores públicos em casos em que o acesso externo por padrão seria tão
bom.
A visibilidade protected
não impede que os desenvolvedores chamem
uma API, mas apenas a torna um pouco mais irritante.
Não implementar ou implementar ambos equals() e hashCode()
Se você substituir um, terá que substituir o outro.
Implementar toString() para classes de dados
As classes de dados são incentivadas a substituir toString()
para ajudar os desenvolvedores a depurar
o código.
Documentar se a saída é para comportamento do programa ou depuração
Decida se o comportamento do programa vai depender da sua implementação ou não. Por exemplo, UUID.toString() e File.toString() documentam o formato específico para uso de programas. Se você estiver expondo informações apenas para depuração, como Intent, implique a herança de documentos da superclasse.
Não inclua informações extras
Todas as informações disponíveis em toString()
também precisam estar disponíveis na
API pública do objeto. Caso contrário, você está incentivando os desenvolvedores a analisar
e confiar na saída toString()
, o que vai impedir mudanças futuras. Uma boa
prática é implementar toString()
usando apenas a API pública do objeto.
Desestimular a dependência da saída de depuração
Embora seja impossível impedir que os desenvolvedores dependam da saída de depuração,
incluir o System.identityHashCode
do objeto na saída toString()
vai tornar muito improvável que dois objetos diferentes tenham uma saída
toString()
igual.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
Isso pode desencorajar os desenvolvedores a escrever declarações de teste como
assertThat(a.toString()).isEqualTo(b.toString())
nos objetos.
Use createFoo ao retornar objetos recém-criados
Use o prefixo create
, não get
ou new
, para métodos que vão criar valores
de retorno, por exemplo, construindo novos objetos.
Quando o método vai criar um objeto para retornar, deixe isso claro no nome do método.
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
Os métodos que aceitam objetos File também precisam aceitar streams
Os locais de armazenamento de dados no Android nem sempre são arquivos no disco. Por exemplo,
o conteúdo transmitido entre os limites do usuário é representado como Uri
s content://
. Para
ativar o processamento de várias fontes de dados, as APIs que aceitam objetos File
também precisam aceitar InputStream
, OutputStream
ou ambos.
public void setDataSource(File file)
public void setDataSource(InputStream stream)
Receber e retornar primitivos brutos em vez de versões em caixa
Se você precisar comunicar valores ausentes ou nulos, use -1
,
Integer.MAX_VALUE
ou Integer.MIN_VALUE
.
public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)
Evitar equivalentes de classe de tipos primitivos evita a sobrecarga de memória dessas classes, o acesso de método a valores e, mais importante, o autoencaixe que vem do casting entre tipos primitivos e de objetos. Evitar esses comportamentos economizar na memória e em alocações temporárias que podem levar a coletas de lixo caras e mais frequentes.
Usar anotações para esclarecer parâmetros válidos e valores de retorno
As anotações para desenvolvedores foram adicionadas para ajudar a esclarecer os valores permitidos em várias
situações. Isso facilita a ajuda de ferramentas aos desenvolvedores quando eles fornecem
valores incorretos (por exemplo, transmitindo um int
arbitrário quando o framework
requer um de um conjunto específico de valores constantes). Use todas as
seguintes anotações quando apropriado:
Nulidade
As anotações de nulidade explícitas são necessárias para APIs Java, mas o conceito de nulidade faz parte da linguagem Kotlin, e as anotações de nulidade nunca devem ser usadas em APIs Kotlin.
@Nullable
:indica que um determinado valor de retorno, parâmetro ou campo pode ser
nulo:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
:indica que um determinado valor de retorno, parâmetro ou campo não pode
ser nulo. Marcar elementos como @Nullable
é relativamente novo no Android, então a maioria
dos métodos de API do Android não são documentados de forma consistente. Portanto, temos um
tri-estado de "unknown, @Nullable
, @NonNull
". Por isso, @NonNull
faz parte
das diretrizes da API:
@NonNull
public String getName()
public void setName(@NonNull String name)
Para documentos da plataforma Android, a anotação dos parâmetros do método vai gerar automaticamente a documentação no formato "Este valor pode ser nulo", a menos que "null" seja usado explicitamente em outro lugar no documento do parâmetro.
Métodos "não anuláveis" atuais:métodos atuais na API sem
uma anotação @Nullable
declarada podem ser anotados como @Nullable
se o método puder
retornar null
em circunstâncias específicas e óbvias (como findViewById()
).
Métodos @NotNull requireFoo()
complementares que geram IllegalArgumentException
precisam ser adicionados para desenvolvedores que não querem verificar nulos.
Métodos de interface:as novas APIs precisam adicionar a anotação adequada ao
implementar métodos de interface, como Parcelable.writeToParcel()
. Ou seja, esse
método na classe de implementação precisa ser writeToParcel(@NonNull Parcel,
int)
, não writeToParcel(Parcel, int)
. No entanto, as APIs que não têm as
anotações não precisam ser "corrigidas".
Aplicação de nulidade
Em Java, os métodos são recomendados para realizar a validação de entrada de parâmetros @NonNull
usando
Objects.requireNonNull()
e gerar uma NullPointerException
quando os parâmetros são nulos. Isso é
realizado automaticamente no Kotlin.
Recursos
Identificadores de recursos:os parâmetros inteiros que denotam IDs para recursos específicos precisam ser anotados com a definição adequada do tipo de recurso.
Há uma anotação para cada tipo de recurso, como @StringRes
,
@ColorRes
e @AnimRes
, além do @AnyRes
de uso geral. Por
exemplo:
public void setTitle(@StringRes int resId)
@IntDef para conjuntos de constantes
Constantes mágicas:os parâmetros String
e int
que devem receber um
de um conjunto finito de valores possíveis indicados por constantes públicas precisam ser
anexados adequadamente com @StringDef
ou @IntDef
. Essas anotações permitem
criar uma nova anotação que funciona como um typedef para
parâmetros permitidos. Exemplo:
/** @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);
Os métodos são recomendados para verificar a validade dos parâmetros anotados
e lançar um IllegalArgumentException
se o parâmetro não fizer parte do
@IntDef
.
@IntDef para flags de máscara de bits
A anotação também pode especificar que as constantes são flags e podem ser combinadas com & e 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 string
Há também a anotação @StringDef
, que é exatamente como @IntDef
na
seção anterior, mas para constantes String
. É possível incluir vários valores de "prefixo", que são usados para emitir automaticamente a documentação de todos os valores.
@SdkConstant para constantes do SDK
@SdkConstant Anotação de campos públicos quando eles são um destes valores 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";
Fornecer nulidade compatível para substituições
Para compatibilidade com a API, a nulidade das substituições precisa ser compatível com a nulidade atual do elemento pai. A tabela a seguir representa as expectativas de compatibilidade. As substituições precisam ser tão restritivas ou mais restritivas do que o elemento que elas substituem.
Tipo | Pai/mãe | filho(a); |
---|---|---|
Tipo de retorno | Sem anotações | Não anotado ou não nulo |
Tipo de retorno | Nullable | Nullable ou não nulo |
Tipo de retorno | Nonnull | Nonnull |
Argumento divertido | Sem anotações | Não anotado ou anulável |
Argumento divertido | Nullable | Nullable |
Argumento divertido | Nonnull | Nullable ou não nulo |
Sempre que possível, prefira argumentos não nulos (como @NonNull).
Quando os métodos são sobrecarregados, é preferível que todos os argumentos sejam não nulos.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
Essa regra também se aplica a setters de propriedade sobrecarregados. O argumento principal precisa ser não nulo, e a limpeza da propriedade precisa ser implementada como um método separado. Isso evita chamadas "nonsense" em que o desenvolvedor precisa definir parâmetros de final de linha, mesmo que eles não sejam necessários.
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()
Prefira tipos de retorno não anuláveis (como @NonNull) para contêineres.
Para tipos de contêiner, como Bundle
ou Collection
, retorne um contêiner vazio e
imutável, quando aplicável. Nos casos em que null
seria usado para
distinguir a disponibilidade de um contêiner, considere fornecer um método
booleano separado.
@NonNull
public Bundle getExtras() { ... }
As anotações de nulidade para pares de get e set precisam ser iguais
Os pares de métodos de acesso e definição para uma única propriedade lógica sempre precisam concordar nas anotações de nulidade. Não seguir essa diretriz vai anular a sintaxe de propriedades do Kotlin. Portanto, adicionar anotações de nulidade conflitantes a métodos de propriedade existentes é uma mudança de origem para usuários do Kotlin.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Valor de retorno em condições de falha ou erro
Todas as APIs precisam permitir que os apps reajam a erros. O retorno de false
, -1
, null
ou outros valores "pega-tudo" de "algo deu errado" não informa ao desenvolvedor
o suficiente sobre a falha em definir as expectativas do usuário ou rastrear com precisão
a confiabilidade do app no campo. Ao projetar uma API, imagine que
você está criando um app. Se você encontrar um erro, a API vai fornecer informações
suficientes para apresentá-lo ao usuário ou reagir de maneira adequada?
- É permitido (e incentivado) incluir informações detalhadas em uma mensagem de exceção, mas os desenvolvedores não precisam analisá-la para processar o erro adequadamente. Códigos de erro detalhados ou outras informações precisam ser expostos como métodos.
- Verifique se a opção de tratamento de erros escolhida oferece flexibilidade para
introduzir novos tipos de erros no futuro. Para
@IntDef
, isso significa incluir um valorOTHER
ouUNKNOWN
. Ao retornar um novo código, você pode verificar otargetSdkVersion
do autor da chamada para evitar o retorno de um código de erro que o app não conhece. Para exceções, tenha uma superclasse comum que suas exceções implementem, para que qualquer código que processe esse tipo também capture e processe os subtipos. - Deve ser difícil ou impossível para um desenvolvedor ignorar acidentalmente
um erro. Se o erro for comunicado retornando um valor, anexe uma anotação
ao método com
@CheckResult
.
Prefira gerar uma ? extends RuntimeException
quando uma condição de falha ou erro
for alcançada devido a algo que o desenvolvedor fez errado, por exemplo, ignorando
restrições em parâmetros de entrada ou deixando de verificar o estado observável.
Os métodos de ação ou setter (por exemplo, perform
) podem retornar um código de status
inteiro se a ação falhar devido a um estado atualizado de forma assíncrona ou
condições fora do controle do desenvolvedor.
Os códigos de status precisam ser definidos na classe que contém como campos public static final
,
com prefixo ERROR_
e enumerados em uma anotação @IntDef
@hide
.
Os nomes dos métodos sempre devem começar com o verbo, não com o sujeito
O nome do método sempre deve começar com o verbo (como get
,
create
, reload
etc.), não com o objeto em que você está agindo.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Prefira tipos de coleção em vez de matrizes como tipo de retorno ou parâmetro
As interfaces de coleção com tipo genérico oferecem várias vantagens em relação a matrizes, incluindo contratos de API mais fortes em relação à exclusividade e ordenação, suporte a genéricos e vários métodos de conveniência para desenvolvedores.
Exceção para primitivos
Se os elementos forem primitivos, use matrizes para evitar o custo do autoboxing. Consulte Receber e retornar primitivos brutos em vez de versões encapsuladas.
Exceção para código sensível ao desempenho
Em alguns casos, quando a API é usada em código sensível à performance (como gráficos ou outras APIs de medição/layout/desenho), é aceitável usar matrizes em vez de coleções para reduzir alocações e a rotatividade da memória.
Exceção para Kotlin
As matrizes do Kotlin são invariáveis, e a linguagem Kotlin oferece APIs de utilitário amplas
em matrizes. Assim, as matrizes são equivalentes a List
e Collection
para APIs do Kotlin
que devem ser acessadas pelo Kotlin.
Preferir coleções @NonNull
Sempre prefira @NonNull
para objetos de coleção. Ao retornar uma coleção
vazia, use o método Collections.empty
apropriado para retornar um objeto de coleção de baixo custo,
com tipo correto e imutável.
Sempre prefira @NonNull
para elementos de
coleção quando houver suporte a anotações de tipo.
Também é recomendável usar @NonNull
ao usar matrizes em vez de coleções (consulte o
item anterior). Se a alocação de objetos for
um problema, crie uma constante e transmita-a. Afinal, uma matriz vazia é
imutável. Exemplo:
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;
}
Mutabilidade da coleção
As APIs do Kotlin precisam preferir tipos de retorno somente leitura (não Mutable
) para coleções
por padrão, a menos que o contrato da API exija especificamente um tipo de retorno
mutável.
No entanto, as APIs Java devem preferir tipos de retorno mutáveis por padrão, porque a
implementação de APIs Java na plataforma Android ainda não oferece uma implementação
conveniente de coleções imutáveis. A exceção a essa regra são
os tipos de retorno Collections.empty
, que são imutáveis. Nos casos em que a mutabilidade
pode ser explorada por clientes, intencionalmente ou por engano, para quebrar o padrão de uso
previsto da API, as APIs Java devem considerar retornar uma cópia
superficial da coleção.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
Tipos de retorno explicitamente mutáveis
As APIs que retornam coleções não devem modificar o objeto de coleção retornado após o retorno. Se a coleção retornada precisar mudar ou ser reutilizada de alguma forma, por exemplo, uma visualização adaptada de um conjunto de dados mutável, o comportamento preciso de quando o conteúdo pode mudar precisa ser documentado explicitamente ou seguir as convenções de nomenclatura da API estabelecidas.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
A convenção .asFoo()
do Kotlin é descrita
abaixo e permite que a coleção retornada por
.asList()
mude se a coleção original mudar.
Mutabilidade dos objetos de tipo de dados retornados
Assim como as APIs que retornam coleções, as APIs que retornam objetos do tipo de dados não devem modificar as propriedades do objeto retornado após o retorno.
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)
}
Em casos extremamente limitados, alguns códigos sensíveis ao desempenho podem se beneficiar do pool de objetos ou da reutilização. Não escreva sua própria estrutura de dados do pool de objetos e não exponha objetos reutilizados em APIs públicas. Em ambos os casos, tenha muito cuidado ao gerenciar o acesso simultâneo.
Uso do tipo de parâmetro vararg
As APIs Kotlin e Java são incentivadas a usar vararg
nos casos em que o
desenvolvedor provavelmente criaria uma matriz no local da chamada com o único
propósito de transmitir vários parâmetros relacionados do mesmo 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);
Cópias defensivas
As implementações de parâmetros vararg
do Java e do Kotlin são compiladas para o mesmo
bytecode com suporte a matriz e, como resultado, podem ser chamadas de código Java com uma
matriz mutável. Os designers de API são fortemente incentivados a criar uma cópia superficial
defensiva do parâmetro de matriz nos casos em que ele será mantido em um
campo ou classe interna anônima.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
Criar uma cópia defensiva não oferece proteção contra modificações simultâneas entre a chamada de método inicial e a criação da cópia, nem contra a mutação dos objetos contidos no vetor.
Fornecer semântica correta com parâmetros de tipo de coleção ou tipos retornados
List<Foo>
é a opção padrão, mas considere outros tipos para fornecer mais
significado:
Use
Set<Foo>
se a ordem dos elementos não for importante para sua API e ela não permitir ou se duplicações não tiverem sentido.Collection<Foo>,
se a API for indiferente à ordem e permitir duplicados.
Funções de conversão do Kotlin
O Kotlin usa com frequência .toFoo()
e .asFoo()
para extrair um objeto de um
tipo diferente de um objeto existente, em que Foo
é o nome do
tipo de retorno da conversão. Isso é consistente com o JDK
Object.toString()
. O Kotlin leva isso adiante usando-o para conversões
primitivas, como 25.toFloat()
.
A distinção entre as conversões nomeadas .toFoo()
e .asFoo()
é
significativa:
Use .toFoo() ao criar um objeto novo e independente
Assim como .toString()
, uma conversão "para" retorna um objeto novo e independente. Se o
objeto original for modificado mais tarde, o novo objeto não vai refletir essas mudanças.
Da mesma forma, se o objeto novo for modificado mais tarde, o objeto antigo não vai refletir
essas mudanças.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
Use .asFoo() ao criar um wrapper dependente, objeto decorado ou cast
A atribuição em Kotlin é realizada usando a palavra-chave as
. Ela reflete uma mudança na
interface, mas não uma mudança na identidade. Quando usado como um prefixo em uma
função de extensão, .asFoo()
decora o receptor. Uma mutação no
objeto receptor original será refletida no objeto retornado por asFoo()
.
Uma mutação no novo objeto Foo
pode ser refletida no objeto original.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
As funções de conversão precisam ser escritas como funções de extensão
Escrever funções de conversão fora das definições da classe receptora e da classe de resultado reduz o acoplamento entre os tipos. Uma conversão ideal precisa apenas de acesso à API pública ao objeto original. Isso prova por exemplo que um desenvolvedor também pode gravar conversões análogas aos próprios tipos preferidos.
Gerar exceções específicas adequadas
Os métodos não podem gerar exceções genéricas, como java.lang.Exception
ou
java.lang.Throwable
. Em vez disso, uma exceção específica adequada precisa ser usada,
como java.lang.NullPointerException
, para permitir que os desenvolvedores processem exceções
sem serem muito amplos.
Erros não relacionados aos argumentos fornecidos diretamente ao método invocado
publicamente devem gerar java.lang.IllegalStateException
em vez de
java.lang.IllegalArgumentException
ou java.lang.NullPointerException
.
Listeners e callbacks
Estas são as regras sobre as classes e os métodos usados para mecanismos de listener e callback.
Os nomes de classes de callback precisam ser singulares
Use MyObjectCallback
em vez de MyObjectCallbacks
Os nomes dos métodos de callback precisam estar no formato on
onFooEvent
significa que FooEvent
está acontecendo e que o callback precisa
agir em resposta.
O pretérito e o presente devem descrever o comportamento de tempo.
Os métodos de callback relacionados a eventos precisam ser nomeados para indicar se o evento já aconteceu ou está em andamento.
Por exemplo, se o método for chamado depois que uma ação de clique for realizada:
public void onClicked()
No entanto, se o método for responsável por executar a ação de clique:
public boolean onClick()
Registro de callback
Quando um listener ou callback pode ser adicionado ou removido de um objeto, os métodos associados precisam ser nomeados como add e remove ou register e unregister. Seja consistente com a convenção usada pela classe ou por outras classes no mesmo pacote. Quando não houver esse precedente, prefira adicionar e remover.
Os métodos que envolvem o registro ou cancelamento do registro de callbacks precisam especificar o nome completo do tipo de callback.
public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);
Evite getters para callbacks
Não adicione métodos getFooCallback()
. Essa é uma saída de emergência tentadora para
casos em que os desenvolvedores podem querer encadear um callback existente com
a própria substituição, mas ela é frágil e dificulta a análise do estado atual
para desenvolvedores de componentes. Por exemplo:
- O Desenvolvedor A chama
setFooCallback(a)
- O desenvolvedor B chama
setFooCallback(new B(getFooCallback()))
- O desenvolvedor A quer remover o callback
a
e não tem como fazer isso sem conhecer o tipo deB
, eB
foi criado para permitir essas modificações do callback encapsulado.
Aceitar o executor para controlar o envio de callbacks
Ao registrar callbacks que não têm expectativas de linha de execução explícitas (quase
em qualquer lugar fora do kit de ferramentas da interface), é altamente recomendável incluir um
parâmetro Executor
como parte do registro para permitir que o desenvolvedor especifique
a linha de execução em que os callbacks serão invocados.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Como exceção às nossas
orientações usuais sobre parâmetros opcionais, é aceitável
fornecer uma sobrecarga omitindo o Executor
, mesmo que ele não seja o argumento
final na lista de parâmetros. Se o Executor
não for fornecido, o callback
precisa ser invocado na linha de execução principal usando Looper.getMainLooper()
e
precisa ser documentado no método sobrecarregado associado.
/**
* ...
* 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)
Problemas de implementação de Executor
:confira o exemplo a seguir de um
executor válido.
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
Isso significa que, ao implementar APIs que usam esse formulário, a implementação do objeto de vinculação
de entrada no lado do processo do app precisa chamar
Binder.clearCallingIdentity()
antes de invocar o callback do app no
Executor
fornecido pelo app. Dessa forma, qualquer código de app que use a identidade do binder (como
Binder.getCallingUid()
) para verificações de permissão atribui corretamente o código
em execução ao app e não ao processo do sistema que chama o app. Se os usuários
da API quiserem as informações de UID ou PID do autor da chamada, isso precisará ser uma
parte explícita da superfície da API, em vez de implícita, com base em onde o
Executor
fornecido foi executado.
A API precisa oferecer suporte à especificação de um Executor
. Em
casos de desempenho crítico, os apps podem precisar executar o código imediatamente ou
simultaneamente com o feedback da API. Aceitar um Executor
permite isso.
Criar defensivamente um HandlerThread
adicional ou semelhante a um trampoline de
derrota esse caso de uso desejável.
Se um app for executar um código caro em algum lugar do próprio processo, avise o usuário. As soluções alternativas que os desenvolvedores de apps vão encontrar para superar suas restrições serão muito mais difíceis de oferecer suporte a longo prazo.
Exceção para callback único:quando a natureza dos eventos que estão sendo informados exige suporte a apenas uma instância de callback, use o seguinte estilo:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
Usar o executor em vez do gerenciador
O Handler
do Android era usado como um padrão para redirecionar a execução de callbacks para
uma linha de execução Looper
específica no passado. Esse padrão foi alterado para preferir
Executor
, já que a maioria dos desenvolvedores de apps gerencia os próprios pools de linhas de execução, tornando a linha de execução principal
ou de interface a única linha de execução Looper
disponível para o app. Use Executor
para
dar aos desenvolvedores o controle necessário para reutilizar os contextos de execução
existentes/preferidos.
Bibliotecas de simultaneidade modernas, como kotlinx.coroutines ou RxJava, fornecem mecanismos de programação
próprios que executam o próprio envio quando necessário. Isso torna
importante oferecer a capacidade de usar um executor direto (como
Runnable::run
) para evitar a latência de saltos de linha dupla. Por exemplo, um salto
para postar em uma linha de execução Looper
usando um Handler
seguido por outro salto do
framework de simultaneidade do app.
As exceções a essa diretriz são raras. As solicitações de exceção comuns incluem:
Preciso usar um Looper
porque preciso de um Looper
para epoll
no evento.
Esse pedido de exceção foi concedido porque os benefícios de Executor
não podem ser
aproveitados nessa situação.
Não quero que o código do app bloqueie minha linha de execução para publicar o evento. Essa solicitação de exceção normalmente não é concedida para códigos executados em um processo de app. Os apps que erram nisso só se prejudicam, sem afetar a integridade geral do sistema. Os apps que fazem isso corretamente ou usam um framework de concorrência comum não pagam penalidades de latência adicionais.
Handler
é consistente localmente com outras APIs semelhantes na mesma classe.
Esse pedido de exceção é concedido de acordo com a situação. A preferência é que
as sobrecargas baseadas em Executor
sejam adicionadas, migrando as implementações de Handler
para
usar a nova implementação de Executor
. (myHandler::post
é um Executor
válido). Dependendo do tamanho da classe, do número de métodos Handler
existentes e da probabilidade de os desenvolvedores precisarem usar métodos baseados em Handler
com o novo método, uma exceção pode ser concedida para adicionar um novo
método baseado em Handler
.
Simetria no registro
Se houver uma maneira de adicionar ou registrar algo, também deve haver uma maneira de remover/cancelar o registro. Método
registerThing(Thing)
precisa ter uma correspondência
unregisterThing(Thing)
Fornecer um identificador de solicitação
Se for razoável que um desenvolvedor reutilize um callback, forneça um objeto identificador para vincular o callback à solicitação.
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
Objetos de callback de vários métodos
Os callbacks de vários métodos devem preferir interface
e usar métodos default
ao adicionar a interfaces lançadas anteriormente. Anteriormente, esta diretriz
recomendava abstract class
devido à falta de métodos default
no Java 7.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
Use android.os.OutcomeReceiver ao modelar uma chamada de função não bloqueante
OutcomeReceiver<R,E>
informa um valor de resultado R
quando bem-sucedido ou E : Throwable
, caso contrário, as
mesmas coisas que uma chamada de método simples pode fazer. Use OutcomeReceiver
como o tipo de callback
ao converter um método de bloqueio que retorna um resultado ou gera uma
exceção para um método assíncrono não de bloqueio:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
Os métodos assíncronos convertidos dessa maneira sempre retornam void
. Qualquer resultado que
requestFoo
retornaria é informado ao OutcomeReceiver.onResult
do parâmetro
callback
de requestFooAsync
chamando-o no executor
fornecido.
Qualquer exceção que o requestFoo
gerar será informada ao
método OutcomeReceiver.onError
da mesma maneira.
O uso de OutcomeReceiver
para informar os resultados de métodos assíncronos também permite um wrapper
suspend fun
do Kotlin para métodos assíncronos usando a
extensão Continuation.asOutcomeReceiver
do androidx.core:core-ktx
:
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
Extensões como essa permitem que os clientes do Kotlin chamem métodos assíncronos não bloqueadores
com a conveniência de uma chamada de função simples sem bloquear a linha de execução
de chamada. Essas extensões 1 para 1 para APIs da plataforma podem ser oferecidas como parte do
artefato androidx.core:core-ktx
no Jetpack quando combinados com verificações e considerações de compatibilidade
de versão padrão. Consulte a documentação de
asOutcomeReceiver
para mais informações, considerações de cancelamento e exemplos.
Métodos assíncronos que não correspondem à semântica de um método que retorna um resultado ou
gera uma exceção quando o trabalho é concluído não devem usar
OutcomeReceiver
como um tipo de callback. Em vez disso, considere uma das outras opções
listadas na seção a seguir.
Prefira interfaces funcionais em vez de criar novos tipos de método abstrato simples (SAM, na sigla em inglês).
O nível 24 da API adicionou os tipos java.util.function.*
(documentos de referência)
que oferecem interfaces genéricas de SAM, como Consumer<T>
, que são
adequadas para uso como lambdas de callback. Em muitos casos, a criação de novas interfaces SAM
não oferece muito valor em termos de segurança de tipo ou comunicação de intent,
expandindo desnecessariamente a área da API do Android.
Considere usar estas interfaces genéricas em vez de criar outras:
Runnable
:() -> Unit
Supplier<R>
:() -> R
Consumer<T>
:(T) -> Unit
Function<T,R>
:(T) -> R
Predicate<T>
:(T) -> Boolean
- muitos outros disponíveis nos documentos de referência
Posicionamento dos parâmetros SAM
Os parâmetros SAM precisam ser colocados por último para permitir o uso idiomático do Kotlin, mesmo se o método estiver sendo sobrecarregado com parâmetros adicionais.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
Documentos
Estas são regras sobre os documentos públicos (Javadoc) para APIs.
Todas as APIs públicas precisam ser documentadas
Todas as APIs públicas precisam ter documentação suficiente para explicar como um desenvolvedor usaria a API. Suponha que o desenvolvedor tenha encontrado o método usando o preenchimento automático ou enquanto navegava pelos documentos de referência da API e tenha uma quantidade mínima de contexto da superfície da API adjacente (por exemplo, a mesma classe).
Métodos
Os parâmetros do método e os valores de retorno precisam ser documentados usando as anotações @param
e
@return
, respectivamente. Formate o corpo do Javadoc como se ele
fosse precedido por "Este método...".
Nos casos em que um método não usa parâmetros, não tem considerações especiais e
retorna o que o nome do método diz, você pode omitir o @return
e
escrever documentos semelhantes a este:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Sempre use links no Javadoc
Os documentos precisam ter links para outros documentos de constantes, métodos e outros
elementos relacionados. Use tags Javadoc (por exemplo, @see
e {@link foo}
), não apenas
palavras de texto simples.
No exemplo de origem a seguir:
public static final int FOO = 0;
public static final int BAR = 1;
Não use texto bruto ou fonte 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) { ... }
Em vez disso, use links:
/**
* Sets value to one of {@link #FOO} or {@link #BAR}.
*
* @param value the value being set
*/
public void setValue(@ValueType int value) { ... }
O uso de uma anotação IntDef
, como @ValueType
, em um parâmetro
gera automaticamente a documentação que especifica os tipos permitidos. Consulte as
orientações sobre anotações para mais informações sobre IntDef
.
Execute o destino "update-api" ou "docs" ao adicionar Javadoc.
Essa regra é particularmente importante ao adicionar tags @link
ou @see
e garantir
que a saída esteja como esperado. A saída ERROR no Javadoc geralmente é causada por links
inválidos. O destino de make update-api
ou docs
executa essa verificação, mas
o destino docs
pode ser mais rápido se você estiver apenas mudando o Javadoc e não
precisando executar o destino update-api
.
Use {@code foo} para distinguir valores Java
Encapsule valores Java como true
, false
e null
com {@code...}
para
distinguir o texto da documentação.
Ao escrever a documentação em origens Kotlin, você pode agrupar o código com chaves como faria para Markdown.
Os resumos de @param e @return precisam ser um único fragmento de frase
Os resumos de parâmetros e valores de retorno precisam começar com um caractere minúsculo e conter apenas um único fragmento de frase. Se você tiver outras informações que se estendem além de uma única frase, mova-as para o corpo do Javadoc do 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.
*/
Precisa ser alterado para:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
As anotações nos documentos precisam de explicações
Documente por que as anotações @hide
e @removed
estão ocultas da API pública.
Inclua instruções sobre como substituir elementos da API marcados com a
anotação @deprecated
.
Usar @throws para documentar exceções
Se um método gerar uma exceção verificada, por exemplo, IOException
, documente a
exceção com @throws
. Para APIs de origem Kotlin destinadas a serem usadas por
clientes Java, anote as funções com
@Throws
.
Se um método gerar uma exceção não verificada indicando um erro evitável, por
exemplo IllegalArgumentException
ou IllegalStateException
, documente a
exceção com uma explicação do motivo dela. A exceção
gerada também precisa indicar por que ela foi gerada.
Alguns casos de exceção não verificada são considerados implícitos e não precisam
ser documentados, como NullPointerException
ou IllegalArgumentException
,
em que um argumento não corresponde a uma @IntDef
ou a uma anotação semelhante que incorpora
o contrato da API à assinatura do 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.");
}
// ...
Ou, em 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")
}
}
// ...
Se o método invocar um código assíncrono que pode gerar exceções, considere
como o desenvolvedor descobre e responde a essas exceções. Normalmente,
isso envolve encaminhar a exceção para um callback e documentar as
exceções geradas no método que as recebe. As exceções assíncronas
não precisam ser documentadas com @throws
, a menos que sejam realmente lançadas novamente do
método anotado.
Termine a primeira frase dos documentos com um ponto
A ferramenta Doclava analisa os documentos de forma simplificada, encerrando o documento de sinopse (a primeira frase, usada na descrição rápida na parte de cima dos documentos da classe) assim que encontra um ponto (.) seguido de um espaço. Isso causa dois problemas:
- Se um documento curto não terminar com um período e se esse membro tiver documentos
herdados que são coletados pela ferramenta, a sinopse também vai coletar esses
documentos. Por exemplo, consulte
actionBarTabStyle
nos documentosR.attr
, que têm a descrição da dimensão adicionada à sinopse. - Evite "por exemplo" na primeira frase pelo mesmo motivo, porque o Doclava termina
os documentos de sinopse após "g". Por exemplo, consulte
TEXT_ALIGNMENT_CENTER
emView.java
. O Metalava corrige automaticamente esse erro inserindo um espaço não quebrável após o período. No entanto, não cometa esse erro.
Formatar documentos para renderização em HTML
O Javadoc é renderizado em HTML. Formate os documentos da seguinte maneira:
As quebras de linha precisam usar uma tag
<p>
explícita. Não adicione uma tag</p>
de fechamento.Não use ASCII para renderizar listas ou tabelas.
As listas devem usar
<ul>
ou<ol>
para não ordenadas e ordenadas, respectivamente. Cada item precisa começar com uma tag<li>
, mas não precisa de uma tag</li>
de fechamento. Uma tag</ul>
ou</ol>
de fechamento é necessária após o último item.As tabelas devem usar
<table>
,<tr>
para linhas,<th>
para cabeçalhos e<td>
para células. Todas as tags de tabela precisam de tags de fechamento correspondentes. É possível usarclass="deprecated"
em qualquer tag para indicar a descontinuação.Para criar uma fonte de código inline, use
{@code foo}
.Para criar blocos de código, use
<pre>
.Todo o texto dentro de um bloco
<pre>
é analisado pelo navegador. Portanto, tenha cuidado com colchetes<>
. É possível usar o escape com entidades HTML<
e>
.Como alternativa, você pode deixar colchetes brutos
<>
no snippet de código se envolver as seções com{@code foo}
. Exemplo:<pre>{@code <manifest>}</pre>
Seguir o guia de estilo de referência da API
Para manter a consistência no estilo de resumos de classes, descrições de métodos e de parâmetros e outros itens, siga as recomendações nas diretrizes oficiais da linguagem Java em Como escrever comentários em documentos na ferramenta Javadoc (link em inglês).
Regras específicas do framework do Android
Essas regras são sobre APIs, padrões e estruturas de dados específicos para
APIs e comportamentos integrados ao framework do Android (por exemplo, Bundle
ou
Parcelable
).
Os criadores de intent precisam usar o padrão create*Intent()
Os criadores de intents precisam usar métodos com o nome createFooIntent()
.
Use o pacote em vez de criar novas estruturas de dados de uso geral
Evite criar novas estruturas de dados de uso geral para representar mapeamentos de valor
tipado para chave arbitrária. Em vez disso, considere usar Bundle
.
Isso geralmente aparece ao escrever APIs de plataforma que servem como canais de comunicação entre apps e serviços que não são da plataforma, em que a plataforma não lê os dados enviados pelo canal, e o contrato da API pode ser parcialmente definido fora da plataforma (por exemplo, em uma biblioteca do Jetpack).
Nos casos em que a plataforma lê os dados, evite usar Bundle
e
prefira uma classe de dados fortemente tipada.
As implementações parceláveis precisam ter um campo CREATOR público
A inflação parcelável é exposta por CREATOR
, não por construtores brutos. Se uma
classe implementar Parcelable
, o campo CREATOR
dela também precisa ser uma API
pública, e o construtor de classe que recebe um argumento Parcel
precisa ser privado.
Usar CharSequence para strings de interface
Quando uma string for apresentada em uma interface do usuário, use CharSequence
para permitir
instâncias de Spannable
.
Se for apenas uma chave ou algum outro rótulo ou valor que não está visível para os usuários,
String
é aceitável.
Evite usar enumerações
O IntDef
precisa ser usado em vez de enumerações em todas as APIs da plataforma e precisa ser considerado
em APIs de biblioteca não agrupadas. Use tipos enumerados apenas quando tiver certeza de que novos valores
não serão adicionados.
Benefícios doIntDef
:
- Permite adicionar valores ao longo do tempo
- As instruções
when
do Kotlin podem falhar no momento da execução se não forem mais exaustivas devido a um valor de tipo enumerado adicionado na plataforma.
- As instruções
- Nenhuma classe ou objeto usado no momento da execução, apenas primitivos
- Embora o R8 ou a minificação possam evitar esse custo para APIs de biblioteca não agrupadas, essa otimização não pode afetar as classes de API da plataforma.
Benefícios do tipo enumerado
- Recurso idiomático da linguagem Java, Kotlin
- Ativa a chave exaustiva, uso da instrução
when
- Observação: os valores não podem mudar com o tempo. Consulte a lista anterior.
- Nomeação com escopo claro e detectável
- Ativa a verificação no tempo de compilação
- Por exemplo, uma instrução
when
em Kotlin que retorna um valor
- Por exemplo, uma instrução
- É uma classe funcional que pode implementar interfaces, ter auxiliares estáticos, exibir métodos de membro ou extensão e expor campos.
Seguir a hierarquia de camadas de pacotes do Android
A hierarquia de pacotes android.*
tem uma ordem implícita, em que pacotes de nível
inferior não podem depender de pacotes de nível superior.
Evite se referir ao Google, a outras empresas e aos produtos delas
A plataforma Android é um projeto de código aberto e tem como objetivo ser neutra em relação ao fornecedor. A API precisa ser genérica e igualmente utilizável por integradores de sistemas ou apps com as permissões necessárias.
As implementações parceláveis precisam ser finais
As classes parceláveis definidas pela plataforma são sempre carregadas do
framework.jar
. Portanto, é inválido que um app tente substituir uma implementação
Parcelable
.
Se o app de envio estender um Parcelable
, o app de recebimento não terá a
implementação personalizada do remetente para descompactar. Observação sobre a compatibilidade
anterior: se a classe não era final, mas não tinha um
construtor disponível publicamente, ainda é possível marcá-la como final
.
Os métodos que chamam o processo do sistema precisam gerar novamente a RemoteException como RuntimeException.
O RemoteException
normalmente é gerado pela AIDL interna e indica que o
processo do sistema foi encerrado ou que o app está tentando enviar muitos dados. Em ambos
os casos, a API pública precisa ser reenviada como RuntimeException
para evitar que os apps
persistam decisões de segurança ou políticas.
Se você souber que o outro lado de uma chamada Binder
é o processo do sistema, esta
prática recomendada é o código boilerplate:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Gerar exceções específicas para mudanças na API
Os comportamentos das APIs públicas podem mudar entre os níveis de API e causar falhas no app, por exemplo, para aplicar novas políticas de segurança.
Quando a API precisar gerar uma exceção para uma solicitação que era válida anteriormente, gere uma nova
exceção específica em vez de uma genérica. Por exemplo, ExportedFlagRequired
em vez de SecurityException
(e ExportedFlagRequired
pode estender
SecurityException
).
Isso vai ajudar os desenvolvedores de apps e as ferramentas a detectar mudanças no comportamento da API.
Implementar o construtor de cópia em vez de clone
O uso do método clone()
do Java é desencorajado devido à falta de contratos
de API fornecidos pela classe Object
e às dificuldades inerentes à extensão
de classes que usam clone()
. Em vez disso, use um construtor de cópia que recebe um objeto
do mesmo tipo.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
As classes que dependem de um Builder para construção devem considerar a adição de um construtor de cópia do Builder para permitir modificações na cópia.
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
Usar ParcelFileDescriptor em vez de FileDescriptor
O objeto java.io.FileDescriptor
tem uma definição ruim de propriedade, o que
pode resultar em bugs obscuros de uso após o fechamento. Em vez disso, as APIs precisam retornar ou
aceitar instâncias ParcelFileDescriptor
. O código herdado pode converter entre PFD e
FD, se necessário, usando
dup()
ou
getFileDescriptor().
Evite usar valores numéricos de tamanho ímpar
Evite usar os valores short
ou byte
diretamente, porque eles geralmente limitam como você
pode evoluir a API no futuro.
Evite usar o BitSet
O java.util.BitSet
é ótimo para implementação, mas não para API pública. Ele é
mutável, requer uma alocação para chamadas de método de alta frequência e não
oferece significado semântico para o que cada bit representa.
Para cenários de alto desempenho, use um int
ou long
com @IntDef
. Para
cenários de baixo desempenho, considere uma Set<EnumType>
. Para dados binários brutos, use
byte[]
.
Preferir android.net.Uri
android.net.Uri
é a encapsulação preferencial para URIs em APIs do Android.
Evite java.net.URI
, porque ele é muito rigoroso na análise de URIs, e nunca use
java.net.URL
, porque a definição de igualdade está muito quebrada.
Ocultar anotações marcadas como @IntDef, @LongDef ou @StringDef
As anotações marcadas como @IntDef
, @LongDef
ou @StringDef
indicam um conjunto de
constantes válidas que podem ser transmitidas a uma API. No entanto, quando elas são exportadas como
APIs, o compilador inline as constantes, e apenas os valores (agora inúteis) permanecem no stub da API da anotação (para a plataforma) ou JAR (para
bibliotecas).
Portanto, o uso dessas anotações precisa ser marcado com a anotação @hide
na plataforma ou a anotação de código @RestrictTo.Scope.LIBRARY)
nas
bibliotecas. Elas precisam ser marcadas como @Retention(RetentionPolicy.SOURCE)
em ambos
os casos para evitar que apareçam em stubs de API ou JARs.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
Ao criar os AARs da biblioteca e do SDK da plataforma, uma ferramenta extrai as anotações e as agrupa separadamente das origens compiladas. O Android Studio lê esse formato agrupado e aplica as definições de tipo.
Não adicionar novas chaves de provedor de configuração
Não exponha novas chaves de
Settings.Global
,
Settings.System
ou
Settings.Secure
.
Em vez disso, adicione uma API Java de getter e setter adequada em uma classe relevante, que geralmente é uma classe "manager". Adicione um mecanismo de listener ou uma transmissão para notificar os clientes sobre as mudanças conforme necessário.
As configurações de SettingsProvider
têm vários problemas em comparação com
getters/setters:
- Sem segurança de tipo.
- Não há uma maneira unificada de fornecer um valor padrão.
- Não há uma maneira adequada de personalizar as permissões.
- Por exemplo, não é possível proteger a configuração com uma permissão personalizada.
- Não há uma maneira adequada de adicionar a lógica personalizada.
- Por exemplo, não é possível mudar o valor da configuração A dependendo do valor da configuração B.
Exemplo:
Settings.Secure.LOCATION_MODE
já existe há muito tempo, mas a equipe de localização o desativou para uma
API Java adequada
LocationManager.isLocationEnabled()
e a transmissão
MODE_CHANGED_ACTION
, o que deu à equipe muito mais flexibilidade, e a semântica das
APIs está muito mais clara agora.
Não estender Activity e AsyncTask
AsyncTask
é um detalhe de implementação. Em vez disso, exponha um listener ou, no
androidx, uma API ListenableFuture
.
Não é possível compor subclasses Activity
. A extensão da atividade para seu
recurso o torna incompatível com outros recursos que exigem que os usuários façam
o mesmo. Em vez disso, use ferramentas como
LifecycleObserver para depender da composição.
Usar a função getUser() do contexto
Classes vinculadas a um Context
, como qualquer coisa retornada de
Context.getSystemService()
, devem usar o usuário vinculado ao Context
em vez
de expor membros que segmentam usuários 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);
}
}
Exceção: um método pode aceitar um argumento do usuário se aceitar valores que não
representem um único usuário, como UserHandle.ALL
.
Use UserHandle em vez de ints simples
O UserHandle
é preferido para fornecer segurança de tipo e evitar a confusão entre IDs de usuário
e UIDs.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Quando inevitável, um int
que representa um ID do usuário precisa ser anotado com
@UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Preferir listeners ou callbacks para intents de transmissão
As intents de transmissão são muito poderosas, mas resultaram em comportamentos emergentes que podem afetar negativamente a integridade do sistema. Portanto, novas intents de transmissão precisam ser adicionadas com cuidado.
Confira algumas preocupações específicas que nos levam a desencorajar a introdução de novas intents de transmissão:
Ao enviar transmissões sem a flag
FLAG_RECEIVER_REGISTERED_ONLY
, eles iniciam à força todos os apps que ainda não estão em execução. Embora isso às vezes seja um resultado esperado, pode resultar na interrupção de dezenas de apps, afetando negativamente a integridade do sistema. Recomendamos usar estratégias alternativas, comoJobScheduler
, para coordenar melhor quando várias pré-condições forem atendidas.Ao enviar transmissões, há pouca capacidade de filtrar ou ajustar o conteúdo enviado aos apps. Isso dificulta ou impossibilita responder a preocupações de privacidade futuras ou introduzir mudanças de comportamento com base no SDK de destino do app receptor.
Como as filas de transmissão são um recurso compartilhado, elas podem ficar sobrecarregadas e não resultar na entrega oportuna do evento. Observamos várias filas de transmissão em uso que têm uma latência de ponta a ponta de 10 minutos ou mais.
Por esses motivos, recomendamos que novos recursos considerem usar listeners ou
callbacks ou outras ferramentas, como JobScheduler
, em vez de intents
de transmissão.
Nos casos em que as intents de transmissão ainda são o design ideal, considere estas práticas recomendadas:
- Se possível, use
Intent.FLAG_RECEIVER_REGISTERED_ONLY
para limitar a transmissão a apps que já estão em execução. Por exemplo,ACTION_SCREEN_ON
usa esse design para evitar ativar apps. - Se possível, use
Intent.setPackage()
ouIntent.setComponent()
para segmentar a transmissão em um app específico de interesse. Por exemplo,ACTION_MEDIA_BUTTON
usa esse design para se concentrar no processamento atual de controles de reprodução do app. - Se possível, defina a transmissão como uma
<protected-broadcast>
para evitar que apps maliciosos imitem o SO.
Intents em serviços de desenvolvedor vinculados ao sistema
Serviços que são destinados a serem estendidos pelo desenvolvedor e vinculados pelo
sistema, por exemplo, serviços abstratos como NotificationListenerService
, podem
responder a uma ação Intent
do sistema. Esses serviços precisam atender aos
seguintes critérios:
- Defina uma constante de string
SERVICE_INTERFACE
na classe que contém o nome totalmente qualificado da classe do serviço. Essa constante precisa ser anotada com@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Documente na classe que um desenvolvedor precisa adicionar um
<intent-filter>
àAndroidManifest.xml
para receber intents da plataforma. - Considere adicionar uma permissão no nível do sistema para impedir que apps maliciosos
enviem
Intent
s aos serviços do desenvolvedor.
Interoperabilidade entre Kotlin e Java
Consulte o Guia de interoperabilidade entre Kotlin e Java (link em inglês) para desenvolvedores Android para conferir uma lista completa de diretrizes. Algumas diretrizes foram copiadas para este guia para melhorar a facilidade de descoberta.
Visibilidade da API
Algumas APIs do Kotlin, como suspend fun
s, não são destinadas a desenvolvedores
Java. No entanto, não tente controlar a visibilidade específica da linguagem
usando @JvmSynthetic
, porque isso tem efeitos colaterais na forma como a API é apresentada em
depurar que dificultam a depuração.
Consulte o Guia de interoperabilidade entre Kotlin e Java ou o Guia de assincronia para orientações específicas.
Objetos complementares
O Kotlin usa companion object
para expor membros estáticos. Em alguns casos, elas
vão aparecer em Java em uma classe interna chamada Companion
, e não na
classe que contém. As classes Companion
podem aparecer como classes vazias em arquivos de texto
de API. Isso está funcionando como esperado.
Para maximizar a compatibilidade com Java, anote os
campos não constantes
dos objetos complementares
com @JvmField
e as
funções públicas
com @JvmStatic
para que eles sejam expostos diretamente na classe que contém.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Evolução das APIs da plataforma Android
Esta seção descreve políticas sobre os tipos de mudanças que você pode fazer nas APIs do Android e como implementar essas mudanças para maximizar a compatibilidade com apps e bases de código atuais.
Alterações interruptivas binárias
Evite mudanças de interrupção binária em plataformas de API públicas finalizadas. Esses tipos de
mudanças geralmente geram erros ao executar make update-api
, mas pode
haver casos extremos que a verificação de API do Metalava não detecta. Em caso de dúvida, consulte o
guia
Evolving Java-based APIs
da Eclipse Foundation para uma explicação detalhada sobre quais tipos de mudanças de API são compatíveis com
Java. As mudanças interruptivas binárias em APIs ocultas (por exemplo, do sistema) precisam seguir
o ciclo de descontinuação/substituição.
Mudanças que quebram a origem
Não recomendamos mudanças que quebrem a origem, mesmo que não sejam binárias. Um
exemplo de mudança binária compatível, mas que quebra a origem, é adicionar um tipo genérico a
uma classe existente, que é
compatível com binários,
mas pode introduzir erros de compilação devido à herança ou referências ambíguas.
As mudanças que quebram a origem não geram erros ao executar make update-api
. Portanto, é necessário entender o impacto das mudanças nas assinaturas de API
existentes.
Em alguns casos, mudanças que quebram a origem se tornam necessárias para melhorar a experiência do desenvolvedor ou a correção do código. Por exemplo, adicionar anotações de nulidade a origens Java melhora a interoperabilidade com o código Kotlin e reduz a probabilidade de erros, mas geralmente requer mudanças, às vezes significativas, no código-fonte.
Mudanças nas APIs privadas
É possível mudar as APIs anotadas com @TestApi
a qualquer momento.
Você precisa preservar as APIs anotadas com @SystemApi
por três anos. É necessário
remover ou refatorar uma API do sistema na seguinte programação:
- API y: adição
- API y+1: Descontinuação
- Marque o código com
@Deprecated
. - Adicione substituições e vincule à substituição no Javadoc para o
código descontinuado usando a anotação de documentos
@deprecated
. - Durante o ciclo de desenvolvimento, registre bugs contra usuários internos informando que a API está sendo descontinuada. Isso ajuda a validar se as APIs de substituição são adequadas.
- Marque o código com
- API y+2: Remoção suave
- Marque o código com
@removed
. - Opcionalmente, lance ou não execute operações para apps destinados ao nível atual do SDK para a versão.
- Marque o código com
- API y+3: Remoção difícil
- Remova completamente o código da árvore de origem.
Previsão de remoção
Consideramos a descontinuação uma mudança na API, e ela pode ocorrer em uma versão principal (como
a versão com a letra). Use a anotação de origem @Deprecated
e a anotação de documentos @deprecated
<summary>
juntas ao descontinuar as APIs. O resumo precisa
incluir uma estratégia de migração. Essa estratégia pode vincular a uma API de substituição ou
explicar por que você não deve usar a 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)
Você precisa descontinuar as APIs definidas em XML e expostas em Java, incluindo
atributos e propriedades estilizáveis expostas na classe android.R
, com um
resumo:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Quando descontinuar uma API
A descontinuação é mais útil para desencorajar a adoção de uma API em novo código.
Também exigimos que você marque as APIs como @deprecated
antes que elas sejam
@removed
, mas isso não motiva muito os
desenvolvedores a migrar de uma API que já estão usando.
Antes de descontinuar uma API, considere o impacto nos desenvolvedores. Os efeitos da descontinuação de uma API incluem:
javac
emite um aviso durante a compilação.- Os avisos de descontinuação não podem ser suprimidos globalmente ou como padrão, portanto,
os desenvolvedores que usam
-Werror
precisam corrigir ou suprimir individualmente cada uso de uma API descontinuada antes de atualizar a versão do SDK de compilação. - Os avisos de descontinuação nas importações de classes descontinuadas não podem ser suprimidos. Como resultado, os desenvolvedores precisam inserir o nome da classe totalmente qualificado em cada uso de uma classe descontinuada antes de atualizar a versão do SDK de compilação.
- Os avisos de descontinuação não podem ser suprimidos globalmente ou como padrão, portanto,
os desenvolvedores que usam
- A documentação sobre
d.android.com
mostra um aviso de descontinuação. - Ambientes de desenvolvimento integrados (IDEs, na sigla em inglês) como o Android Studio mostram um aviso no site de uso da API.
- Os IDEs podem rebaixar a classificação ou ocultar a API do preenchimento automático.
Como resultado, a descontinuação de uma API pode desencorajar os desenvolvedores mais
preocupados com a integridade do código (aqueles que usam -Werror
) a adotar novos SDKs.
Os desenvolvedores que não se preocupam com avisos no código atual provavelmente
vão ignorar as descontinuações.
Um SDK que introduz um grande número de descontinuações piora esses dois casos.
Por esse motivo, recomendamos a descontinuação de APIs apenas nos casos em que:
- Planejamos
@remove
a API em uma versão futura. - O uso da API leva a um comportamento incorreto ou indefinido que não podemos corrigir sem interromper a compatibilidade.
Quando você descontinuar uma API e substituí-la por uma nova, recomendamos
adicionar uma API de compatibilidade correspondente a uma biblioteca do Jetpack, como
androidx.core
, para simplificar o suporte a dispositivos antigos e novos.
Não recomendamos a descontinuação de APIs que funcionam conforme o esperado nas versões atuais e futuras:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
A descontinuação é adequada nos casos em que as APIs não podem mais manter os comportamentos documentados:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Mudanças em APIs descontinuadas
É necessário manter o comportamento das APIs descontinuadas. Isso significa que as implementações de teste precisam permanecer as mesmas, e os testes precisam continuar sendo aprovados depois que a API for descontinuada. Se a API não tiver testes, adicione-os.
Não expanda as plataformas de API descontinuadas em versões futuras. É possível adicionar anotações
de correção de lint (por exemplo, @Nullable
) a uma API descontinuada
existente, mas não é possível adicionar novas APIs.
Não adicione novas APIs como descontinuadas. Se alguma API foi adicionada e posteriormente descontinuada em um ciclo de pré-lançamento (ou seja, inicialmente entraria na API pública como descontinuada), você precisa removê-las antes de finalizar a API.
Exclusão reversível
A remoção parcial é uma mudança que quebra a origem, e você deve evitá-la em APIs públicas,
a menos que o Conselho de API a aprove explicitamente.
Para APIs do sistema, é necessário descontinuar a API durante
a duração de uma versão principal antes de uma remoção suave. Remova todas as referências de documentos para
as APIs e use a anotação de documentos @removed <summary>
ao remover
as APIs. O resumo precisa incluir o motivo da remoção e pode incluir uma
estratégia de migração, conforme explicado em Descontinuação.
O comportamento das APIs removidas parcialmente pode ser mantido como está, mas o mais importante é que elas precisam ser preservadas para que os autores de chamadas atuais não falhem ao chamar a API. Em alguns casos, isso pode significar preservar o comportamento.
A cobertura de teste precisa ser mantida, mas o conteúdo dos testes pode precisar mudar para acomodar mudanças comportamentais. Os testes ainda precisam validar que os autores de chamadas atuais não falham no momento da execução. Você pode manter o comportamento das APIs removidas parcialmente, mas o mais importante é que elas precisam ser preservadas para que os autores de chamadas atuais não falhem ao chamar a API. Em alguns casos, isso pode significar preservar o comportamento.
Você precisa manter a cobertura de teste, mas o conteúdo dos testes pode precisar mudar para acomodar mudanças comportamentais. Os testes ainda precisam validar que os autores de chamadas atuais não falham no momento da execução.
Em um nível técnico, removemos a API do JAR do stub do SDK e do caminho de classe
no tempo de compilação usando a anotação Javadoc @remove
, mas ela ainda existe no
caminho de classe no tempo de execução, semelhante às APIs @hide
:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
Do ponto de vista do desenvolvedor de apps, a API não aparece mais no preenchimento automático
e o código-fonte que faz referência a ela não é compilado quando o compileSdk
é
igual ou mais recente do que o SDK em que a API foi removida. No entanto, o código-fonte
continua sendo compilado com sucesso em SDKs anteriores e binários que
fazem referência à API.
Algumas categorias de API não podem ser removidas de forma suave. Não é possível remover soft algumas categorias de API.
Métodos abstratos
Não é possível remover métodos abstratos de forma suave em classes que os desenvolvedores podem estender. Isso torna impossível para os desenvolvedores estenderem a classe em todos os níveis do SDK.
Em casos raros em que nunca e nunca será possível para os desenvolvedores estenderem uma classe, ainda é possível remover métodos abstratos de forma suave.
Remoção forçada
A remoção permanente é uma mudança binária e nunca deve ocorrer em APIs públicas.
Anotação desencorajada
Usamos a anotação @Discouraged
para indicar que não recomendamos uma API
na maioria dos casos (mais de 95%). As APIs desaconselhadas são diferentes das APIs descontinuadas porque
existe um caso de uso crítico restrito que impede a descontinuação. Ao marcar uma
API como desaconselhada, você precisa fornecer uma explicação e uma solução 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);
}
Não é recomendável adicionar novas APIs como desaconselhadas.
Mudanças no comportamento de APIs atuais
Em alguns casos, talvez seja necessário mudar o comportamento de implementação de uma
API existente. Por exemplo, no Android 7.0, melhoramos o DropBoxManager
para
comunicar claramente quando os desenvolvedores tentavam postar eventos muito grandes para
enviar pelo Binder
.
No entanto, para evitar problemas com apps atuais, recomendamos
preservar um comportamento seguro para apps mais antigos. Historicamente, protegemos essas
mudanças de comportamento com base na ApplicationInfo.targetSdkVersion
do app, mas
recentemente migramos para exigir o uso do framework de compatibilidade do app. Confira
um exemplo de como implementar uma mudança de comportamento usando esse novo 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
}
}
}
O uso desse design do framework de compatibilidade de apps permite que os desenvolvedores desativem temporariamente mudanças de comportamento específicas durante as versões Beta e de prévia como parte da depuração dos apps, em vez de forçar a adaptação a dezenas de mudanças de comportamento simultaneamente.
Compatibilidade com versões futuras
A compatibilidade futura é uma característica de design que permite que um sistema aceite entradas destinadas a uma versão posterior dele mesmo. No caso do design da API, é preciso prestar atenção especial ao design inicial e às mudanças futuras porque os desenvolvedores esperam programar o código uma vez, testá-lo uma vez e executá-lo em todos os lugares sem problemas.
Os seguintes itens causam os problemas de compatibilidade futura mais comuns no Android:
- Adição de novas constantes a um conjunto (como
@IntDef
ouenum
) que antes era considerado completo (por exemplo, ondeswitch
tem umdefault
que gera uma exceção). - Foi adicionado suporte a um recurso que não é capturado diretamente na plataforma da API,
por exemplo, suporte para a atribuição de recursos do tipo
ColorStateList
em XML, em que anteriormente só eram aceitos recursos<color>
. - Restrições mais flexíveis nas verificações de tempo de execução, por exemplo, a remoção de uma
verificação
requireNotNull()
que estava presente em versões anteriores.
Em todos esses casos, os desenvolvedores descobrem que algo está errado apenas no momento de execução. Pior ainda, eles podem descobrir isso como resultado de relatórios de erros de dispositivos mais antigos no campo.
Além disso, esses casos são mudanças de API tecnicamente válidas. Elas não interrompem a compatibilidade binária ou de origem, e o lint da API não detecta nenhum desses problemas.
Como resultado, os designers de API precisam prestar atenção ao modificar classes existentes. Faça a pergunta: "Essa mudança vai fazer com que o código escrito e testado somente na versão mais recente da plataforma falhe em versões anteriores?"
Esquemas XML
Se um esquema XML servir como uma interface estável entre os componentes, esse esquema precisa ser especificado explicitamente e evoluir de uma maneira compatível com versões anteriores, semelhante a outras APIs do Android. Por exemplo, a estrutura de elementos e atributos XML precisa ser preservada de forma semelhante à maneira como os métodos e variáveis são mantidos em outras plataformas da API do Android.
Descontinuação do XML
Se você quiser descontinuar um elemento ou atributo XML, adicione o
marcador xs:annotation
, mas continue oferecendo suporte a todos os arquivos XML
seguindo o ciclo de vida de evolução @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>
Os tipos de elementos precisam ser preservados
Os esquemas oferecem suporte aos elementos sequence
, choice
e all
como
elementos filhos do elemento complexType
. No entanto, esses elementos filhos diferem no
número e na ordem dos elementos filhos. Portanto, modificar um tipo existente
seria uma mudança incompatível.
Se você quiser modificar um tipo existente, a prática recomendada é descontinuar o tipo antigo e introduzir um novo tipo para substituí-lo.
<!-- 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>
Padrões específicos da linha principal
O Mainline é um projeto que permite atualizar subsistemas ("mainline modules") do SO Android individualmente, em vez de atualizar toda a imagem do sistema.
Os módulos principais precisam ser "desempacotados" da plataforma principal, o que significa que todas as interações entre cada módulo e o restante do mundo precisam ser feitas usando APIs formais (públicas ou do sistema).
Há certos padrões de design que os módulos principais precisam seguir. Esta seção os descreve.
O padrão <Module>FrameworkInitializer
Se um módulo principal precisar expor classes @SystemService
(por exemplo,
JobScheduler
), use o seguinte padrão:
Exponha uma classe
<YourModule>FrameworkInitializer
do seu módulo. Essa classe precisa estar em$BOOTCLASSPATH
. Exemplo: StatsFrameworkInitializerMarque-o com
@SystemApi(client = MODULE_LIBRARIES)
.Adicione um método
public static void registerServiceWrappers()
a ele.Use
SystemServiceRegistry.registerContextAwareService()
para registrar uma classe de gerenciador de serviços quando ela precisar de uma referência a umContext
.Use
SystemServiceRegistry.registerStaticService()
para registrar uma classe de gerenciador de serviços quando ela não precisar de uma referência a umContext
.Chame o método
registerServiceWrappers()
do inicializador estático deSystemServiceRegistry
.
O padrão <Module>ServiceManager
Normalmente, para registrar objetos de vinculação de serviço do sistema ou receber referências
a eles, é necessário usar
ServiceManager
,
mas os módulos principais não podem usá-lo porque ele está oculto. Essa classe está oculta
porque os módulos principais não podem registrar ou se referir a objetos de vinculação de
serviço do sistema expostos pela plataforma estática ou por outros módulos.
Os módulos principais podem usar o padrão a seguir para registrar e receber referências a serviços de vinculação implementados no módulo.
Crie uma classe
<YourModule>ServiceManager
, seguindo o design de TelephonyServiceManager.Exponha a classe como
@SystemApi
. Se você só precisar acessar o recurso de classes$BOOTCLASSPATH
ou classes do servidor do sistema, use@SystemApi(client = MODULE_LIBRARIES)
. Caso contrário,@SystemApi(client = PRIVILEGED_APPS)
funcionará.Essa classe consiste em:
- Um construtor oculto, para que apenas o código estático da plataforma possa ser instanciado.
- Métodos getter públicos que retornam uma instância
ServiceRegisterer
para um nome específico. Se você tiver um objeto de vinculação, precisará de um método getter. Se você tiver dois, precisará de dois getters. - Em
ActivityThread.initializeMainlineModules()
, instancie essa classe e transmita-a a um método estático exposto pelo módulo. Normalmente, você adiciona uma API@SystemApi(client = MODULE_LIBRARIES)
estática na classeFrameworkInitializer
que a usa.
Esse padrão impediria que outros módulos principais acessassem essas APIs,
porque não há como outros módulos conseguirem uma instância de
<YourModule>ServiceManager
, mesmo que as APIs get()
e register()
sejam
visíveis para eles.
Confira como a telefonia recebe uma referência ao serviço de telefonia: link de pesquisa de código.
Se você implementar um objeto de vinculação de serviço em código nativo, use
as APIs nativas AServiceManager
.
Essas APIs correspondem às APIs Java ServiceManager
, mas as nativas são
expostas diretamente aos módulos principais. Não use-as para registrar ou se referir a
objetos de vinculação que não são de propriedade do módulo. Se você expor um objeto de vinculação
de forma nativa, o <YourModule>ServiceManager.ServiceRegisterer
não precisará de um
método register()
.
Definições de permissões em módulos principais
Os módulos principais que contêm APKs podem definir permissões (personalizadas) no APK
AndroidManifest.xml
da mesma forma que um APK normal.
Se a permissão definida for usada apenas internamente em um módulo, o nome da permissão vai precisar ter o nome do pacote do APK como prefixo, por exemplo:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
Se a permissão definida for fornecida como parte de uma API de plataforma atualizável para outros apps, o nome da permissão precisa ter o prefixo "android.permission". (como qualquer permissão estática da plataforma) mais o nome do pacote do módulo, para indicar que é uma API de plataforma de um módulo, evitando conflitos de nomenclatura, por exemplo:
<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" />
Em seguida, o módulo pode expor esse nome de permissão como uma constante de API na superfície
da API, por exemplo,
HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.