Połącz z siecią

Aby wykonywać operacje sieciowe w aplikacji, musisz dodać do pliku manifestu 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 zadbać o bezpieczeństwo danych i informacji w aplikacji podczas przesyłania ich przez sieć. Aby to zrobić, postępuj zgodnie z tymi sprawdzonymi metodami dotyczącymi bezpieczeństwa sieci:

  • Minimalizuj 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 umożliwia aplikacji zaufanie niestandardowym urzędom certyfikacji lub ograniczenie zestawu systemowych urzędów certyfikacji, którym ufa w przypadku bezpiecznej komunikacji.

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

Wybieranie klienta HTTP

Większość aplikacji połączonych z siecią używa protokołu HTTP do wysyłania i odbierania danych. Platforma Android zawiera klienta HttpsURLConnection, który obsługuje TLS, przesyłanie strumieniowe, konfigurowalne limity czasu, IPv6 i pule 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 przydatne funkcje, takie jak serializacja treści żądań i deserializacja treści odpowiedzi.

  • Retrofit: bezpieczny pod względem typów klient HTTP dla JVM od Square, oparty na OkHttp. Retrofit umożliwia deklaratywne tworzenie interfejsu klienta i obsługuje kilka bibliotek serializacji.
  • Ktor: klient HTTP od JetBrains, stworzony w całości w języku Kotlin i oparty na korutynach. 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ę specjalistycznych wyszukiwań DNS zarówno w trybie tekstu nieszyfrowanego, jak i w trybie DNS-over-TLS. Interfejs DnsResolver API zapewnia ogólne, asynchroniczne rozwiązywanie problemów, które umożliwia wyszukiwanie SRV, NAPTR i innych typów rekordów. Analiza odpowiedzi należy do aplikacji.

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym resolver DNS platformy obsługuje tylko rekordy AAAAA. 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 zobacz android_res_nsend.

Hermetyzacja operacji sieciowych za pomocą repozytorium

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

Za pomocą biblioteki 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ą obejmować operacje sieciowe i udostępniać ich wyniki. Ta hermetyzacja sprawia, że komponenty wywołujące repozytorium nie muszą wiedzieć, jak są przechowywane dane. Wszelkie przyszłe zmiany w sposobie przechowywania danych będą również ograniczone do klasy repozytorium. Możesz na przykład wprowadzić zdalną zmianę, taką jak aktualizacja punktów końcowych interfejsu API, lub zaimplementować 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 odpowiada, nie wykonuj operacji sieciowych w głównym wątku. Domyślnie Android wymaga wykonywania operacji sieciowych w wątku innym niż główny wątek UI. Jeśli spróbujesz wykonać operacje sieciowe w wątku głównym, zostanie zgłoszony wyjątek NetworkOnMainThreadException.

W poprzednim przykładzie kodu operacja sieciowa nie jest faktycznie wywoływana. Element wywołujący funkcję UserRepository musi zaimplementować wątki za pomocą korutyn lub funkcji enqueue(). Więcej informacji znajdziesz w samouczku Pobieranie danych z internetu, w którym pokazujemy, jak zaimplementować wątki za pomocą współprogramów Kotlin.

Przetrwanie zmian konfiguracji

Gdy nastąpi zmiana konfiguracji, np. obrócenie ekranu, fragment lub aktywność zostaną zniszczone i utworzone ponownie. Wszystkie dane niezapisane w stanie instancji aktywności fragmentu, która może przechowywać tylko niewielkie ilości danych, zostaną utracone. W takim przypadku może być konieczne ponowne wysłanie żądań sieciowych.

Możesz użyć ViewModel, aby dane przetrwały zmiany konfiguracji. 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 podanego wyżej UserRepository, ViewModel może wysyłać niezbędne żądania sieciowe i przekazywać wyniki do fragmentu lub aktywności 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
            }
        });
    }
}

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