Aby można było wykonywać operacje sieciowe w aplikacji, plik manifestu musi zawierać 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 upewnić się, że dane i informacje zawarte 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ć.
- Wysyłaj cały ruch w sieci ze swojej aplikacji przez SSL.
- Rozważ utworzenie konfiguracji zabezpieczeń sieci, dzięki której aplikacja będzie ufać niestandardowym urzędom certyfikacji (CA) lub ograniczyć zestaw zaufanych urzędów certyfikacji systemu, aby zapewnić bezpieczną komunikację.
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 Androida zawiera klienta HttpsURLConnection
, który obsługuje TLS, przesyłanie strumieniowe i pobieranie plików, konfigurowane limity czasu, protokół IPv6 i pulę 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 zwiększające wygodę, takie jak serializacja treści żądań i deserializacja treści odpowiedzi.
- Retrofit: klient HTTP odpowiedni dla typu JVM firmy Square, oparty na OkHttp. Retrofit pozwala utworzyć interfejs klienta deklaratywnie i obsługuje kilka bibliotek serializacji.
- Ktor: klient HTTP od JetBrains, stworzony w całości dla Kotlina i obsługiwany przez współrzędne. 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
udostępnia ogólną, asynchroniczną rozdzielczość, która umożliwia wyszukiwanie SRV
, NAPTR
i 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 daną nazwą, ale nie obsługuje żadnych 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.
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ą hermetyzować operacje sieciowe i prezentować ich wyniki. Dzięki temu komponenty, które wywołują 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 zgłoszony obiekt NetworkOnMainThreadException
.
W poprzednim przykładzie kodu operacja sieciowa
nie jest w rzeczywistości wywoływana. Wywołujący interfejs UserRepository
musi wdrożyć wątki za pomocą współrzędnych lub funkcji enqueue()
. Więcej informacji znajdziesz w ćwiczeniu w Codelabs dotyczącym pobierania danych z internetu, w którym pokazujemy, jak wdrożyć wątki za pomocą współrzędnych Kotlina.
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ń sieciowych.
Aby Twoje dane przetrwały zmiany konfiguracji, możesz użyć ViewModel
. 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 poprzedniego elementu UserRepository
, ViewModel
może wysyłać niezbędne żądania sieciowe i dostarczać wynik 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 } }); } }
Przeczytaj powiązane przewodniki
Więcej informacji na ten temat znajdziesz w tych powiązanych przewodnikach:
- Zmniejszanie obciążenia baterii sieci: omówienie
- Zminimalizuj wpływ regularnych aktualizacji
- Treści internetowe
- Podstawy aplikacji
- Przewodnik po architekturze aplikacji