Verbindung zum Netzwerk herstellen

Zum Ausführen von Netzwerkvorgängen in Ihrer Anwendung muss das 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 eine 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 sicher sind. Befolgen 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 Netzwerktraffic von Ihrer Anwendung über SSL.
  • Erwägen Sie die Erstellung einer Netzwerksicherheitskonfiguration, mit der Ihre Anwendung benutzerdefinierten Zertifizierungsstellen (CAs) vertrauen oder die Gruppe von System-CAs einschränken kann, denen sie für eine sichere Kommunikation vertraut.

Weitere Informationen zur Anwendung von Grundsätzen für sichere Netzwerke finden Sie unter Tipps zur Netzwerksicherheit.

HTTP-Client auswählen

Die meisten mit dem Netzwerk verbundenen Apps verwenden HTTP zum Senden und Empfangen von Daten. Die Android-Plattform umfasst den HttpsURLConnection-Client, der TLS, Streaming-Uploads und -Downloads, konfigurierbare Zeitlimits, IPv6 und Verbindungs-Pooling unterstützt.

Bibliotheken von Drittanbietern, die übergeordnete APIs für Netzwerkvorgänge anbieten, sind ebenfalls verfügbar. Diese unterstützen verschiedene praktische Funktionen, 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. Retrofit ermöglicht Ihnen, eine Clientschnittstelle deklarativ zu erstellen, und unterstützt mehrere Serialisierungsbibliotheken.
  • Ktor: Ein HTTP-Client von JetBrains, der vollständig für Kotlin entwickelt wurde und von Koroutinen unterstützt wird. Ktor unterstützt verschiedene Engines, Serialisierer 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-Lookups sowohl über Klartextsuchen 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 suchen können. Das Parsen der Antwort bleibt der Anwendung ü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 nach den mit einem Namen verknüpften IP-Adressen suchen. Andere Eintragstypen werden jedoch nicht unterstützt.

Informationen zu NDK-basierten Anwendungen finden Sie unter android_res_nsend.

Netzwerkvorgänge mit einem Repository kapseln

Sie können das Repository-Designmuster verwenden, um die Durchführung von Netzwerkvorgängen zu vereinfachen und Codeduplizierung in verschiedenen Teilen Ihrer Anwendung zu reduzieren. Ein Repository ist eine Klasse, die Datenvorgänge verarbeitet und eine saubere API-Abstraktion über bestimmte Daten oder Ressourcen bietet.

Sie können mit Retrofit 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 zur Verfügung stellen. Durch diese Kapselung wird sichergestellt, 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. Sie können beispielsweise eine Remote-Änderung wie eine Aktualisierung der API-Endpunkte vornehmen oder 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 für den Hauptthread aus, damit keine nicht reagierende UI erstellt wird. Standardmäßig erfordert Android, dass Sie Netzwerkvorgänge für einen anderen Thread als den Haupt-UI-Thread ausführen. Wenn Sie versuchen, Netzwerkvorgänge im Hauptthread auszuführen, wird ein NetworkOnMainThreadException ausgelöst.

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

Konfigurationsänderungen beibehalten

Bei einer Konfigurationsänderung, z. B. bei einer Bildschirmdrehung, wird das Fragment oder die Aktivität gelöscht und neu erstellt. Alle Daten, die nicht im Instanzstatus der Fragmentaktivität gespeichert sind und nur kleine Datenmengen enthalten können, gehen verloren. 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 beibehalten. Die Komponente ViewModel wurde entwickelt, um UI-bezogene Daten unter Berücksichtigung des Lebenszyklus zu speichern und zu verwalten. Mithilfe des vorherigen UserRepository kann das ViewModel die erforderlichen Netzwerkanfragen senden und das Ergebnis mithilfe von LiveData an das Fragment oder die Aktivität übergeben:

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 verwandten Leitfäden: