Connettiti alla rete

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 una comunicazione di rete sicura

Prima di aggiungere la funzionalità di rete all'app, devi assicurarti che i dati e le informazioni all'interno dell'app rimangano al sicuro durante la trasmissione su una rete. Per farlo, segui queste best practice per la sicurezza della 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 della sicurezza di rete, che consente alla tua app di considerare attendibili le autorità di certificazione (CA) personalizzate o limitare l'insieme di CA di sistema che considera attendibili per le comunicazioni sicure.

Per ulteriori informazioni su come applicare i principi di una rete sicura, consulta i suggerimenti per la sicurezza del networking.

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, flussi di caricamenti e download, timeout configurabili, IPv6 e pooling di connessioni.

Sono inoltre disponibili librerie di terze parti che offrono API di livello superiore per le operazioni di networking. Queste supportano varie funzionalità pratiche, come la serializzazione dei corpi di richiesta e la deserializzazione dei corpi di risposta.

  • Retrofit: un client HTTP sicuro per il tipo di connessione per la JVM da Square, basato su OkHttp. Retrofit consente di creare un'interfaccia client in modo dichiarativo e supporta diverse librerie di serializzazione.
  • Ktor: un client HTTP di JetBrains, realizzato interamente per Kotlin e basato su coroutine. Ktor supporta vari motori, serializzatori e piattaforme.

Risolvi le query DNS

I dispositivi che eseguono Android 10 (livello API 29) e versioni successive hanno il supporto integrato per ricerche DNS specializzate tramite ricerche in chiaro 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 viene lasciata all'app per eseguire l'analisi.

Sui dispositivi che eseguono Android 9 (livello API 28) e versioni precedenti, il risolutore 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, vedi 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 dell'API pulita 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 ed esporre i risultati. Questa incapsulamento garantisce che i componenti che chiamano il repository non debbano sapere come sono archiviati i dati. Eventuali modifiche future alle modalità di archiviazione dei dati vengono isolate anche nella classe del repository. Ad esempio, potresti avere una modifica remota, come un aggiornamento degli endpoint API, o 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'UI principale. Se provi a eseguire operazioni di rete sul thread principale, viene restituito un NetworkOnMainThreadException.

Nell'esempio di codice precedente, l'operazione di rete non viene 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 illustra come implementare il threading utilizzando le coroutine Kotlin.

Sopravvivi alle modifiche di configurazione

Quando si verifica una modifica alla configurazione, ad esempio una rotazione dello schermo, il frammento o l'attività vengono eliminati e ricreati. Tutti i dati non salvati nello stato dell'istanza per l'attività dei frammenti, che possono contenere solo piccole quantità di dati, vengono persi. In questo caso, potrebbe essere necessario effettuare di nuovo le richieste di rete.

Puoi utilizzare una ViewModel per fare in modo che i dati rimangano invariati dopo le modifiche di 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
            }
        });
    }
}

Per saperne di più su questo argomento, consulta le seguenti guide correlate: