Połącz z siecią

Aby można było wykonywać operacje sieciowe w aplikacji, plik manifestu musi obejmować 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 swojej aplikacji funkcje sieciowe, upewnij się, że dane i informacje w aplikacji są bezpieczne podczas przesyłania przez sieć. W tym celu postępuj zgodnie z tymi sprawdzonymi metodami zapewniania bezpieczeństwa sieci:

  • Ogranicz ilość poufnych lub osobistych danych użytkownika przesyłanych przez sieć.
  • Wysyłaj cały ruch w sieci z aplikacji przez SSL.
  • Rozważ utworzenie konfiguracji zabezpieczeń sieci, która pozwoli aplikacji ufać niestandardowych urzędom certyfikacji (CA) lub ograniczyć zestaw urzędów certyfikacji, którym ufa w ramach bezpiecznej komunikacji.

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 Android obejmuje klienta HttpsURLConnection, który obsługuje protokół TLS, przesyłanie strumieniowe i pobieranie danych, konfigurowalne limity czasu, IPv6 i pula połączeń.

Dostępne są też biblioteki zewnętrzne, które oferują interfejsy API wyższego poziomu do operacji sieciowych. Obsługują one różne udogodnienia, takie jak serializacja treści żądań i deserializacja treści odpowiedzi.

  • Retrofit: bezpieczny klient HTTP dla JVM z Square, zbudowany na bazie OkHttp. Funkcja Retrofit umożliwia deklaratywne utworzenie interfejsu klienta i obsługuje kilka bibliotek serializacji.
  • Ktor: klient HTTP od JetBrains zaprojektowany w całości dla Kotlin i obsługiwany przez procesory. Ktor obsługuje różne silniki, serializery 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ólną, asynchroniczną rozdzielczość, która umożliwia wyszukiwanie rekordów w rekordach SRV, NAPTR i innych. Odpowiedź zostanie przetworzona przez aplikację.

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym mechanizm rozpoznawania nazw 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.

Informacje na temat aplikacji opartych na pakiecie NDK znajdziesz tutaj: android_res_nsend.

Herbaty operacji sieciowych za pomocą repozytorium

Aby uprościć proces 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 przejrzystą abstrakcję interfejsu API w przypadku niektórych 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ą ujmować operacje sieciowe i udostępniać ich wyniki. Dzięki temu komponenty, które wywołują repozytorium, nie muszą wiedzieć, jak przechowywane są dane. Wszystkie przyszłe zmiany sposobu przechowywania danych będą izolowane również od klasy repozytorium. Może to być na przykład zdalna zmiana, taka jak aktualizacja punktów końcowych interfejsu API, lub wdrożenie lokalnego buforowania.

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 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 UI. Próba wykonania operacji sieciowych w wątku głównym powoduje zgłoszenie błędu NetworkOnMainThreadException.

W poprzednim przykładzie kodu operacja sieciowa nie jest aktywowana. Element wywołujący UserRepository musi wdrożyć podział na wątki za pomocą współprogramów lub funkcji enqueue(). Więcej informacji znajdziesz w ćwiczeniach z programowania Pobieranie danych z internetu, które pokazują, jak wdrożyć podział na wątki za pomocą współprogramów Kotlin.

Przetrwaj zmiany konfiguracji

Gdy nastąpi zmiana konfiguracji, np. obrót ekranu, fragment lub aktywność jest niszczona i odtwarzana. Wszystkie dane, które nie zostały zapisane w stanie instancji związane z aktywnością związaną z fragmentem, które mogą zawierać tylko niewielkie ilości danych, zostaną utracone. W takim przypadku być może trzeba będzie ponownie wysłać żądania sieciowe.

Aby dane pozostawały bez zmian w konfiguracji, możesz użyć właściwości ViewModel. Komponent ViewModel służy do przechowywania danych związanych z interfejsem i zarządzania nimi w sposób uwzględniający cykl życia. Korzystając z poprzedniej funkcji UserRepository, ViewModel może wysyłać niezbędne żądania sieciowe i przekazywać wyniki 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 można znaleźć w następujących powiązanych przewodnikach: