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 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 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 } }); } }
Przeczytaj powiązane przewodniki
Aby dowiedzieć się więcej na ten temat, zapoznaj się z tymi przewodnikami:
- Zmniejszanie obciążenia baterii przez sieć: omówienie
- Minimalizowanie wpływu regularnych aktualizacji
- treści internetowe,
- Podstawy aplikacji
- Przewodnik po architekturze aplikacji