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 ze sprawdzonymi metodami zapewniania bezpieczeństwa sieci:

  • Ogranicz do minimum ilość poufnych lub osobistych danych użytkowników przesyłanych przez sieć.
  • Przesyłaj cały ruch sieciowy z aplikacji przez SSL.
  • Rozważ utworzenie konfiguracji 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 siecią do wysyłania i odbierania danych używa protokołu HTTP. Platforma Androida zawiera klienta HttpsURLConnection, który obsługuje TLS, przesyłanie i pobieranie strumieniowe, konfigurowalne limity czasu, IPv6 oraz zespół połączeń.

Dostępne są również biblioteki zewnętrzne, które oferują interfejsy API wyższego poziomu na potrzeby 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) lub nowszym mają wbudowaną obsługę specjalistycznych wyszukiwań DNS z wykorzystaniem zarówno wyszukiwania zwykłego tekstu, jak i trybu 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. Analiza odpowiedzi jest pozostawiana do wykonania aplikacji.

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym moduł rozpoznawania DNS platformy obsługuje tylko rekordy A i AAAA. Umożliwia to wyszukiwanie adresów IP powiązanych z nazwą, ale nie obsługuje innych typów rekordów.

W przypadku aplikacji opartych na NDK otwórz android_res_nsend.

Opakowanie operacji sieciowych w repozytorium

Aby uprościć proces wykonywania operacji sieciowych i ograniczyć duplikowanie kodu w różnych częściach aplikacji, możesz użyć wzorca projektu 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ąć utworzenia interfejsu, który nie odpowiada, nie wykonuj operacji sieciowych w wątku głównym. Domyślnie Android wymaga wykonywania operacji sieciowych w wątku innym niż główny wątek interfejsu. Jeśli spróbujesz wykonać operacje sieciowe w wątku głównym, zostanie rzucony 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 wystąpi zmiana konfiguracji, np. obrót ekranu, Twój fragment lub działanie zostaną zniszczone i odtworzone. Wszystkie dane dotyczące aktywności związanej z fragmentami, które nie są zapisane w stanie instancji, 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 zarządzać nimi 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 czynnoś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: