Um Netzwerkvorgänge in Ihrer Anwendung ausführen zu können, muss Ihr Manifest die folgenden Berechtigungen enthalten:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Best Practices für sichere Netzwerkkommunikation
Bevor Sie Ihrer App Netzwerkfunktionen hinzufügen, müssen Sie dafür sorgen, dass Daten und Informationen in Ihrer App bei der Übertragung über ein Netzwerk sicher bleiben. Beachten Sie dazu die folgenden Best Practices für die Netzwerksicherheit:
- Minimieren Sie die Menge an vertraulichen oder personenbezogenen Nutzerdaten, die Sie über das Netzwerk übertragen.
- Senden Sie den gesamten Netzwerk-Traffic Ihrer App über SSL.
- Erwägen Sie, eine Netzwerksicherheitskonfiguration zu erstellen, mit der Ihre App benutzerdefinierten Zertifizierungsstellen (CAs) vertrauen oder die Gruppe der System-CAs einschränken kann, denen sie für die sichere Kommunikation vertraut.
Weitere Informationen zum Anwenden von Prinzipien für sichere Netzwerke finden Sie in den Tipps zur Netzwerksicherheit.
HTTP-Client auswählen
Die meisten Apps, die mit dem Netzwerk verbunden sind, verwenden HTTP zum Senden und Empfangen von Daten. Die Android-Plattform enthält den HttpsURLConnection
-Client, der TLS, Streaming-Uploads und ‑Downloads, konfigurierbare Zeitüberschreitungen, IPv6 und Connection Pooling unterstützt.
Es sind auch Drittanbieterbibliotheken verfügbar, die APIs auf höherer Ebene für Netzwerkoperationen bieten. Sie unterstützen verschiedene Komfortfunktionen wie die Serialisierung von Anfrage- und die Deserialisierung von Antworttexten.
- Retrofit: Ein typsicherer HTTP-Client für die JVM von Square, der auf OkHttp basiert. Mit Retrofit können Sie eine Clientschnittstelle deklarativ erstellen. Außerdem werden mehrere Serialisierungsbibliotheken unterstützt.
- Ktor: Ein HTTP-Client von JetBrains, der vollständig für Kotlin entwickelt wurde und auf Coroutinen basiert. Ktor unterstützt verschiedene Engines, Serializer und Plattformen.
DNS-Abfragen auflösen
Geräte mit Android 10 (API-Level 29) und höher unterstützen spezielle DNS-Lookups sowohl über Cleartext-Lookups als auch im DNS-over-TLS-Modus.
Die DnsResolver
API bietet eine generische, asynchrone Auflösung, mit der Sie SRV
-, NAPTR
- und andere Datensatztypen abrufen können. Die Analyse der Antwort muss von der App durchgeführt werden.
Auf Geräten mit Android 9 (API-Level 28) und niedriger unterstützt der DNS-Resolver der Plattform nur A
- und AAAA
-Einträge. So können Sie die IP-Adressen, die einem Namen zugeordnet sind, nachschlagen. Andere Datensatztypen werden jedoch nicht unterstützt.
Informationen zu NDK-basierten Apps finden Sie unter android_res_nsend
.
Netzwerkvorgänge mit einem Repository kapseln
Um die Durchführung von Netzwerkoperationen zu vereinfachen und Codeduplikate in verschiedenen Teilen Ihrer App zu vermeiden, können Sie das Repository-Designmuster verwenden. Ein Repository ist eine Klasse, die Datenvorgänge verarbeitet und eine saubere API-Abstraktion für bestimmte Daten oder Ressourcen bietet.
Mit Retrofit können Sie eine Schnittstelle deklarieren, die die HTTP-Methode, URL, Argumente und den Antworttyp für Netzwerkoperationen angibt, wie im folgenden Beispiel:
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); }
In einer Repository-Klasse können Funktionen Netzwerkoperationen kapseln und ihre Ergebnisse verfügbar machen. Durch diese Kapselung müssen die Komponenten, die das Repository aufrufen, nicht wissen, wie die Daten gespeichert werden. Alle zukünftigen Änderungen an der Art und Weise, wie die Daten gespeichert werden, sind ebenfalls auf die Repository-Klasse beschränkt. Möglicherweise haben Sie eine Remote-Änderung, z. B. eine Aktualisierung der API-Endpunkte, oder Sie möchten lokales Caching implementieren.
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); } }
Um eine nicht reagierende Benutzeroberfläche zu vermeiden, sollten Sie keine Netzwerkoperationen im Hauptthread ausführen. Standardmäßig müssen Sie in Android Netzwerkoperationen in einem anderen Thread als dem Haupt-UI-Thread ausführen. Wenn Sie versuchen, Netzwerkoperationen im Hauptthread auszuführen, wird eine NetworkOnMainThreadException
ausgelöst.
Im vorherigen Codebeispiel wird der Netzwerkbetrieb nicht tatsächlich ausgelöst. Der Aufrufer von UserRepository
muss die Threading-Funktion entweder mit Coroutinen oder mit der Funktion enqueue()
implementieren. Weitere Informationen finden Sie im Codelab Daten aus dem Internet abrufen, in dem gezeigt wird, wie Threading mit Kotlin-Coroutinen implementiert wird.
Konfigurationsänderungen überstehen
Wenn eine Konfigurationsänderung erfolgt, z. B. eine Bildschirmdrehung, wird Ihr Fragment oder Ihre Aktivität zerstört und neu erstellt. Alle Daten, die nicht im Instanzstatus für Ihre Fragmentaktivität gespeichert sind, gehen verloren. Der Instanzstatus kann nur kleine Datenmengen enthalten. In diesem Fall müssen Sie Ihre Netzwerkanfragen möglicherweise noch einmal senden.
Mit einem ViewModel
können Sie dafür sorgen, dass Ihre Daten Konfigurationsänderungen überstehen. Die ViewModel
-Komponente wurde entwickelt, um UI-bezogene Daten auf lebenszyklusbewusste Weise zu speichern und zu verwalten. Mit dem vorangegangenen UserRepository
kann das ViewModel
die erforderlichen Netzwerkanfragen stellen und das Ergebnis über LiveData
an Ihr Fragment oder Ihre Aktivität zurückgeben:
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 } }); } }
Zugehörige Leitfäden lesen
Weitere Informationen zu diesem Thema finden Sie in den folgenden zugehörigen Leitfäden:
- Netzwerk-Akkuverbrauch verringern: Übersicht
- Auswirkungen regelmäßiger Updates minimieren
- Webbasierte Inhalte
- Grundlagen der Anwendung
- Leitfaden zur App-Architektur