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:
- 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.
- 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.
Wybierz 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ą 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 SRV
i NAPTR
oraz innych typów rekordów. Analizowanie odpowiedzi pozostawiono 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
.
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.
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 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 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.
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ń do sieci.
Możesz użyć ViewModel
, aby dane przetrwały zmiany konfiguracji. 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 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 } }); } }
Czytanie powiązanych przewodników
Aby dowiedzieć się więcej na ten temat, zapoznaj się z tymi przewodnikami:
- Zmniejszanie obciążenia baterii sieci: omówienie
- Zminimalizuj wpływ regularnych aktualizacji
- Treści internetowe
- Podstawy tworzenia aplikacji
- Przewodnik po architekturze aplikacji