Per eseguire operazioni di rete nella tua applicazione, il manifest deve includere le seguenti autorizzazioni:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Best practice per la comunicazione di rete sicura
Prima di aggiungere funzionalità di rete alla tua app, devi assicurarti che i dati e le informazioni al suo interno rimangano al sicuro durante la trasmissione su una rete. Per farlo, segui queste best practice per la sicurezza di rete:
- Riduci al minimo la quantità di dati utente sensibili o personali che trasmetti sulla rete.
- Invia tutto il traffico di rete dalla tua app tramite SSL.
- Valuta la possibilità di creare una configurazione di sicurezza di rete che consenta alla tua app di considerare attendibili autorità di certificazione (CA) personalizzate o di limitare l'insieme di CA di sistema considerate attendibili per la comunicazione sicura.
Per saperne di più su come applicare i principi di una rete sicura, consulta i suggerimenti per la sicurezza della rete.
Scegli un client HTTP
La maggior parte delle app connesse alla rete utilizza HTTP per inviare e ricevere dati. La piattaforma Android include il client HttpsURLConnection
, che supporta TLS, caricamenti e download in streaming, timeout configurabili, IPv6 e il pooling delle connessioni.
Sono disponibili anche librerie di terze parti che offrono API di livello superiore per le operazioni di rete. Supportano varie funzionalità di praticità, come la serializzazione dei campi della richiesta e la deserializzazione dei campi della risposta.
- Retrofit: un client HTTP per la JVM di Square, basato su OkHttp, che garantisce la sicurezza del tipo. Retrofit ti consente di creare un'interfaccia client in modo dichiarativo e supporta diverse librerie di serializzazione.
- Ktor: un client HTTP di JetBrains, sviluppato interamente per Kotlin e basato su coroutine. Ktor supporta vari motori, serializzatori e piattaforme.
Risolvere le query DNS
I dispositivi con Android 10 (livello API 29) e versioni successive hanno il supporto integrato per le ricerche DNS specializzate tramite ricerche in testo non cifrato e modalità DNS over TLS.
L'API DnsResolver
offre una risoluzione generica e asincrona, che consente di cercare SRV
, NAPTR
e altri tipi di record. L'analisi della risposta è lasciata all'app.
Sui dispositivi con Android 9 (livello API 28) e versioni precedenti, il resolver DNS della piattaforma supporta solo i record A
e AAAA
. In questo modo puoi cercare gli indirizzi IP associati a un nome, ma non supporta altri tipi di record.
Per le app basate su NDK, consulta
android_res_nsend
.
Incapsulare le operazioni di rete con un repository
Per semplificare il processo di esecuzione delle operazioni di rete e ridurre la duplicazione di codice in varie parti dell'app, puoi utilizzare il pattern di progettazione del repository. Un repository è una classe che gestisce le operazioni sui dati e fornisce un'astrazione API chiara su alcuni dati o risorse specifici.
Puoi utilizzare Retrofit per dichiarare un'interfaccia che specifica il metodo HTTP, l'URL, gli argomenti e il tipo di risposta per le operazioni di rete, come nell'esempio seguente:
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); }
All'interno di una classe di repository, le funzioni possono incapsulare le operazioni di rete e esporre i relativi risultati. Questa incapsulazione garantisce che i componenti che richiamano il repository non debbano sapere come vengono archiviati i dati. Eventuali modifiche future alla modalità di archiviazione dei dati vengono isolate anche per la classe del repository. Ad esempio, potresti avere una modifica remota come un aggiornamento degli endpoint API o potresti voler implementare la memorizzazione nella cache locale.
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); } }
Per evitare di creare una UI che non risponde, non eseguire operazioni di rete sul
thread principale. Per impostazione predefinita, Android richiede di eseguire operazioni di rete su un
thread diverso dal thread dell'interfaccia utente principale. Se provi a eseguire operazioni di rete
sul thread principale, viene generato un
NetworkOnMainThreadException
.
Nell'esempio di codice precedente, l'operazione di rete non viene effettivamente attivata. Il chiamante dell'UserRepository
deve implementare il thread utilizzando coroutine o la funzione
enqueue()
. Per ulteriori informazioni, consulta il codelab Ottenere dati da
internet,
che mostra come implementare il threading utilizzando le coroutine Kotlin.
Sopravvivere alle modifiche alla configurazione
Quando si verifica una modifica alla configurazione, ad esempio la rotazione dello schermo, il frammento o l'attività viene distrutto e ricreato. Tutti i dati non salvati nello stato dell'istanza per l'attività del frammento, che può contenere solo piccole quantità di dati, vengono persi. In questo caso, potrebbe essere necessario effettuare nuovamente le richieste di rete.
Puoi utilizzare un ViewModel
per fare in modo che i tuoi dati rimangano invariati in caso di modifiche alla configurazione. Il componente ViewModel
è progettato per archiviare e gestire i dati relativi all'interfaccia utente in modo consapevole del ciclo di vita. Utilizzando l'oggetto UserRepository
precedente, ViewModel
può effettuare
le richieste di rete necessarie e fornire il risultato al frammento o all'attività
utilizzando 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 } }); } }
Leggi le guide correlate
Per saperne di più su questo argomento, consulta le seguenti guide correlate:
- Ridurre il consumo della batteria di rete: panoramica
- Ridurre al minimo l'effetto degli aggiornamenti regolari
- Contenuti basati sul web
- Concetti fondamentali delle applicazioni
- Guida all'architettura dell'app