Połącz z siecią

Aby aplikacja mogła wykonywać operacje sieciowe, manifest musi zawierać te uprawnienia:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Sprawdzone metody dotyczące bezpiecznej komunikacji w sieci

Zanim dodasz do aplikacji funkcje sieciowe, musisz się upewnić, że dane i informacje w aplikacji są bezpieczne podczas przesyłania przez sieć. Aby to zrobić, postępuj zgodnie z tymi sprawdzonymi metodami dotyczącymi bezpieczeństwa sieci:

  • Zminimalizuj ilość danych użytkownika, które są wrażliwe lub osobowe, i przesyłane przez sieć.
  • Przesyłaj cały ruch sieciowy z aplikacji przez SSL.
  • Utwórz konfigurację zabezpieczeń sieci, która pozwoli Twojej aplikacji ufać niestandardowym urzędom certyfikacji (CA) lub ograniczyć zestaw urzędów CA, które są zaufane do bezpiecznej komunikacji.

Więcej informacji o stosowaniu zasad bezpiecznego korzystania z sieci znajdziesz w wskazówkach dotyczących bezpieczeństwa sieci.

Wybieranie klienta HTTP

Większość aplikacji połączonych z internetem do wysyłania i odbierania danych korzysta z protokołu HTTP. Platforma Android zawiera klienta HttpsURLConnection, który obsługuje TLS, przesyłanie i pobieranie strumieniowe, konfigurowalne limity czasu, IPv6 oraz zespół połączeń.

Dostępne są też biblioteki innych firm, które oferują interfejsy API wyższego poziomu do operacji sieciowych. Obsługują one różne funkcje ułatwiające pracę, takie jak serializacja treści żądania i deserializacja treści odpowiedzi.

  • Retrofit: bezpieczny pod względem typów klient HTTP dla JVM od Square, zbudowany na podstawie OkHttp. Retrofit umożliwia deklaratywnie tworzenie interfejsu klienta i obsługuje kilka bibliotek serializacji.
  • Ktor: klient HTTP od JetBrains, napisany całkowicie w Kotlinie i wykorzystujący coroutines. Ktor obsługuje różne silniki, serializatory i platformy.

Rozwiązywanie zapytań DNS

Urządzenia z Androidem 10 (poziom interfejsu API 29) i nowszym mają wbudowaną obsługę wyspecjalizowanych wyszukiwań DNS zarówno w trybie tekstowym, jak i w trybie DNS-over-TLS. Interfejs API DnsResolver zapewnia ogólne, asynchroniczne rozwiązanie, które umożliwia wyszukiwanie rekordów typu SRVNAPTR oraz innych typów rekordów. Analizowanie odpowiedzi pozostawiono aplikacji.

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym platformowy serwer DNS obsługuje tylko rekordy AAAAA. Umożliwia wyszukiwanie adresów IP powiązanych z nazwą, ale nie obsługuje innych typów rekordów.

Informacje o aplikacjach opartych na NDK znajdziesz w artykule android_res_nsend.

Opakowanie operacji sieciowych w repozytorium

Aby uprościć proces wykonywania operacji sieciowych i zmniejszyć duplikację kodu w różnych częściach aplikacji, możesz użyć wzorca repozytorium. Repozytorium to klasa, która obsługuje operacje na danych i zapewnia czystą abstrakcję interfejsu API dla określonych danych lub zasobów.

Za pomocą Retrofit możesz zadeklarować interfejs, który określa metodę HTTP, adres URL, argumenty i typ odpowiedzi dla operacji sieciowych, jak w tym przykładzie:

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);
}

W klasie repozytorium funkcje mogą otaczać operacje sieciowe i wyświetlać ich wyniki. Dzięki temu komponenty wywołujące repozytorium nie muszą wiedzieć, jak są przechowywane dane. Wszelkie przyszłe zmiany sposobu przechowywania danych są również izolowane w klasie repozytorium. Możesz na przykład wprowadzić zmianę zdalna, np. zaktualizować punkty końcowe interfejsu API, lub wdrożyć lokalne buforowanie.

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);
    }
}

Aby uniknąć tworzenia interfejsu użytkownika, który nie reaguje, nie wykonuj operacji sieciowych w wątku głównym. Domyślnie Android wymaga, aby operacje sieciowe były wykonywane w wątku innym niż główny wątek interfejsu użytkownika. Jeśli spróbujesz wykonać operacje sieciowe w wątku głównym, zostanie zgłoszony wyjątek NetworkOnMainThreadException.

W poprzednim przykładzie kodu operacja sieci nie jest faktycznie wywoływana. Wywołujący UserRepository musi zaimplementować wątki za pomocą coroutines lub funkcji enqueue(). Więcej informacji znajdziesz w laboratorium kodu Pobieranie danych z internetu, w którym pokazano, jak zaimplementować wątki za pomocą coroutines w Kotlinie.

Przetrwanie zmian konfiguracji

Gdy nastąpi zmiana konfiguracji, np. obrót ekranu, fragment lub aktywność zostaną zniszczone i ponownie utworzone. Wszystkie dane, które nie zostały zapisane w stanie instancji aktywności fragmentu (może on zawierać tylko niewielkie ilości danych), zostaną utracone. W takim przypadku może być konieczne ponowne wysłanie żądań do sieci.

Możesz użyć ViewModel, aby dane przetrwały zmiany konfiguracji. Komponent ViewModel został zaprojektowany w taki sposób, aby przechowywać dane związane z interfejsem użytkownika i nimi zarządzać w sposób uwzględniający cykl życia. Korzystając z poprzedniej funkcji UserRepository, element ViewModel może wysyłać niezbędne żądania sieciowe i przekazywać wyniki do fragmentu lub aktywności za pomocą funkcji 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
            }
        });
    }
}

Aby dowiedzieć się więcej na ten temat, zapoznaj się z tymi przewodnikami: