Verbindung zum Netzwerk herstellen

Damit Sie Netzwerkvorgänge in Ihrer App ausführen können, 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 Anwendung Netzwerkfunktionen hinzufügen, müssen Sie dafür sorgen, dass Daten und Informationen innerhalb Ihrer Anwendung sicher sind, wenn Sie über ein Netzwerk übertragen. Halten Sie sich dazu an die folgenden Best Practices für die Netzwerksicherheit:

  • Minimieren Sie die Menge an vertraulichen oder persönlichen Nutzerdaten, die Sie über das Netzwerk übertragen.
  • Gesamten Netzwerktraffic von Ihrer Anwendung über SSL senden
  • Sie können eine Netzwerksicherheitskonfiguration erstellen, damit Ihre Anwendung benutzerdefinierten Zertifizierungsstellen (Certificate Authorities, CAs) vertrauen oder die Gruppe von System-CAs einschränken kann, denen sie für die sichere Kommunikation vertraut.

Weitere Informationen zur Anwendung sicherer Netzwerkprinzipien finden Sie in den Tipps zur Netzwerksicherheit.

HTTP-Client auswählen

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

Bibliotheken von Drittanbietern, die APIs auf höherer Ebene für Netzwerkvorgänge anbieten, sind ebenfalls verfügbar. Diese unterstützen verschiedene praktische Funktionen wie die Serialisierung von Anfragetexten und die Deserialisierung von Antworttexten.

  • Retrofit: ein typsicherer HTTP-Client für die JVM aus Square, der auf OkHttp basiert. Mit Retrofit können Sie eine Clientschnittstelle deklarativ erstellen und mehrere Serialisierungsbibliotheken unterstützen.
  • 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 haben integrierte Unterstützung für spezialisierte DNS-Lookups sowohl über die Klartextsuche als auch 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 muss die Anwendung ausführen.

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 aufrufen. 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 Designmuster für Repositories verwenden, um die Durchführung von Netzwerkvorgängen zu vereinfachen und die Codeduplizierung in verschiedenen Teilen Ihrer Anwendung zu reduzieren. Ein Repository ist eine Klasse, die Datenvorgänge verarbeitet und eine saubere API-Abstraktion für bestimmte Daten oder Ressourcen bietet.

Sie können Retrofit verwenden, um eine Schnittstelle zu deklarieren, die die HTTP-Methode, die URL, die Argumente und den Antworttyp für Netzwerkvorgänge angibt, wie im folgenden Beispiel gezeigt:

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 freigeben. 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 werden ebenfalls auf die Repository-Klasse beschränkt. Vielleicht möchten Sie eine Remote-Änderung wie eine Aktualisierung der API-Endpunkte vornehmen oder das lokale 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, damit keine nicht reagierende UI erstellt wird. Standardmäßig müssen Sie in Android Netzwerkvorgänge für einen anderen Thread als den UI-Hauptthread ausführen. Wenn Sie versuchen, Netzwerkvorgänge im Hauptthread auszuführen, wird ein NetworkOnMainThreadException ausgegeben.

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

Konfigurationsänderungen überstehen

Bei einer Konfigurationsänderung, z. B. bei einer Bildschirmdrehung, werden Fragmente oder Aktivitäten gelöscht und neu erstellt. Alle Daten, die nicht im Instanzstatus Ihrer Fragmentaktivität gespeichert wurden und nur kleine Datenmengen enthalten können, gehen verloren. In diesem Fall müssen Sie Ihre Netzwerkanfragen möglicherweise noch einmal stellen.

Sie können einen ViewModel verwenden, damit Ihre Daten Konfigurationsänderungen nicht verlieren. Die Komponente ViewModel dient zum Speichern und Verwalten von UI-bezogenen Daten unter Berücksichtigung des Lebenszyklus. Mit dem vorherigen UserRepository kann ViewModel die erforderlichen Netzwerkanfragen stellen und das Ergebnis mithilfe von LiveData an das Fragment oder die 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 zugehörigen Leitfäden: