Para realizar operações de rede no seu aplicativo, o manifesto precisa incluir as seguintes permissões:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Práticas recomendadas para comunicação de rede segura
Antes de adicionar a funcionalidade de rede ao seu app, você precisa garantir que os dados e as informações dele permaneçam seguros quando forem transmitidos pela rede. Para fazer isso, siga estas práticas recomendadas de segurança de rede:
- Minimize a quantidade de dados confidenciais ou pessoais do usuário transmitidos pela rede.
- Envie todo o tráfego de rede do seu app por SSL.
- Crie uma configuração de segurança de rede, que permite que seu app confie em autoridades de certificação (CAs, na sigla em inglês) personalizadas ou restrinja o conjunto de CAs do sistema em que ele confia para uma comunicação segura.
Para mais informações sobre como aplicar princípios de rede seguros, consulte dicas de segurança na rede.
Escolher um cliente HTTP
A maioria dos apps Android conectados à rede usa HTTP para enviar e receber dados. A plataforma
Android inclui o cliente
HttpsURLConnection
,
que oferece suporte à TLS, uploads e downloads em streaming, tempos limite configuráveis,
IPv6 e pooling de conexão.
Também estão disponíveis bibliotecas de terceiros que oferecem APIs de nível superior para operações de rede. Elas oferecem suporte a vários recursos de conveniência, como a serialização de corpos de solicitação e a desserialização de corpos de resposta.
- Retrofit (link em inglês): um cliente HTTP de tipo seguro para a JVM do Square, criado com base no OkHttp. A Retrofit permite criar uma interface do cliente de maneira declarativa e oferece suporte a várias bibliotecas de serialização.
- Ktor (link em inglês): um cliente HTTP da JetBrains, criado totalmente para Kotlin e com corrotinas. O Ktor oferece suporte a vários mecanismos, serializadores e plataformas.
Resolver consultas DNS
Os dispositivos com o Android 10 (nível 29 da API) e versões mais recentes têm suporte integrado para
buscas DNS especializadas com pesquisas de texto não criptografado e com o modo DNS via TLS.
A API DnsResolver
fornece resolução
assíncrona genérica, que permite que você pesquise por SRV
, NAPTR
e outros
tipos de registro. A análise da resposta é deixada para execução pelo app.
Em dispositivos com o Android 9 (nível 28 da API) e versões anteriores, o resolvedor
de DNS da plataforma é compatível apenas com registros A
e AAAA
. Isso permite pesquisar os endereços
IP associados a um nome, mas não é compatível com nenhum outro tipo de registro.
Para apps baseados em NDK, consulte android_res_nsend
.
Encapsular operações de rede com um repositório.
Para simplificar o processo de execução de operações de rede e reduzir a duplicação de código em várias partes do app, você pode usar o padrão de design do repositório. Um repositório é uma classe que processa operações de dados e fornece uma abstração limpa da API sobre alguns dados ou recursos específicos.
Você pode usar a Retrofit para declarar uma interface que especifica o método HTTP, o URL, os argumentos e o tipo de resposta para operações de rede, como no exemplo a seguir:
Kotlin
interface UserService { @GET("/users/{id}") suspend fun getUser(@Path("id") id: String): User }
Java
public interface UserService { @GET("/user/{id}") Call<User> getUserById(@Path("id") String id); }
Em uma classe de repositório, as funções podem encapsular operações de rede e expor os resultados. Esse encapsulamento garante que os componentes que chamam o repositório não precisem saber como os dados são armazenados. As mudanças futuras na forma como os dados são armazenados também são isoladas na classe do repositório. Por exemplo, você pode ter uma mudança remota, como uma atualização de endpoints de API, ou pode implementar um armazenamento em cache local.
Kotlin
class UserRepository constructor( private val userService: UserService ) { suspend fun getUserById(id: String): User { return userService.getUser(id) } }
Java
class UserRepository { private UserService userService; public UserRepository( UserService userService ) { this.userService = userService; } public Call<User> getUserById(String id) { return userService.getUser(id); } }
Para evitar a criação de uma interface não responsiva, não execute operações de rede na
linha de execução principal. Por padrão, o Android exige que você execute operações de rede em uma
linha de execução diferente da linha de execução de interface principal. Se você tentar executar operações de rede
na linha de execução principal, uma
NetworkOnMainThreadException
será gerada.
No exemplo de código anterior, a
operação de rede não é realmente acionada. O autor da chamada do UserRepository
precisa implementar as linhas de execução usando corrotinas ou a função
enqueue()
. Para mais informações, consulte o codelab Receber dados da
Internet,
que demonstra como implementar linhas de execução usando corrotinas do Kotlin.
Sobreviver às alterações de configuração
Quando ocorre uma mudança de configuração, como uma rotação de tela, o fragmento, ou a atividade, é destruído e recriado. Todos os dados que não estiverem salvos no estado da instância para a atividade do fragmento, que precisam conter apenas pequenas quantidades de dados, são perdidos. Se isso acontecer, talvez será necessário fazer as solicitações de rede novamente.
Você pode usar um ViewModel
para permitir
que seus dados sobrevivam a mudanças na configuração. O componente ViewModel
foi
projetado para armazenar e gerenciar dados relacionados à interface considerando o ciclo de
vida. Com o UserRepository
acima, o ViewModel
pode fazer as
solicitações de rede necessárias e fornecer o resultado para seu fragmento ou atividade
usando LiveData
:
Kotlin
class MainViewModel constructor( savedStateHandle: SavedStateHandle, userRepository: UserRepository ) : ViewModel() { private val userId: String = savedStateHandle["uid"] ?: throw IllegalArgumentException("Missing user ID") private val _user = MutableLiveData<User>() val user = _user as LiveData<User> init { viewModelScope.launch { try { // Calling the repository is safe as it moves execution off // the main thread val user = userRepository.getUserById(userId) _user.value = user } catch (error: Exception) { // Show error message to user } } } }
Java
class MainViewModel extends ViewModel { private final MutableLiveData<User> _user = new MutableLiveData<>(); LiveData<User> user = (LiveData<User>) _user; public MainViewModel( SavedStateHandle savedStateHandle, UserRepository userRepository ) { String userId = savedStateHandle.get("uid"); Call<User> userCall = userRepository.getUserById(userId); userCall.enqueue(new Callback<User>() { @Override public void onResponse(Call<User> call, Response<User> response) { if (response.isSuccessful()) { _user.setValue(response.body()); } } @Override public void onFailure(Call<User> call, Throwable t) { // Show error message to user } }); } }
Guias relacionados
Para saber mais sobre este tópico, consulte estes guias relacionados:
- Reduzir consumo de bateria por rede: visão geral
- Minimizar o efeito de atualizações regulares
- Conteúdo na Web
- Fundamentos de aplicativos
- Guia para a arquitetura do app