Verbindung zum Netzwerk herstellen

Wenn Sie Netzwerkvorgänge in Ihrer Anwendung ausführen möchten, 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 die 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 vertraulichen oder personenbezogenen Nutzerdaten, die Sie über das Netzwerk übertragen.
  • Senden Sie den gesamten Netzwerkverkehr Ihrer App über SSL.
  • Sie können eine Netzwerksicherheitskonfiguration 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 zur Anwendung von Prinzipien für sichere Netzwerke finden Sie in den Tipps zur Netzwerksicherheit.

HTTP-Client auswählen

Die meisten mit dem Netzwerk verbundenen Apps verwenden HTTP, um Daten zu senden und zu empfangen. Die Android-Plattform enthält den HttpsURLConnection-Client, der TLS, Streaming-Uploads und -Downloads, konfigurierbare Zeitlimits, IPv6 und Verbindungs-Pooling unterstützt.

Es sind auch Drittanbieterbibliotheken verfügbar, die übergeordnete APIs für Netzwerkvorgänge bieten. Diese unterstützen verschiedene Komfortfunktionen, z. B. die Serialisierung von Anfragetexten 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 Client-Schnittstelle 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 Koroutinen basiert. Ktor unterstützt verschiedene Engines, Serializer und Plattformen.

DNS-Abfragen auflösen

Geräte mit Android 10 (API-Level 29) und höher bieten integrierte Unterstützung für spezielle DNS-Suchvorgänge, sowohl über Klartext-Suchvorgänge als auch über den DNS-over-TLS-Modus. Die DnsResolver API bietet eine generische, asynchrone Auflösung, mit der Sie SRV, NAPTR und andere Eintragstypen abfragen können. Das Parsen 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 mit einem Namen verknüpften IP-Adressen nachschlagen. Andere Eintragstypen werden jedoch nicht unterstützt.

Informationen zu NDK-basierten Apps finden Sie unter android_res_nsend.

Netzwerkvorgänge mit einem Repository kapseln

Um die Ausführung von Netzwerkvorgängen zu vereinfachen und Codeduplikate in verschiedenen Teilen Ihrer App zu reduzieren, 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 bereitstellt.

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 verfügbar machen. Diese Kapselung sorgt dafür, dass die Komponenten, die das Repository aufrufen, nicht wissen müssen, wie die Daten gespeichert werden. Alle zukünftigen Änderungen an der Speicherung der Daten sind ebenfalls auf die Repository-Klasse beschränkt. Beispielsweise kann es eine Remote-Änderung wie ein Update von API-Endpunkten geben 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ühre keine Netzwerkvorgänge im Hauptthread aus, um zu vermeiden, dass die Benutzeroberfläche nicht mehr reagiert. Standardmäßig müssen Sie unter Android Netzwerkvorgänge in einem anderen Thread als dem Haupt-UI-Thread ausführen. Wenn Sie versuchen, Netzwerkvorgänge im Hauptthread auszuführen, wird eine NetworkOnMainThreadException ausgelöst.

Im vorherigen Codebeispiel wird der Netzwerkvorgang nicht tatsächlich ausgelöst. Der Aufrufer von UserRepository muss das Threading entweder mit Koroutinen oder mit der Funktion enqueue() implementieren. Weitere Informationen finden Sie im Codelab Daten aus dem Internet abrufen, in dem gezeigt wird, wie Sie Threading mit Kotlin-Koroutinen implementieren.

Konfigurationsänderungen überstehen

Wenn eine Konfigurationsänderung auftritt, z. B. eine Bildschirmdrehung, wird das Fragment oder die Aktivität gelöscht und neu erstellt. Alle Daten, die nicht im Instanzstatus für die 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 stellen.

Sie können ein ViewModel verwenden, damit Ihre Daten Konfigurationsänderungen überstehen. Die Komponente ViewModel ist so konzipiert, dass sie UI-bezogene Daten auf lebenszyklusbewusste Weise speichert und verwaltet. Mit dem vorherigen UserRepository kann der ViewModel die erforderlichen Netzwerkanfragen stellen und das Ergebnis mithilfe von LiveData an Ihr Fragment oder Ihre Aktivität senden:

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: