Połącz z siecią

Aby można było wykonywać operacje sieciowe w aplikacji, plik manifestu musi zawierać te uprawnienia:

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

Sprawdzone metody bezpiecznej komunikacji sieciowej

Zanim dodasz do aplikacji funkcje sieciowe, musisz upewnić się, że dane i informacje zawarte 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ć.
  • Wysyłaj cały ruch w sieci ze swojej aplikacji przez SSL.
  • Rozważ utworzenie konfiguracji zabezpieczeń sieci, dzięki której aplikacja będzie ufać niestandardowym urzędom certyfikacji (CA) lub ograniczyć zestaw zaufanych urzędów certyfikacji systemu, aby zapewnić bezpieczną komunikację.

Więcej informacji o stosowaniu zasad dotyczących bezpiecznych sieci znajdziesz we wskazówkach dotyczących bezpieczeństwa sieci.

Wybierz klienta HTTP

Większość aplikacji połączonych z siecią używa protokołu HTTP do wysyłania i odbierania danych. Platforma Androida zawiera klienta HttpsURLConnection, który obsługuje TLS, przesyłanie strumieniowe i pobieranie plików, konfigurowane limity czasu, protokół IPv6 i pulę 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 zwiększające wygodę, takie jak serializacja treści żądań i deserializacja treści odpowiedzi.

  • Retrofit: klient HTTP odpowiedni dla typu JVM firmy Square, oparty na OkHttp. Retrofit pozwala utworzyć interfejs klienta deklaratywnie i obsługuje kilka bibliotek serializacji.
  • Ktor: klient HTTP od JetBrains, stworzony w całości dla Kotlina i obsługiwany przez współrzędne. 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 udostępnia ogólną, asynchroniczną rozdzielczość, która umożliwia wyszukiwanie SRV, NAPTR i 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 daną nazwą, ale nie obsługuje żadnych innych typów rekordów.

W przypadku aplikacji opartych na NDK otwórz android_res_nsend.

Enkapuluj operacje sieciowe za pomocą 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 umożliwia czystą abstrakcję interfejsu API w odniesieniu do określonych danych lub zasobów.

Możesz użyć funkcji Retrofit, aby 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ą hermetyzować operacje sieciowe i prezentować ich wyniki. Dzięki temu komponenty, które wywołują repozytorium, nie muszą wiedzieć, jak są przechowywane dane. Wszelkie przyszłe zmiany w sposobie przechowywania danych będą również odizolowane od klasy repozytorium. Może na przykład zaistnieć zdalna zmiana, taka jak aktualizacja punktów końcowych interfejsu API, lub wdrożenie lokalnej pamięci podręcznej.

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 zgłoszony obiekt NetworkOnMainThreadException.

W poprzednim przykładzie kodu operacja sieciowa nie jest w rzeczywistości wywoływana. Wywołujący interfejs UserRepository musi wdrożyć wątki za pomocą współrzędnych lub funkcji enqueue(). Więcej informacji znajdziesz w ćwiczeniu w Codelabs dotyczącym pobierania danych z internetu, w którym pokazujemy, jak wdrożyć wątki za pomocą współrzędnych Kotlina.

Przetrwaj zmiany 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ń sieciowych.

Aby Twoje dane przetrwały zmiany konfiguracji, możesz użyć ViewModel. Komponent ViewModel został zaprojektowany do przechowywania danych związanych z interfejsem i zarządzania nimi w sposób uwzględniający cykl życia. Korzystając z poprzedniego elementu UserRepository, ViewModel może wysyłać niezbędne żądania sieciowe i dostarczać wynik do fragmentu lub działania za pomocą 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
            }
        });
    }
}

Więcej informacji na ten temat znajdziesz w tych powiązanych przewodnikach: