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 SRV
i NAPTR
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 } }); } }
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
- Minimalizowanie wpływu regularnych aktualizacji
- Treści internetowe
- Podstawy aplikacji
- Przewodnik po architekturze aplikacji