Verbindung zum Netzwerk herstellen

Damit Netzwerkvorgänge in Ihrer Anwendung ausgeführt werden 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 die 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 geschützt bleiben. Beachten Sie dazu die folgenden Best Practices für die Netzwerksicherheit:

  • Minimieren Sie die Menge an sensiblen oder personenbezogenen Nutzerdaten, die Sie über das Netzwerk übertragen.
  • Senden Sie den gesamten Netzwerkverkehr von Ihrer App über SSL.
  • Sie können eine Netzwerksicherheitskonfiguration erstellen, mit der Ihre App benutzerdefinierten Zertifizierungsstellen vertrauen oder die Anzahl der System-Zertifizierungsstellen einschränken kann, die für die sichere Kommunikation verwendet werden.

Weitere Informationen zum Anwenden von Prinzipien für sichere Netzwerke finden Sie in den Tipps zur Netzwerksicherheit.

HTTP-Client auswählen

Die meisten netzwerkverbundenen Apps 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 Verbindungs-Pooling unterstützt.

Es sind auch Bibliotheken von Drittanbietern verfügbar, die APIs auf höherer Ebene für Netzwerkfunktionen bieten. Sie unterstützen verschiedene praktische Funktionen 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 Clientoberfläche deklarativ erstellen und es unterstützt mehrere Serialization-Bibliotheken.
  • Ktor: Ein HTTP-Client von JetBrains, der vollständig für Kotlin entwickelt wurde und auf coroutines 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-Suchanfragen sowohl über Klartextabfragen als auch über den DNS-over-TLS-Modus. Die DnsResolver API bietet eine generische, asynchrone Auflösung, mit der Sie SRV-, NAPTR- und andere Datensatztypen abrufen können. Das Parsen der Antwort wird der App überlassen.

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 abrufen, die mit einem Namen verknüpft sind. Es werden jedoch keine anderen Datensatztypen unterstützt.

Informationen zu NDK-basierten Apps finden Sie unter android_res_nsend.

Netzwerkvorgänge in einem Repository kapseln

Mit dem Repository-Designmuster können Sie Netzwerkvorgänge vereinfachen und die Codeduplizierung in verschiedenen Teilen Ihrer App reduzieren. 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, die URL, die Argumente und den Antworttyp für Netzwerkvorgänge 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);
}

Innerhalb einer Repository-Klasse können Funktionen Netzwerkvorgänge kapseln und ihre Ergebnisse bereitstellen. Durch diese Kapselung müssen die Komponenten, die das Repository aufrufen, nicht wissen, wie die Daten gespeichert werden. Alle zukünftigen Änderungen an der Speicherung der Daten sind ebenfalls auf die Repository-Klasse beschränkt. Möglicherweise gibt es beispielsweise eine Remote-Änderung wie eine Aktualisierung von API-Endpunkten 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);
    }
}

Führen Sie keine Netzwerkvorgänge im Hauptthread aus, um eine nicht reagierende Benutzeroberfläche zu vermeiden. Standardmäßig müssen Sie Netzwerkvorgänge unter Android in einem anderen Thread als dem Haupt-UI-Thread ausführen. Wenn Sie versuchen, Netzwerkvorgänge im Hauptthread auszuführen, wird eine NetworkOnMainThreadException geworfen.

Im vorherigen Codebeispiel wird der Netzwerkvorgang nicht ausgelöst. Der Aufrufer von UserRepository muss das Threading entweder mithilfe von coroutines oder mithilfe 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.

Bei Konfigurationsänderungen bestehen

Wenn eine Konfigurationsänderung auftritt, z. B. eine Bildschirmdrehung, wird das Fragment oder die Aktivität zerstört und neu erstellt. Alle Daten, die nicht im Instanzstatus für Ihre Fragmentaktivität gespeichert sind, gehen verloren. Dieser Status kann nur kleine Datenmengen aufnehmen. 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 bei Konfigurationsänderungen erhalten bleiben. Die ViewModel-Komponente wurde entwickelt, um UI-bezogene Daten auf nutzungsorientierte Weise zu speichern und zu verwalten. Anhand der vorherigen UserRepository kann die ViewModel die erforderlichen Netzwerkanfragen stellen und das Ergebnis mithilfe von LiveData an Ihr Fragment oder Ihre Aktivität weitergeben:

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
            }
        });
    }
}

Weitere Informationen zu diesem Thema finden Sie in den folgenden Leitfäden: