Diretrizes da API do Android

Esta página é um guia para desenvolvedores entenderem os princípios gerais que o Conselho de APIs aplica nas revisões 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 executadas em APIs.

Pense nisso como o guia das regras obedecidas pela ferramenta Lint, além de conselhos gerais sobre regras que não podem ser codificadas nessa ferramenta com alta precisão.

Ferramenta API Lint

O API Lint é integrado à ferramenta de análise estática Metalava e é executado automaticamente durante a validação na CI. É possível executar manualmente em um checkout da plataforma local usando m checkapi ou um checkout do AndroidX local usando ./gradlew :path:to:project:checkApi.

Regras de API

A plataforma Android e muitas bibliotecas Jetpack existiam antes da criação deste conjunto de diretrizes, e as políticas estabelecidas mais adiante nesta página estão em constante evolução para atender às necessidades do ecossistema Android.

Como resultado, algumas APIs atuais podem não seguir as diretrizes. Em outros casos, pode ser melhor para os desenvolvedores de apps que uma nova API permaneça consistente com as APIs atuais em vez de aderir estritamente às diretrizes.

Use seu julgamento e entre em contato com o Conselho de APIs se houver dúvidas difíceis sobre uma API que precisam ser resolvidas ou diretrizes que precisam ser atualizadas.

Princípios básicos da API

Esta categoria se refere aos aspectos principais de uma API do Android.

Todas as APIs precisam ser implementadas

Independente do público-alvo de uma API (por exemplo, público ou @SystemApi), todas as superfícies de API precisam ser implementadas quando mescladas ou expostas como API. Não mescle stubs de API com a implementação para uma data posterior.

As superfícies 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.
  • As 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, geralmente, a ideia de que as APIs precisam ser implementadas.

Testar as superfícies da API oferece uma garantia básica de que a superfície da API é utilizável e que abordamos os casos de uso esperados. Testar a existência não é suficiente. É preciso testar o comportamento da API.

Uma mudança que adiciona uma nova API precisa incluir testes correspondentes na mesma CL ou tópico do Gerrit.

As APIs também precisam ser testáveis. Você precisa conseguir responder à pergunta: "Como um desenvolvedor de apps testa o código que usa sua API?"

Todas as APIs precisam ser documentadas

A documentação é uma parte fundamental da usabilidade da API. Embora a sintaxe de uma superfície de API possa parecer óbvia, os novos clientes não vão entender a semântica, o comportamento ou o contexto por trás da API.

Todas as APIs geradas precisam estar em conformidade com as diretrizes

As APIs geradas por ferramentas precisam seguir as mesmas diretrizes das APIs escritas 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

Esta categoria se refere ao estilo geral de código que os desenvolvedores devem usar, especialmente ao escrever APIs públicas.

Siga as convenções de codificaçã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 em Java e Kotlin.

Os acrônimos não podem 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 da implementação. Evite isso.

Classes

Esta seção descreve regras sobre classes, interfaces e herança.

Herde novas classes públicas da classe base apropriada

A herança expõe elementos de API na sua subclasse que podem não ser adequados. Por exemplo, uma nova subclasse pública de FrameLayout se parece com FrameLayout mais os novos comportamentos e elementos da API. Se essa 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 está usando.

Usar as classes de coleções básicas

Ao usar uma coleção como argumento ou retorná-la como um valor, sempre prefira a classe base em vez da implementação específica (por exemplo, retorne 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 cuja coleção precisa ser ordenada e Set para uma API cuja coleção precisa consistir em elementos únicos.

No Kotlin, prefira coleções imutáveis. Consulte Mutabilidade da coleta para mais detalhes.

Classes abstratas x interfaces

O Java 8 adiciona suporte a métodos de interface padrão, o que permite que os 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 compatíveis com o Java 8 ou versões mais recentes.

Nos casos em que a implementação padrão não tem estado, os designers de API devem preferir interfaces em vez de 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 é exigido 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 de classe precisam refletir o que eles estendem

Por exemplo, as classes que estendem Service devem ser chamadas de FooService para clareza:

public class IntentHelper extends Service {}
public class IntentService extends Service {}

Sufixos genéricos

Evite usar sufixos genéricos de nomes de classe, 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 fazem ponte entre várias classes, dê à classe que contém um nome significativo que explique o que ela faz.

Em casos muito limitados, usar o sufixo Helper pode ser adequado:

  • Usado para composição do comportamento padrão.
  • Pode envolver a delegação de um comportamento existente para novas classes
  • Talvez exija estado persistente
  • Normalmente envolve View

Por exemplo, se as dicas de ferramentas de backport exigirem a persistência do estado associado a um View e a chamada de vários métodos no View para instalar o backport, TooltipHelper seria 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 por 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 maior parte da IDL no Android está em AIDL, então esta página se concentra em AIDL.

As classes AIDL geradas não atendem aos requisitos do guia de estilo da API (por exemplo, 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 em uma API pública.

Em vez disso, adicione uma camada de API pública sobre a interface AIDL, mesmo que ela seja inicialmente um wrapper superficial.

Interfaces 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 com versões anteriores seja mantida. Por exemplo, talvez seja necessário adicionar novos argumentos às chamadas internas ou otimizar o tráfego de IPC usando lotes ou streaming, memória compartilhada ou algo semelhante. Nenhuma dessas ações pode ser realizada 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, encapsule 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 um novo argumento for necessário mais tarde para essa chamada, a interface interna poderá ser mínima e sobrecargas convenientes adicionadas à API pública. Você pode usar a camada de encapsulamento para lidar com 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 interface em si. 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 significado por si só e, portanto, não deve 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 gerenciamento precisam ser finais

As classes de gerenciamento precisam ser declaradas como final. As classes de gerenciamento se comunicam com serviços do sistema e são o único ponto de interação. Não é necessário fazer personalizações, então declare como final.

Não use CompletableFuture ou Future

java.util.concurrent.CompletableFuture tem uma grande superfície de API que permite mutação arbitrária do valor do futuro e tem padrões propensos a erros.

Por outro lado, java.util.concurrent.Future não tem escuta sem bloqueio, 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 por Kotlin e Java, prefira uma combinação de um callback de conclusão, Executor e, se a API oferecer suporte ao cancelamento, CancellationSignal.

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor,
    android.os.OutcomeReceiver<FooResult, Throwable> callback);

Se você estiver segmentando o Kotlin, prefira as funções suspend.

suspend fun asyncLoadFoo(): Foo

Em Bibliotecas de integração específicas do Java, você pode usar o ListenableFuture do Guava.

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

Não use "Optional"

Embora Optional possa ter vantagens em algumas superfícies de API, ele é inconsistente com a área de superfície da API Android atual. @Nullable e @NonNull oferecem assistência de ferramentas para segurança de null, e o Kotlin aplica contratos de nulidade no nível do compilador, tornando Optional desnecessário.

Para primitivos opcionais, use métodos has e get pareados. Se o valor não estiver definido (has retornar false), o método get vai gerar um IllegalStateException.

public boolean hasAzimuth() { ... }
public int getAzimuth() {
  if (!hasAzimuth()) {
    throw new IllegalStateException("azimuth is not set");
  }
  return azimuth;
}

Usar construtores particulares para classes não instanciáveis

As classes que só podem ser criadas por Builders, classes que contêm apenas constantes ou métodos estáticos ou classes não instanciáveis devem incluir pelo menos um construtor particular para evitar a instanciação usando o construtor padrão sem argumentos.

public final class Log {
  // Not instantiable.
  private Log() {}
}

Singletons

O singleton não é recomendado porque tem as seguintes desvantagens relacionadas a testes:

  1. A construção é gerenciada pela classe, impedindo o uso de simulações.
  2. Os testes não podem ser herméticos devido à natureza estática de um singleton.
  3. Para contornar esses problemas, os desenvolvedores precisam conhecer os detalhes internos do singleton ou criar um wrapper ao redor dele.

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 a própria estrutura de injeção de dependência para gerenciar a implementação sem precisar criar um wrapper. A biblioteca também pode fornecer um falso em um artefato -testing.

Classes que liberam recursos precisam implementar AutoCloseable

As classes que liberam recursos usando close, release, destroy ou métodos semelhantes precisam implementar java.lang.AutoCloseable para permitir que os desenvolvedores limpem automaticamente esses recursos ao usar um bloco try-with-resources.

Evite introduzir novas subclasses View em android.*

Não introduza novas classes que herdam direta 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 é prioriza o 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ção para desenvolvedores em bibliotecas do Jetpack. Oferecer esses componentes em bibliotecas permite 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 particulares e acessíveis apenas usando getters e setters públicos, independentemente de serem finais ou não.

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 padrão de nomenclatura de variáveis, por exemplo, Point.x e Point.y.

As classes Kotlin podem expor propriedades.

Os campos expostos precisam ser marcados como finais

Não é recomendado usar campos brutos (@see Don't expose raw fields). Mas na rara situação em que um campo é exposto como público, marque-o como final.

Os campos internos não devem ser expostos

Não faça referência a nomes de campos internos em APIs públicas.

public int mFlags;

Usar "public" em vez de "protected"

@see Use public instead of protected

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. Se não for esse o caso, 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ública.

As constantes static final precisam usar a convenção de nomenclatura com todas as letras maiúsculas e separadas por sublinhado.

Todas as palavras na constante precisam estar em 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 serem 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 constante de string precisam ser consistentes com o nome da constante e geralmente devem ser definidos no pacote ou domínio. Exemplo:

public static final String FOO_THING = "foo"

não é nomeado de forma consistente nem tem o 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 intents, bem como as entradas de pacotes, precisam ser namespaceados usando o nome do pacote em que estã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"
}

Usar "public" em vez de "protected"

@see Use public instead of protected

Usar prefixos consistentes

Todas as constantes relacionadas precisam começar com o mesmo prefixo. Por exemplo, para um conjunto de constantes a serem usadas com valores de flag:

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see Use standard prefixes for constants

Use 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 aos 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

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.

Recursos de layout e drawable não podem ser expostos como APIs públicas. No entanto, se eles precisarem ser expostos, os layouts e elementos gráficos públicos devem 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 inserir valores constantes inline. Portanto, manter os valores iguais é considerado parte do contrato da API. Se o valor de uma constante MIN_FOO ou MAX_FOO puder mudar no futuro, considere transformá-las em métodos dinâmicos em vez disso.

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 isso, as constantes entregues aos apps precisam considerar a versão desejada da API do app e mapear as constantes mais recentes para um valor consistente. Considere o seguinte cenário:

Origem 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 um sucesso.

Os métodos que retornam constantes podem processar casos como esse 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 vai mudar no futuro. Se você definir uma API com uma constante UNKNOWN ou UNSPECIFIED que pareça um catch-all, os desenvolvedores vão presumir que as constantes publicadas quando escreveram 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 no comportamento do targetSdkVersion no código da biblioteca é complicado e propenso a erros.

Constante de número inteiro ou string

Use constantes inteiras e @IntDef se o namespace para valores não for extensível fora do seu pacote. Use constantes de string se o namespace for compartilhado ou puder ser estendido por código fora do seu 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 Kotlin públicas, porque o compilador Kotlin não garante a compatibilidade binária ou da API de linguagem para o 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.

Em 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 retorne um novo objeto.

Ao fornecer uma função copy() em 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 corresponda à implementação da classe de dados do Kotlin, por exemplo, User(var1=Alex, var2=42).

Métodos

São regras sobre vários detalhes específicos em métodos, parâmetros, nomes de métodos, 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 desaçucarização e devem ser preferidos ao expressar tempo em parâmetros de API ou valores de retorno.

Prefira expor apenas variantes de uma API que aceitam ou retornam 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 pretendidos tenha um impacto proibitivo no desempenho.

Os métodos que expressam durações devem ser chamados de "duration"

Se um valor de tempo expressar a duração envolvida, nomeie o parâmetro como "duration", não "time".

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

Exceções:

"timeout" é adequado quando a duração se aplica especificamente a um valor de tempo limite.

"time" com um tipo de java.time.Instant é adequado 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 um tipo primitivo precisam ser nomeados com a unidade de tempo e usar "long".

Os métodos que aceitam ou retornam durações como um tipo primitivo devem ter o nome do método com o sufixo das unidades de tempo associadas (como Millis, Nanos, Seconds) para reservar o nome sem decoração 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 tempo SystemClock.elapsedRealtime().
  • @UptimeMillisLong: o valor é um carimbo de data/hora não negativo na base de tempo SystemClock.uptimeMillis().

Parâmetros ou valores de retorno de tempo primitivo devem usar long, não int.

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

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 desde 1970-01-01T00:00:00Z, portanto, na base de tempo System.currentTimeMillis().
  • @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 tempo SystemClock#elapsedRealtime().
  • @UptimeMillisLong: o valor é um carimbo de data/hora não negativo na base de tempo SystemClock#uptimeMillis().

Unidades de medida

Para todos os métodos que expressam uma unidade de medida diferente de tempo, prefira prefixos de unidades do SI em CamelCase.

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, a não ser 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ê a ele um novo nome.

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 remova os valores de parâmetro padrão (somente Kotlin)

Se um método for lançado com um parâmetro com um valor padrão, a remoção desse valor será uma mudança incompatível com a origem.

Os parâmetros de método mais distintos e identificadores devem vir primeiro

Se você tiver um método com vários parâmetros, coloque os mais relevantes primeiro. Parâmetros que especificam flags e outras opções são menos importantes do que aqueles que descrevem o objeto em que a ação está sendo realizada. 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 em sobrecargas

Builders

O padrão Builder é recomendado para criar objetos Java complexos e é usado com frequência no Android para 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 de 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 geralmente 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 opcionais de criação
  • Configure 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 ao escrever.
  • Configure a criação incremental de um objeto, em que várias partes diferentes do código de configuração 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 parâmetro opcional, quase sempre será possível pular um builder e usar um construtor simples.

As classes de origem Kotlin devem preferir construtores anotados com @JvmOverloads com argumentos padrão em vez de Builders, mas podem melhorar a usabilidade para clientes Java fornecendo também 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 o encadeamento 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 builder de 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 superfície da API Android, todos os builders precisam ser criados por um construtor e não por 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 um método de fábrica/mecanismo de criação no estilo DSL. As bibliotecas não podem usar @PublishedApi internal para ocultar seletivamente o construtor da classe Builder de clientes 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)

Argumentos opcionais, por exemplo, @Nullable, precisam ser movidos para métodos setter. O construtor do builder precisa gerar uma NullPointerException (considere usar Objects.requireNonNull) se algum argumento obrigatório não for especificado.

As classes builder precisam ser classes internas estáticas finais dos tipos criados.

Para fins de organização lógica em um pacote, as classes de builder geralmente são 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 de uma instância atual.

Os builders podem incluir um construtor de cópia para criar uma nova instância de builder de um builder ou objeto criado. Eles não devem fornecer métodos alternativos para criar instâncias de builder com base em 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 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

Geralmente, é mais simples usar um valor anulável para entrada de segundo grau, principalmente em Kotlin, que usa argumentos padrão em vez de builders e sobrecargas.

Além disso, os setters de @Nullable vão corresponder aos 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.
 */

Setters de builder podem ser fornecidos para propriedades mutáveis em que 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 a classe 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 esperado:

  1. O objeto criado precisa ser imediatamente utilizável. Portanto, os setters devem ser fornecidos para todas as propriedades relevantes, sejam mutáveis ou imutáveis.

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. Algumas chamadas adicionais podem precisar ser feitas antes que o objeto criado seja útil. Portanto, 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 builders 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();
}

Nomenclatura de métodos de builder

Os nomes dos métodos de 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 construído.

Os métodos Builder build() precisam retornar objetos @NonNull

Espera-se que o método build() de um builder retorne uma instância não nula do objeto construído. Se o objeto não puder ser criado devido a parâmetros inválidos, a validação poderá ser adiada para o método de build e uma IllegalStateException será gerada.

Não exponha bloqueios internos

Os métodos na API pública não devem usar a palavra-chave synchronized. Essa palavra-chave faz com que seu objeto ou classe seja usado como o bloqueio. Como ele fica exposto a outros, você pode encontrar efeitos colaterais inesperados se outro código fora da sua classe começar a usá-lo para fins de bloqueio.

Em vez disso, faça o 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 de estilo de acessador precisam seguir as diretrizes de propriedades do Kotlin

Quando vistos de fontes Kotlin, os métodos de estilo de acessador (aqueles que usam os prefixos get, set ou is) também estarão disponíveis como propriedades Kotlin. Por exemplo, int getField() definido em Java está disponível em 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 devem se comportar de maneira semelhante aos campos Java. Evite usar prefixos de estilo de acessador quando:

  • O método tem efeitos colaterais. Prefira um nome mais descritivo.
  • O método envolve um trabalho computacionalmente caro. Prefira compute.
  • O método envolve o bloqueio ou outro trabalho de longa duração para retornar um valor, como IPC ou outra E/S. Prefira fetch.
  • O método bloqueia a linha de execução até que possa retornar um valor. Prefira await.
  • O método retorna uma nova instância de objeto em cada chamada. Prefira create.
  • O método pode não retornar um valor corretamente. Prefira request.

Realizar um trabalho computacionalmente caro uma vez e armazenar o valor em cache para chamadas subsequentes ainda conta como realizar um trabalho computacionalmente caro. O jank não é amortizado entre frames.

Usar o prefixo "is" para métodos de acesso booleanos

Essa é a convenção de nomenclatura padrão para métodos e campos booleanos em Java. Em geral, os nomes de métodos e variáveis booleanos precisam ser escritos como perguntas que são respondidas pelo valor de retorno.

Os métodos acessadores booleanos do 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;

Usar 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

As propriedades e os métodos de acesso geralmente usam nomes positivos, por exemplo, Enabled em vez de Disabled. Usar 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);

Em casos em que o booleano descreve a inclusão ou a propriedade de uma propriedade, você pode usar 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 pode e deve:

// "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 devem ser escritos como perguntas 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: adicione get e coloque em maiúscula o primeiro caractere do getter, e adicione set e coloque em maiúscula o primeiro caractere do setter. A declaração de propriedade vai gerar métodos chamados 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 próprio nome da propriedade será usado como 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

Acessadores de máscara de bits

Consulte Usar @IntDef para flags de máscara de bits para diretrizes da API sobre como definir flags de máscara de bits.

Setters

Dois métodos setter precisam ser fornecidos: um que usa uma string de bits completa e substitui todas as flags atuais, e outro que usa 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 receber 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();

Usar "public" em vez de "protected"

Sempre prefira public a protected na API pública. O acesso protegido acaba sendo problemático a longo prazo, porque os implementadores precisam substituir para fornecer acessadores públicos em casos em que o acesso externo por padrão teria sido tão bom quanto.

Lembre-se de que a visibilidade protected não impede que os desenvolvedores chamem uma API. Ela apenas torna isso um pouco mais desagradável.

Implemente nenhum ou ambos os métodos equals() e hashCode()

Se você substituir um, precisará substituir o outro.

Implementar toString() para classes de dados

É recomendável que as classes de dados substituam toString() para ajudar os desenvolvedores a depurar o código.

Documente se a saída é para comportamento do programa ou depuração

Decida se você quer que o comportamento do programa dependa da sua implementação ou não. Por exemplo, UUID.toString() e File.toString() documentam o formato específico para uso em programas. Se você estiver expondo informações apenas para depuração, como Intent, implique herdar 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ê vai incentivar os desenvolvedores a analisar e confiar na saída do toString(), o que vai impedir mudanças futuras. Uma boa prática é implementar toString() usando apenas a API pública do objeto.

Desincentivar a confiança na 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 seu objeto na saída toString() torna 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 seus 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 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 limites de usuários é representado como content:// Uris. Para permitir 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 o excesso de memória dessas classes, o acesso de métodos a valores e, mais importante, o autoboxing que vem da transmissão entre tipos primitivos e de objeto. Evitar esses comportamentos economiza memória e alocações temporárias que podem levar a coletas de lixo caras e mais frequentes.

Use anotações para esclarecer parâmetros e valores de retorno válidos

Anotações para desenvolvedores foram adicionadas para ajudar a esclarecer os valores permitidos em várias situações. Isso facilita a ajuda das ferramentas aos desenvolvedores quando eles fornecem valores incorretos (por exemplo, transmitir um int arbitrário quando o framework exige um de um conjunto específico de valores constantes). Use qualquer uma das seguintes anotações quando apropriado:

Nulidade

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 itens como @Nullable é relativamente novo no Android, então a maioria dos métodos de API do Android não é documentada de forma consistente. Portanto, temos um estado triplo de "desconhecido, @Nullable, @NonNull", e é por isso que @NonNull faz parte das diretrizes da API:

@NonNull
public String getName()

public void setName(@NonNull String name)

Para a documentação da plataforma Android, a anotação dos parâmetros do método gera automaticamente a documentação na forma "Este valor pode ser nulo", a menos que "nulo" seja usado explicitamente em outro lugar na documentação do parâmetro.

Métodos "não realmente anuláveis" atuais:os 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()). Os métodos @NotNull requireFoo() complementares que geram IllegalArgumentException precisam ser adicionados para desenvolvedores que não querem verificar valores 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, o método na classe de implementação precisa ser writeToParcel(@NonNull Parcel, int), não writeToParcel(Parcel, int)). No entanto, as APIs atuais que não têm as anotações não precisam ser "corrigidas".

Aplicação de valores nulos

Em Java, os métodos são recomendados para realizar a validação de entrada de parâmetros @NonNull usando Objects.requireNonNull() e gerar um NullPointerException quando os parâmetros são nulos. Isso é feito automaticamente em Kotlin.

Recursos

Identificadores de recursos:parâmetros inteiros que denotam IDs de recursos específicos precisam ser anotados com a definição de tipo de recurso apropriada. Há uma anotação para cada tipo de recurso, como @StringRes, @ColorRes e @AnimRes, além da @AnyRes, que abrange todos os casos. Por exemplo:

public void setTitle(@StringRes int resId)

@IntDef para conjuntos de constantes

Constantes mágicas:os parâmetros String e int destinados a receber um de um conjunto finito de valores possíveis indicados por constantes públicas precisam ser anotados adequadamente com @StringDef ou @IntDef. Com elas, é possível 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);

É recomendado que os métodos verifiquem a validade dos parâmetros anotados e lancem 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 "prefix", que são usados para emitir automaticamente a documentação de todos os valores.

@SdkConstant para constantes do SDK

@SdkConstant Anote campos públicos quando eles forem 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 capacidade de aceitar nulo compatível para substituições

Para compatibilidade de API, a capacidade de aceitar valores nulos das substituições precisa ser compatível com a capacidade de aceitar valores nulos atual do elemento pai. A tabela a seguir representa as expectativas de compatibilidade. As substituições precisam ser tão restritivas quanto o elemento que substituem ou mais restritivas.

Tipo Pai/mãe filho(a);
Tipo de retorno Sem anotações Não anotado ou não nulo
Tipo de retorno Nullable Aceita ou não valores nulos
Tipo de retorno Nonnull Nonnull
Argumento divertido Sem anotações Sem anotação ou anulável
Argumento divertido Nullable Nullable
Argumento divertido Nonnull Aceita ou não valores nulos

Prefira argumentos não anuláveis (como @NonNull) sempre que possível.

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 propriedades 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 "sem sentido" em que o desenvolvedor precisa definir parâmetros finais 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êineres 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 capacidade de aceitar nulo para pares de get e set precisam concordar

Os pares de métodos get e set para uma única propriedade lógica sempre precisam concordar nas anotações de capacidade de aceitar nulo. Não seguir essa diretriz vai prejudicar a sintaxe de propriedades do Kotlin, e adicionar anotações de nulidade conflitantes a métodos de propriedade atuais é, portanto, uma mudança que causa incompatibilidade de origem para usuários do Kotlin.

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

Retornar valor em condições de falha ou erro

Todas as APIs precisam permitir que os apps reajam a erros. Retornar false, -1, null ou outros valores gerais de "algo deu errado" não informa ao desenvolvedor o suficiente sobre a falha para definir as expectativas do usuário ou rastrear com precisão a confiabilidade do app em 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 apresentar ao usuário ou reagir de maneira adequada?

  1. É bom (e recomendado) 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 devem ser expostos como métodos.
  2. Verifique se a opção de tratamento de erros escolhida oferece a flexibilidade necessária para introduzir novos tipos de erros no futuro. Para @IntDef, isso significa incluir um valor OTHER ou UNKNOWN. Ao retornar um novo código, você pode verificar o targetSdkVersion do caller para evitar retornar um código de erro que o app não conhece. Para exceções, tenha uma superclasse comum que suas exceções implementem. Assim, qualquer código que processe esse tipo também vai capturar e processar subtipos.
  3. É difícil ou impossível para um desenvolvedor ignorar um erro por acidente. Se o erro for comunicado retornando um valor, anote o método com @CheckResult.

Prefira gerar uma ? extends RuntimeException quando uma condição de falha ou erro for atingida devido a algo que o desenvolvedor fez errado, por exemplo, ignorar restrições em parâmetros de entrada ou não verificar o estado observável.

Métodos setter ou de ação (por exemplo, perform) podem retornar um código de status inteiro se a ação falhar como resultado de um estado atualizado de forma assíncrona ou de condições fora do controle do desenvolvedor.

Os códigos de status precisam ser definidos na classe que os contém como campos public static final, com o prefixo ERROR_ e enumerados em uma anotação @hide @IntDef.

Os nomes de métodos sempre devem começar com o verbo, não com o sujeito.

O nome do método sempre começa 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 Collection em vez de matrizes como tipo de retorno ou parâmetro

As interfaces de coleta com tipo genérico oferecem várias vantagens em relação aos arrays, incluindo contratos de API mais fortes em relação à exclusividade e à ordenação, suporte a genéricos e vários métodos convenientes para desenvolvedores.

Exceção para primitivos

Se os elementos forem primitivos, use matrizes para evitar o custo do boxing automático. Consulte Usar e retornar primitivos brutos em vez de versões encadeadas

Exceção para código sensível ao desempenho

Em determinados cenários, quando a API é usada em código sensível ao desempenho (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 rotatividade de memória.

Exceção para Kotlin

As matrizes Kotlin são invariantes, e a linguagem Kotlin oferece muitas APIs de utilidade para matrizes. Portanto, elas estão no mesmo nível que List e Collection para APIs Kotlin destinadas ao acesso em Kotlin.

Preferir coleções @NonNull

Sempre prefira @NonNull para objetos de coleta. 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.

Quando as anotações de tipo são compatíveis, sempre prefira @NonNull para elementos de coleção.

Também é preferível usar @NonNull ao usar matrizes em vez de coleções (consulte o item anterior). Se a alocação de objetos for uma preocupação, 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 Kotlin devem 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 retorno mutável tipo.

No entanto, as APIs Java devem preferir tipos de retorno mutáveis por padrão porque a implementação da plataforma Android de APIs Java 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 (de propósito ou por engano) para quebrar o padrão de uso pretendido da API, as APIs Java precisam 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 convenções de nomenclatura de 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 de objetos de tipo de dados retornados

Assim como as APIs que retornam coleções, as que retornam objetos de 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 ou da reutilização de objetos. Não escreva sua própria estrutura de dados de pool de objetos e não exponha objetos reutilizados em APIs públicas. Em qualquer caso, tenha muito cuidado ao gerenciar o acesso simultâneo.

Uso do tipo de parâmetro vararg

É recomendável que as APIs Kotlin e Java usem vararg nos casos em que o desenvolvedor provavelmente criaria uma matriz no site de chamada com o único objetivo 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 em Java e Kotlin de parâmetros vararg 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á persistido 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 do método inicial e a criação da cópia, nem contra mutações dos objetos contidos na matriz.

Fornecer a 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 API for indiferente à ordem dos elementos e não permitir duplicados ou se eles não fizerem sentido.

  • Collection<Foo>, se a API for indiferente à ordem e permitir duplicatas.

Funções de conversão do Kotlin

O Kotlin usa com frequência .toFoo() e .asFoo() para receber 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 vai além e usa isso para conversões primitivas, como 25.toFloat().

A distinção entre conversões chamadas .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 depois, o novo objeto não vai refletir essas mudanças. Da mesma forma, se o objeto new for modificado depois, o objeto old 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, um objeto decorado ou uma conversão

A conversão de tipos no Kotlin é feita usando a palavra-chave as. Ela reflete uma mudança na interface, mas não na identidade. Quando usado como 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 de classe do receptor e do resultado reduz o acoplamento entre tipos. Uma conversão ideal precisa apenas de acesso à API pública do objeto original. Isso prova, por exemplo, que um desenvolvedor também pode escrever 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 apropriada precisa ser usada, como java.lang.NullPointerException, para permitir que os desenvolvedores processem exceções sem serem muito abrangentes.

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 das 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 deve agir em resposta.

O tempo verbal passado e presente deve 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 processo de acontecer.

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 realizar 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 devem ser chamados de 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 um precedente, prefira adicionar e remover.

Os métodos que envolvem o registro ou cancelamento 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 tentadora para casos em que os desenvolvedores podem querer encadear um callback existente com a própria substituição, mas é frágil e dificulta a compreensão 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, mas não sabe como fazer isso sem conhecer o tipo de B, e B foi criado para permitir essas modificações do callback encapsulado.

Aceitar Executor para controlar o envio de callbacks

Ao registrar callbacks que não têm expectativas explícitas de threading (praticamente em qualquer lugar fora do kit de ferramentas de UI), é 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 diretrizes 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 precisará ser invocado na linha de execução principal usando Looper.getMainLooper(), e isso 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)

Armadilhas de implementação do Executor:observe que o seguinte é um executor válido.

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

Isso significa que, ao implementar APIs que assumem essa forma, a implementação do objeto binder recebido 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 sua API quiserem as informações de UID ou PID do caller, 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.

Especificar um Executor deve ser compatível com sua API. Em casos críticos de desempenho, os apps podem precisar executar o código imediatamente ou de forma síncrona com o feedback da API. Aceitar um Executor permite isso. Criar defensivamente um HandlerThread adicional ou semelhante ao trampolim de anula esse caso de uso desejável.

Se um app vai executar um código caro em algum lugar do próprio processo, deixe que ele faça isso. 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 um único callback:quando a natureza dos eventos relatados exige apenas o suporte a uma única instância de callback, use o seguinte estilo:

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Usar Executor em vez de Handler

O Handler do Android era usado como padrão para redirecionar a execução de callback 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 UI 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 atuais/preferidos.

Bibliotecas de simultaneidade modernas, como kotlinx.coroutines ou RxJava, oferecem mecanismos de agendamento próprios que realizam o próprio envio quando necessário. Por isso, é importante oferecer a capacidade de usar um executor direto (como Runnable::run) para evitar a latência de saltos de linha de execução duplos. 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. Alguns argumentos comuns para uma exceção incluem:

Preciso usar um Looper porque preciso de um Looper para epoll no evento. Essa solicitação de exceção é concedida porque os benefícios de Executor não podem ser realizados nessa situação.

Não quero que o código do app bloqueie minha linha de execução ao publicar o evento. Essa solicitação de exceção geralmente não é concedida para código executado em um processo de app. Os apps que fazem isso errado só prejudicam a si mesmos, sem afetar a integridade geral do sistema. Os apps que fazem isso corretamente ou usam uma estrutura de simultaneidade comum não devem pagar penalidades de latência adicionais.

Handler é consistente localmente com outras APIs semelhantes na mesma classe. Essa solicitação de exceção é concedida de acordo com a situação. A preferência é que sobrecargas baseadas em Executor sejam adicionadas, migrando 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 atuais e da probabilidade de os desenvolvedores precisarem usar métodos Handler atuais 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 um

unregisterThing(Thing)

Fornecer um identificador de solicitação

Se for razoável para um desenvolvedor reutilizar 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 com vários métodos

Callbacks de vários métodos devem preferir interface e usar métodos default ao adicionar a interfaces lançadas anteriormente. Antes, essa 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.
  }
}

Usar android.os.OutcomeReceiver ao modelar uma chamada de função não bloqueadora

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 em um método assíncrono sem 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 forma 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 requestFoo geraria é informada ao método OutcomeReceiver.onError da mesma forma.

Usar OutcomeReceiver para informar resultados de métodos assíncronos também oferece um wrapper suspend fun do Kotlin para métodos assíncronos usando a extensão Continuation.asOutcomeReceiver de 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 clientes Kotlin chamem métodos assíncronos sem bloqueio com a conveniência de uma chamada de função simples sem bloquear a linha de execução de chamadas. Essas extensões 1-1 para APIs de plataforma podem ser oferecidas como parte do artefato androidx.core:core-ktx no Jetpack quando combinadas 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 sobre 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 único (SAM)

O nível 24 da API adicionou os tipos java.util.function.* (documentos de referência) , que oferecem interfaces SAM genéricas, como Consumer<T>, adequadas para uso como lambdas de callback. Em muitos casos, a criação de novas interfaces SAM oferece pouco valor em termos de segurança de tipo ou comunicação de intenção, além de expandir desnecessariamente a área de superfície da API Android.

Considere usar estas interfaces genéricas em vez de criar novas:

Posicionamento dos parâmetros de SAM

Os parâmetros SAM precisam ser colocados por último para permitir o uso idiomático do Kotlin, mesmo que o método esteja sendo sobrecarregado com parâmetros adicionais.

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

Documentos

Estas são regras sobre a documentação pública (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 encontrou o método usando o preenchimento automático ou navegando pelos documentos de referência da API e tem uma quantidade mínima de contexto da superfície da API adjacente (por exemplo, a mesma classe).

Métodos

Os parâmetros de método e os valores de retorno precisam ser documentados usando as anotações de documentos @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 que ele faz, é possível omitir o @return e escrever documentos semelhantes a:

/**
 * Returns the priority of the thread.
 */
@IntRange(from = 1, to = 10)
public int getPriority() { ... }

Os documentos precisam ter links para outros documentos com constantes, métodos e outros elementos relacionados. Use tags do Javadoc (por exemplo, @see e {@link foo}), não apenas palavras de texto simples.

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

Usar 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 update-api ou docs target ao adicionar Javadoc

Essa regra é especialmente importante ao adicionar tags @link ou @see. Verifique se a saída está como esperado. A saída ERROR no Javadoc geralmente é causada por links incorretos. O update-api ou o docs Make target realiza essa verificação, mas o destino docs pode ser mais rápido se você estiver mudando apenas o Javadoc e não precisar executar o destino update-api.

Use {@code foo} para distinguir valores Java

Encapsule valores Java, como true, false e null, com {@code...} para distinguir esses valores do texto da documentação.

Ao escrever documentação em fontes Kotlin, você pode incluir o código entre crases como faria no 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 fragmento de frase. Se você tiver mais 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 do 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 originadas em Kotlin e destinadas ao uso 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 da geração. A exceção gerada também precisa indicar o motivo.

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 um @IntDef ou uma anotação semelhante que incorpora o contrato da API na 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 possa 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. Exceções assíncronas não devem 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 final

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 final (.) seguido de um espaço. Isso causa dois problemas:

  • Se um documento curto não terminar com um ponto final e se esse membro tiver herdado documentos que são coletados pela ferramenta, a sinopse também vai coletar esses documentos herdados. Por exemplo, consulte actionBarTabStyle na documentação do R.attr, que tem a descrição da dimensão adicionada à sinopse.
  • Evite "por exemplo" na primeira frase pelo mesmo motivo, porque o Doclava encerra os documentos de sinopse após "g". Por exemplo, consulte TEXT_ALIGNMENT_CENTER em View.java. O Metalava corrige automaticamente esse erro inserindo um espaço inseparável após o ponto final. No entanto, não cometa esse erro em primeiro lugar.

Formatar documentos para serem renderizados em HTML

O Javadoc é renderizado em HTML. Portanto, formate esses documentos de acordo com o seguinte:

  • As quebras de linha precisam usar uma tag <p> explícita. Não adicione uma tag de fechamento </p>.

  • 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 de fechamento </li>. Uma tag de fechamento </ul> ou </ol> é obrigató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. Você pode usar class="deprecated" em qualquer tag para indicar descontinuação.

  • Para criar uma fonte de código in-line, 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 os colchetes <>. É possível usar as entidades HTML &lt; e &gt;.

  • Outra opção é deixar colchetes <> brutos no snippet de código se você envolver as seções problemáticas em {@code foo}. Exemplo:

    <pre>{@code <manifest>}</pre>
    

Siga 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, descrições 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 (em inglês).

Regras específicas do framework 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 intents precisam usar o padrão create*Intent()

Os criadores de intents devem usar métodos chamados 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 arbitrários de chave para valor tipado. Em vez disso, use Bundle.

Isso geralmente acontece ao escrever APIs de plataforma que servem como canais de comunicação entre apps e serviços não relacionados à 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 Jetpack).

Nos casos em que a plataforma os dados, evite usar Bundle e prefira uma classe de dados fortemente tipada.

As implementações Parcelable precisam ter um campo CREATOR público

A inflação Parcelable é exposta por CREATOR, não por construtores brutos. Se uma classe implementar Parcelable, o campo CREATOR também precisará ser uma API pública, e o construtor da classe que recebe um argumento Parcel precisará ser particular.

Usar CharSequence para strings de interface

Quando uma string é apresentada em uma interface do usuário, use CharSequence para permitir instâncias de Spannable.

Se for apenas uma chave ou outro rótulo ou valor que não esteja visível para os usuários, use String.

Evite usar enums

IntDef precisa ser usado em vez de enums em todas as APIs da plataforma e deve ser considerado em APIs de biblioteca não agrupadas. Use enums somente quando tiver certeza de que novos valores não serão adicionados.

Benefícios do IntDef:

  • Permite adicionar valores ao longo do tempo
    • As instruções when do Kotlin podem falhar no tempo de execução se deixarem de ser exaustivas devido a um valor de enumeração adicionado na plataforma.
  • Nenhuma classe ou objeto usado no tempo de 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 da enumeração

  • Recurso idiomático das linguagens Java e Kotlin
  • Ativa a troca exaustiva, o uso da instrução when
    • Observação: os valores não podem mudar com o tempo. Consulte a lista anterior.
  • Nomenclatura claramente definida e fácil de descobrir
  • Ativa a verificação no momento da compilação.
    • Por exemplo, uma instrução when em Kotlin que retorna um valor
  • É uma classe funcional que pode implementar interfaces, ter helpers estáticos, expor 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 ordenação 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 independente de fornecedores. 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 Parcelable precisam ser finais

As classes Parcelable definidas pela plataforma são sempre carregadas de framework.jar. Portanto, é inválido que um app tente substituir uma implementação de Parcelable .

Se o app de envio estender uma Parcelable, o app de recebimento não terá a implementação personalizada do remetente para descompactar. Observação sobre compatibilidade com versões anteriores: se sua 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 RemoteException como RuntimeException

RemoteException normalmente é gerado pelo AIDL interno e indica que o processo do sistema foi eliminado ou que o app está tentando enviar muitos dados. Em ambos os casos, a API pública precisa gerar uma nova exceção como RuntimeException para evitar que os apps persistam decisões de segurança ou políticas.

Se você sabe que o outro lado de uma chamada Binder é o processo do sistema, este código clichê é a prática recomendada:

try {
    ...
} catch (RemoteException e) {
    throw e.rethrowFromSystemServer();
}

Gerar exceções específicas para mudanças na API

Os comportamentos da API pública 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, 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 clonar

O uso do método clone() do Java não é recomendado 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 receba 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 precisam adicionar 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 fechamento. Em vez disso, as APIs precisam retornar ou aceitar instâncias ParcelFileDescriptor. O código legado pode converter entre PFD e FD, se necessário, usando dup() ou getFileDescriptor().

Evite usar valores numéricos de tamanho ímpar

Evite usar valores short ou byte diretamente, porque eles geralmente limitam a evolução da API no futuro.

Evite usar BitSet

java.util.BitSet é ótimo para implementação, mas não para API pública. Ele é mutável, exige uma alocação para chamadas de método de alta frequência e não fornece 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 um Set<EnumType>. Para dados binários brutos, use byte[].

Prefira android.net.Uri

android.net.Uri é o encapsulamento preferido 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 dele é muito falha.

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 são exportadas como APIs, o compilador in-line as constantes, e apenas os valores (agora inúteis) permanecem no stub da API da anotação (para a plataforma) ou no JAR (para bibliotecas).

Portanto, os usos dessas anotações precisam ser marcados com a anotação de documentos @hide na plataforma ou a anotação de código @RestrictTo.Scope.LIBRARY) nas bibliotecas. Eles precisam ser marcados como @Retention(RetentionPolicy.SOURCE) nos dois 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 o SDK da plataforma e os AARs da biblioteca, uma ferramenta extrai as anotações e as agrupa separadamente das fontes compiladas. O Android Studio lê esse formato agrupado e aplica as definições de tipo.

Não adicione novas chaves de provedor de configurações

Não exponha novas chaves de Settings.Global, Settings.System, ou Settings.Secure.

Em vez disso, adicione uma API Java 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 sua configuração com uma permissão personalizada.
  • Não há uma maneira adequada de adicionar 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 existe há muito tempo, mas a equipe de localização descontinuou o uso dele em favor de uma API Java adequada LocationManager.isLocationEnabled() e da transmissão MODE_CHANGED_ACTION, que deu muito mais flexibilidade à equipe, e a semântica das APIs está muito mais clara agora.

Não estenda 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. Estender a atividade do seu recurso o torna incompatível com outros recursos que exigem que os usuários façam o mesmo. Em vez disso, use a composição com ferramentas como LifecycleObserver.

Usar getUser() do Context

As classes vinculadas a um Context, como tudo o que é retornado 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 de usuário se aceitar valores que não representam um único usuário, como UserHandle.ALL.

Usar UserHandle em vez de ints simples

UserHandle é preferível para oferecer segurança de tipo e evitar a confusão de IDs de usuário com uids.

Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);

Quando for inevitável, um int que representa um ID de usuário precisa ser anotado com @UserIdInt.

Foobar getFoobarForUser(@UserIdInt int user);

Prefira listeners ou callbacks a intents de transmissão

As intents de transmissão são muito eficientes, mas resultaram em comportamentos emergentes que podem afetar negativamente a integridade do sistema. Por isso, novas intents de transmissão devem ser adicionadas com cuidado.

Estas são 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, elas iniciam à força os apps que ainda não estão em execução. Embora isso possa às vezes ser um resultado esperado, pode resultar em uma enxurrada de dezenas de apps, afetando negativamente a integridade do sistema. Recomendamos usar estratégias alternativas, como JobScheduler, para coordenar melhor quando várias pré-condições são atendidas.

  • Ao enviar transmissões, há pouca capacidade de filtrar ou ajustar o conteúdo entregue aos apps. Isso dificulta ou impossibilita responder a futuras questões de privacidade 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 seu evento. Observamos várias filas de transmissão em situações reais com uma latência de ponta a ponta de 10 minutos ou mais.

Por esses motivos, incentivamos os novos recursos a considerar o uso de listeners ou callbacks ou outras facilidades, como JobScheduler, em vez de intents de transmissão.

Nos casos em que as intents de transmissão ainda são o design ideal, confira algumas práticas recomendadas a serem consideradas:

  • Se possível, use Intent.FLAG_RECEIVER_REGISTERED_ONLY para limitar sua transmissão a apps que já estão em execução. Por exemplo, o ACTION_SCREEN_ON usa esse design para evitar a ativação de apps.
  • Se possível, use Intent.setPackage() ou Intent.setComponent() para segmentar a transmissão para um app específico de interesse. Por exemplo, ACTION_MEDIA_BUTTON usa esse design para se concentrar no app atual que processa os controles de reprodução.
  • Se possível, defina sua transmissão como um <protected-broadcast> para evitar que apps maliciosos se passem pelo SO.

Intents em serviços para desenvolvedores vinculados ao sistema

Serviços que devem ser 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:

  1. 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).
  2. Documento sobre a classe que um desenvolvedor precisa adicionar um <intent-filter> ao AndroidManifest.xml para receber intents da plataforma.
  3. Considere adicionar uma permissão no nível do sistema para evitar que apps maliciosos enviem Intents aos serviços para desenvolvedores.

Interoperabilidade entre Kotlin e Java

Consulte o guia oficial de interoperabilidade entre Kotlin e Java do Android para conferir uma lista completa de diretrizes. Algumas diretrizes foram copiadas para este guia para melhorar a capacidade de descoberta.

Visibilidade da API

Algumas APIs Kotlin, como suspend funs, não foram criadas para serem usadas por 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 depuradores, o que dificulta a depuração.

Consulte o Guia de interoperabilidade entre Kotlin e Java ou o Guia assíncrono para orientações específicas.

Objetos complementares

O Kotlin usa companion object para expor membros estáticos. Em alguns casos, eles vão aparecer em Java em uma classe interna chamada Companion, e não na classe que a contém. As classes Companion podem aparecer como classes vazias em arquivos de texto da API. Isso está funcionando conforme o 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 expô-los diretamente na classe contida.

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 as políticas sobre os tipos de mudanças que você pode fazer nas APIs Android atuais e como implementar essas mudanças para maximizar a compatibilidade com apps e bases de código atuais.

Mudanças interruptivas binárias

Evite mudanças que quebrem o binário em superfícies de API pública 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 (em inglês) da Eclipse Foundation para uma explicação detalhada de quais tipos de mudanças na API são compatíveis em Java. Mudanças interruptivas de binários em APIs ocultas (por exemplo, do sistema) precisam seguir o ciclo de descontinuação/substituição.

Mudanças que interrompem a origem

Não recomendamos mudanças que interrompem a origem, mesmo que não sejam interruptivas binárias. Um exemplo de uma mudança compatível com binários, mas que quebra a fonte, é adicionar um tipo genérico a uma classe existente, que é compatível com binários mas pode introduzir erros de compilação devido a herança ou referências ambíguas. As mudanças que causam incompatibilidade de origem não geram erros ao executar make update-api. Portanto, é preciso entender o impacto das mudanças nas assinaturas de API existentes.

Em alguns casos, mudanças que quebram a fonte são necessárias para melhorar a experiência do desenvolvedor ou a correção do código. Por exemplo, adicionar anotações de nulidade a fontes Java melhora a interoperabilidade com o código Kotlin e reduz a probabilidade de erros, mas geralmente exige mudanças, às vezes significativas, no código-fonte.

Mudanças nas APIs privadas

Você pode mudar as APIs anotadas com @TestApi a qualquer momento.

Você precisa preservar as APIs anotadas com @SystemApi por três anos. Você precisa remover ou refatorar uma API do sistema de acordo com o seguinte cronograma:

  • API y: adicionada
  • API y+1: descontinuação
    • Marque o código com @Deprecated.
    • Adicione substituições e crie um link para a substituição no Javadoc do 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.
  • API y+2: remoção gradual
    • Marque o código com @removed.
    • Opcionalmente, gere uma exceção ou não faça nada para apps destinados ao nível atual do SDK para a versão.
  • API y+3: remoção definitiva
    • Remova completamente o código da árvore de origem.

Previsão de remoção

Consideramos a descontinuação como uma mudança na API, e ela pode ocorrer em uma versão principal (como uma letra). Use a anotação de origem @Deprecated e a anotação de documentos @deprecated <summary> juntas ao descontinuar APIs. Seu resumo precisa incluir uma estratégia de migração. Essa estratégia pode vincular a uma API substituta 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ê também precisa descontinuar as APIs definidas em XML e expostas em Java, incluindo atributos e propriedades estilizadas 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

As descontinuações são mais úteis para desencorajar a adoção de uma API em novos códigos.

Também exigimos que você marque as APIs como @deprecated antes de serem @removed, mas isso não oferece uma motivação forte para os desenvolvedores migrarem 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.
    • Não é possível suprimir ou definir como base os avisos de descontinuação de forma global. Por isso, os desenvolvedores que usam -Werror precisam corrigir ou suprimir todos os usos de uma API descontinuada antes de atualizar a versão do SDK de compilação.
    • Não é possível suprimir avisos de descontinuação em importações de classes descontinuadas. Como resultado, os desenvolvedores precisam inserir o nome da classe totalmente qualificado para cada uso de uma classe descontinuada antes de atualizar a versão do SDK de compilação.
  • A documentação sobre d.android.com mostra um aviso de descontinuação.
  • IDEs como o Android Studio mostram um aviso no site de uso da API.
  • As IDEs podem reduzir 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 os dois casos.

Por isso, recomendamos descontinuar 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 pode ser corrigido sem quebrar a compatibilidade.

Quando você descontinua uma API e a substitui por uma nova, recomendamos muito 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 descontinuar APIs que funcionam como 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 nas APIs descontinuadas

Você precisa 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 você descontinuar a API. 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 do lint (por exemplo, @Nullable) a uma API descontinuada, mas não é possível adicionar novas APIs.

Não adicione novas APIs como descontinuadas. Se alguma API foi adicionada e depois descontinuada em um ciclo de pré-lançamento (entrando inicialmente na superfície da API pública como descontinuada), você precisa removê-las antes de finalizar a API.

Remoção reversível

A remoção suave é uma mudança que causa incompatibilidade na origem, e você deve evitá-la em APIs públicas, a menos que o Conselho de APIs aprove explicitamente. Para APIs do sistema, é necessário descontinuar a API durante uma versão principal antes de uma remoção gradual. Remova todas as referências de documentos às APIs e use a anotação de documentos @removed <summary> ao remover APIs de forma suave. Seu resumo precisa incluir o motivo da remoção e pode incluir uma estratégia de migração, conforme explicamos em Descontinuação.

O comportamento das APIs removidas de forma suave pode ser mantido como está, mas, mais importante, deve ser preservado para que os chamadores 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 as mudanças de comportamento. Os testes ainda precisam validar se os chamadores atuais não falham durante a execução. Você pode manter o comportamento das APIs removidas de forma suave como está, mas, mais importante, é necessário preservar esse comportamento para que os chamadores atuais não falhem ao chamar a API. Em alguns casos, isso pode significar preservar o comportamento.

Você precisa manter a cobertura de testes, mas o conteúdo deles pode precisar mudar para se adequar às mudanças de comportamento. Os testes ainda precisam validar se os chamadores atuais não falham durante a execução.

Em um nível técnico, removemos a API do JAR stub do SDK e do classpath de tempo de compilação usando a anotação Javadoc @remove, mas ela ainda existe no classpath de 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 de um 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 será compilado quando o compileSdk for igual ou posterior ao SDK em que a API foi removida. No entanto, o código-fonte continua sendo compilado com sucesso em SDKs anteriores, e os binários que fazem referência à API continuam funcionando.

Algumas categorias de API não podem ser removidas de forma reversível. Você não pode remover suavemente determinadas categorias de API.

Métodos abstratos

Você não pode remover de forma reversível métodos abstratos em classes que os desenvolvedores podem estender. Isso impossibilita que os desenvolvedores estendam a classe em todos os níveis do SDK.

Em casos raros em que nunca foi e não será possível para os desenvolvedores estender uma classe, ainda é possível remover métodos abstratos de forma suave.

Remoção irreversível

A remoção completa é uma mudança que causa falhas binárias 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 (>95%) dos casos. As APIs desaconselhadas diferem das APIs descontinuadas porque há um caso de uso crítico específico que impede a descontinuação. Quando você marca uma API como desencorajada, é preciso 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 adicione novas APIs que não são recomendadas.

Mudanças no comportamento das APIs atuais

Em alguns casos, talvez seja necessário mudar o comportamento de implementação de uma API atual. 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 de apps. 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
    }
  }
}

Com esse design do framework de compatibilidade de apps, os desenvolvedores podem desativar temporariamente mudanças específicas de comportamento durante as prévias e versões Beta como parte da depuração dos apps, em vez de forçar o ajuste 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 de API, é preciso prestar atenção especial ao design inicial e às mudanças futuras, porque os desenvolvedores esperam escrever e testar o código uma vez e executá-lo em qualquer lugar sem problemas.

Os seguintes itens causam os problemas de compatibilidade com versões futuras mais comuns no Android:

  • Adicionar novas constantes a um conjunto (como @IntDef ou enum) anteriormente considerado completo (por exemplo, quando switch tem um default que gera uma exceção).
  • Adição de suporte a um recurso que não é capturado diretamente na superfície da API (por exemplo, suporte para atribuição de recursos do tipo ColorStateList em XML em que antes apenas recursos <color> eram compatíveis).
  • Diminuir as restrições nas verificações de tempo de execução, por exemplo, removendo uma verificação de requireNotNull() que estava presente em versões anteriores.

Em todos esses casos, os desenvolvedores só descobrem que algo está errado no tempo de execução. Pior ainda, eles podem descobrir isso como resultado de relatórios de erros de dispositivos mais antigos em campo.

Além disso, todos esses casos são mudanças de API tecnicamente válidas. Elas não quebram a compatibilidade binária ou de origem, e o lint da API não detecta nenhum desses problemas.

Por isso, os designers de API precisam ter muito cuidado ao modificar classes existentes. Pergunte: "Essa mudança vai fazer com que o código escrito e testado apenas 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 componentes, ele precisará 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 maneira semelhante a como métodos e variáveis são mantidos em outras superfícies de API do Android.

Suspensão do uso de XML

Se quiser descontinuar um elemento ou atributo XML, adicione o marcador xs:annotation, mas continue oferecendo suporte a todos os arquivos XML atuais seguindo o ciclo de vida de evolução @SystemApi típico.

<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 são compatíveis com os 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 quiser modificar um tipo existente, a prática recomendada é descontinuar o tipo antigo e introduzir um novo 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 ("módulos do Mainline") do SO Android individualmente, em vez de atualizar toda a imagem do sistema.

Os módulos principais precisam ser "desagrupados" 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 de sistema).

Há determinados padrões de design que os módulos principais precisam seguir. Esta seção descreve essas opções.

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: StatsFrameworkInitializer

  • Marque 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 um Context.

  • Use SystemServiceRegistry.registerStaticService() para registrar uma classe de gerenciador de serviços quando ela não precisar de uma referência a um Context.

  • Chame o método registerServiceWrappers() do inicializador estático SystemServiceRegistry.

O padrão <Module>ServiceManager

Normalmente, para registrar objetos de binder de serviço do sistema ou receber referências a eles, usa-se ServiceManager, mas os módulos principais não podem usar esse recurso porque ele está oculto. Essa classe está oculta porque os módulos principais não devem registrar nem se referir a objetos binder de serviço do sistema expostos pela plataforma estática ou por outros módulos.

Os módulos principais podem usar o seguinte padrão para registrar e receber referências a serviços de binder implementados no módulo.

  • Crie uma classe <YourModule>ServiceManager seguindo o design do TelephonyServiceManager.

  • Exponha a classe como @SystemApi. Se você só precisar acessar de classes $BOOTCLASSPATH ou classes de servidor do sistema, use @SystemApi(client = MODULE_LIBRARIES). Caso contrário, @SystemApi(client = PRIVILEGED_APPS) vai funcionar.

  • Essa classe consistiria em:

    • Um construtor oculto para que apenas o código estático da plataforma possa instanciá-lo.
    • Métodos getter públicos que retornam uma instância ServiceRegisterer para um nome específico. Se você tiver um objeto binder, 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 seu módulo. Normalmente, você adiciona uma API @SystemApi(client = MODULE_LIBRARIES) estática na classe FrameworkInitializer que a usa.

Esse padrão impede que outros módulos principais acessem essas APIs porque não há como outros módulos receberem 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 da pesquisa de código.

Se você implementar um objeto binder 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-os para registrar ou se referir a objetos binder que não são de propriedade do seu módulo. Se você expuser um objeto binder de nativo, seu <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 comum.

Se a permissão definida for usada apenas internamente em um módulo, o nome dela deverá ter como prefixo o nome do pacote do APK. 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 dela deverá ter o prefixo "android.permission." (como qualquer permissão estática da plataforma) mais o nome do pacote do módulo, para sinalizar que é uma API de plataforma de um módulo e evitar 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.