Ausführliche Erklärung und Verwendung von Android LiveData

1. Was ist LiveData?

LiveDataist eine beobachtbare Datenspeicherklasse.

Im Gegensatz zu regulären beobachtbaren Klassen LiveDataist sie lebenszyklusbewusst, d. h. sie folgt dem Lebenszyklus anderer Anwendungskomponenten wie Activity, Fragmentoder . ServiceDieses Bewusstsein stellt sicher, dass LiveDatanur Anwendungskomponentenbeobachter aktualisiert werden, die sich in einem aktiven Lebenszyklusstatus befinden.

Ein Beobachter (dargestellt durch eine Klasse) gilt als aktiv, wenn Observerseine Lebensdauer im Zustand STARTEDoder liegt. Nur aktive Beobachter werden über Aktualisierungen benachrichtigt. Inaktive Beobachter, die zur Beobachtung des Objekts registriert sind, werden nicht über Änderungen benachrichtigt.RESUMEDLiveDataLiveDataLiveData

LifecycleOwnerSie können Beobachter gepaart mit Objekten registrieren, die die Schnittstelle implementieren. Wenn diese Beziehung besteht, kann der Beobachter entfernt werden, wenn sich der Zustand des entsprechenden LifecycleObjekts ändert . DESTROYEDDies ist besonders nützlich für Activityund Fragment, da sie LiveDataObjekte sicher beobachten können, ohne sich Gedanken über Lecks machen zu müssen (das System meldet sie sofort ab, wenn Activityihre FragmentLebensdauer zerstört ist).

2. Vorteile von LiveData

Die Verwendung LiveDatahat folgende Vorteile:

  • Stellen Sie sicher, dass die Schnittstelle dem Datenstatus entspricht

    LiveDataFolgen Sie dem Beobachtermuster. LiveDataDas **-Objekt wird benachrichtigt , wenn sich die zugrunde liegenden Daten ändern Observer. Sie können Code zum ObserverAktualisieren der Schnittstelle in diese Objekte integrieren. Auf diese Weise müssen Sie Ihre Benutzeroberfläche nicht jedes Mal aktualisieren, wenn sich die Daten Ihrer App ändern, da der Beobachter dies für Sie erledigt.

  • keine Speicherlecks

    Beobachter sind an LifecycleObjekte gebunden und räumen auf, nachdem die zugehörigen Lebenszyklen zerstört wurden.

  • Keine Abstürze, wenn die Aktivität beendet wird

    Wenn die Lebensdauer des Beobachters inaktiv ist (wie im Backstack Activity), ist es nicht gut, Ereignisse zu LiveDataempfangen

  • Keine manuelle Bearbeitung von Lebenszyklen mehr

    UI-Komponenten beobachten lediglich die relevanten Daten und unterbrechen die Beobachtung nicht oder setzen sie nicht fort. LiveDataAlle diese Vorgänge werden automatisch verwaltet, da bei der Beobachtung relevante Änderungen des Lebenszyklusstatus berücksichtigt werden.

  • Die Daten sind immer aktuell

    Wenn ein Lebenszyklus inaktiv wird, erhält er die neuesten Daten, wenn er wieder aktiv wird. Beispielsweise erhält eine Aktivität, die einmal im Hintergrund war, die neuesten Daten, sobald sie wieder in den Vordergrund tritt.

  • Entsprechende Konfigurationsänderungen

    ActivityWenn oder aufgrund einer Konfigurationsänderung, beispielsweise einer Geräterotation, neu erstellt wird Fragment, erhält es sofort die neuesten verfügbaren Daten.

  • Ressource teilen

    Sie können das Singleton-Muster verwenden, um LiveDataObjekte zu erweitern, um Systemdienste zu kapseln, sodass sie anwendungsübergreifend gemeinsam genutzt werden können. LiveDataDas Objekt stellt einmal eine Verbindung zum Systemdienst her, und dann beobachtet jeder Beobachter, der die entsprechende Ressource benötigt, einfach LiveDatadas Objekt.

3. Verwenden Sie das LiveData-Objekt

Führen Sie die folgenden Schritte aus, um LiveDatadas Objekt zu verwenden:

  1. Es wird eine Instanz von erstellt LiveData, um bestimmte Datentypen zu speichern. Dies ViewModelgeschieht in der Regel im Unterricht.

  2. Erstellt ein Objekt onChanged(), das Observereine Methode definiert, die steuert, LiveDatawas passiert, wenn sich die vom Objekt gespeicherten Daten ändern. Normalerweise erstellen Sie ObserverObjekte in einem Schnittstellencontroller wie einer Aktivität oder einem Fragment.

  3. Verwenden Sie observe()die Methode, um Observerdas Objekt an LiveDatadas Objekt anzuhängen. observe()Die Methode akzeptiert LifecycleOwnerein Objekt. Dadurch Observerabonniert das Objekt LiveDatadas Objekt, sodass es über Änderungen benachrichtigt wird. Normalerweise hängen Sie ObserverObjekte in einem Schnittstellencontroller an, beispielsweise einer Aktivität oder einem Fragment.

    Hinweis : Sie können observeForever(Observer)die Methode verwenden, um einen Beobachter ohne zugehöriges LifecycleOwnerObjekt zu registrieren. In diesem Fall gilt der Beobachter als immer aktiv und wird daher immer über Änderungen benachrichtigt. Sie können removeObserver(Observer)diese Beobachter entfernen, indem Sie die Methode aufrufen.

Wenn Sie LiveDataden im Objekt gespeicherten Wert aktualisieren, werden alle registrierten Beobachter ausgelöst (sofern der angehängte Wert LifecycleOwneraktiv ist).

LiveDataErmöglicht UI-Controller-Beobachtern das Abonnieren von Updates. Wenn LiveDatasich die Daten im Objektspeicher ändern, wird die Schnittstelle automatisch aktualisiert, um zu reagieren.

  • LiveDataObjekt erstellen

    LiveDataist ein Wrapper, der für alle Daten verwendet werden kann, einschließlich Collectionsimplementierter Objekte wie List. LiveDataObjekte werden normalerweise in ViewModelObjekten gespeichert und über Getter-Methoden darauf zugegriffen, wie im folgenden Beispiel gezeigt:

    public class NameViewModel extends ViewModel {
          
          
    
    // Create a LiveData with a String
    private MutableLiveData<String> currentName;
    
        public MutableLiveData<String> getCurrentName() {
          
          
            if (currentName == null) {
          
          
                currentName = new MutableLiveData<String>();
            }
            return currentName;
        }
    
    // Rest of the ViewModel...
    }
    

    LiveDataDie Daten im Objekt sind zunächst nicht festgelegt.

Hinweis : Stellen Sie aus folgenden Gründen sicher, dass LiveDatadas zum Aktualisieren der Schnittstelle verwendete Objekt im Objekt und nicht in der Aktivität oder dem Fragment gespeichert ist: Vermeiden Sie zu große Aktivitäten und Fragmente. ViewModelNun sind diese UI-Controller für die Anzeige von Daten verantwortlich, nicht jedoch für die Speicherung des Datenstatus. Trennen Sie LiveDatadie Instanz von einer bestimmten Aktivitäts- oder Fragmentinstanz und ermöglichen Sie LiveDatadem Objekt, Konfigurationsänderungen zu überleben

  • Beobachtungsobjekt LiveData_

    In den meisten Fällen onCreate()ist die Methode zum Anbringen einer Komponente LiveDataaus folgenden Gründen der richtige Ausgangspunkt für die Betrachtung eines Objekts:

    • Stellen Sie sicher, dass das System keine redundanten Aufrufe von Aktivitäts- oder Fragmentmethoden durchführt onResume().
    • Stellen Sie sicher, dass die Aktivität oder das Fragment über Daten verfügt, die sofort nach ihrer Aktivierung angezeigt werden können. Sobald sich eine Anwendungskomponente im STARTEDStatus befindet, empfängt sie den neuesten Wert von dem Objekt, das sie beobachtet LiveData. Dies geschieht nur, wenn ein zu überwachendes LiveDataObjekt festgelegt ist.

    Normalerweise sendet LiveData Updates nur, wenn sich die Daten ändern, und nur an aktive Beobachter. Eine Ausnahme von diesem Verhalten besteht darin, dass Beobachter auch Updates erhalten, wenn sie von inaktiv zu aktiv wechseln. Wenn der Beobachter ein zweites Mal von inaktiv auf aktiv wechselt, erhält er außerdem nur Aktualisierungen, wenn sich der Wert seit seiner letzten Aktivierung geändert hat.

    Der folgende Beispielcode veranschaulicht, wie mit der Beobachtung von LiveDataObjekten begonnen wird:

    public class NameActivity extends AppCompatActivity {
          
          
    
        private NameViewModel model;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
          
          
            super.onCreate(savedInstanceState);
    
            // Other code to setup the activity...
    
            // Get the ViewModel.
            model = new ViewModelProvider(this).get(NameViewModel.class);
    
            // Create the observer which updates the UI.
            final Observer<String> nameObserver = new Observer<String>() {
          
          
                @Override
                public void onChanged(@Nullable final String newName) {
          
          
                    // Update the UI, in this case, a TextView.
                    nameTextView.setText(newName);
                }
            };
    
            // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
            model.getCurrentName().observe(this, nameObserver);
        }
    }
    

    nameObserverWenn es mit dem übergebenen Parameter aufgerufen wird observe(), wird es sofort aufgerufen onChanged()und stellt mCurrentNameden neuesten in der Datei gespeicherten Wert bereit. Wird nicht aufgerufen, wenn für LiveDatadas Objekt nicht bereits ein Wert festgelegt ist .mCurrentNameonChanged()

  • LiveDataObjekt aktualisieren

    LiveData verfügt über keine öffentlich verfügbaren Methoden zum Aktualisieren gespeicherter Daten. MutableLiveDataDie Klasse stellt Methoden bereit setValue(T), die verwendet werden müssen, postValue(T)wenn Sie den im Objekt gespeicherten Wert ändern müssen . LiveDataWird normalerweise in ViewModelverwendet MutableLiveDataund macht dann ViewModelnur unveränderliche LiveDataObjekte für Beobachter sichtbar.

    Nachdem Sie die Beobachterbeziehungen eingerichtet haben, können Sie LiveDataden Wert des Objekts aktualisieren (wie im folgenden Beispiel), sodass alle Beobachter ausgelöst werden, wenn der Benutzer auf eine Schaltfläche tippt:

    button.setOnClickListener(new OnClickListener() {
          
          
        @Override
        public void onClick(View v) {
          
          
            String anotherName = "John Doe";
            model.getCurrentName().setValue(anotherName);
        }
    });
    

Der Aufruf in diesem Beispiel setValue(T)führt dazu, dass der Beobachter John Doeseine Methode mit dem Wert aufruft onChanged(). Dieses Beispiel zeigt einen Tastendruck, kann aber auch aus verschiedenen Gründen aufgerufen setValue()oder postValue()aktualisiert mNamewerden, unter anderem als Reaktion auf eine Netzwerkanfrage oder wenn der Ladevorgang einer Datenbank abgeschlossen ist. In allen Fällen löst der Aufruf setValue()oder postValue()den Beobachter aus und aktualisiert die Schnittstelle.

Hinweis : Sie müssen setValue(T)die Methode aufrufen, um LiveDatadas Objekt vom Hauptthread aus zu aktualisieren. Wenn Sie Code in einem Arbeitsthread ausführen, können Sie stattdessen postValue(T)die Methode verwenden, um LiveDatadas Objekt zu aktualisieren.

  • Verwendung LiveDatamit Room

    Die Room- Persistenzbibliothek unterstützt LiveDatabeobachtbare Abfragen, die Objekte zurückgeben. Beobachtbare Abfragen sind Teil von Database Access Objects (DAO).

    Wenn die Datenbank aktualisiert wird, generiert Room LiveDataden gesamten Code, der zum Aktualisieren der Objekte erforderlich ist. Der generierte Code führt die Abfrage bei Bedarf asynchron in einem Hintergrundthread aus. Dieses Muster hilft dabei, die in der Benutzeroberfläche angezeigten Daten mit den in der Datenbank gespeicherten Daten synchron zu halten. Weitere Informationen zu Room und DAOs finden Sie im Room Persistence Library Guide .

4. LiveData in der Anwendungsarchitektur

LiveDataEs verfügt über ein Lebenszyklusbewusstsein und folgt dem Lebenszyklus von Entitäten wie und activity. Sie können Daten zwischen diesen Lebensdauereigentümern und anderen Objekten mit unterschiedlichen Lebensdauern, z. B. Objekten, fragmentübermitteln Die Hauptaufgabe von besteht darin, schnittstellenbezogene Daten zu laden und zu verwalten. Daher eignet es sich sehr gut als alternative Methode zum Persistieren von Objekten. Sie können Objekte erstellen und diese Objekte dann verwenden, um den Status für die UI-Ebene bereitzustellen.LiveDataViewModelViewModelLiveDataViewModelLiveData

activityund fragmentsollten keine Instanzen beibehalten, LiveDatada ihr Zweck darin besteht, Daten anzuzeigen und nicht den Status zu halten. Außerdem erleichtert es das Schreiben von Unit-Tests, indem Aktivitäten und Fragmente persistent gemacht werden, ohne dass Daten persistent bleiben.

Sie könnten versucht sein, Objekte in Ihren Datenschichtklassen zu verwenden LiveData, diese LiveDatasind jedoch nicht gut für die Verarbeitung asynchroner Datenströme geeignet. Sie können zwar LiveDataTransformationen verwenden, MediatorLiveDataum dies zu erreichen, die Nachteile dieses Ansatzes bestehen jedoch darin, dass die Funktionalität zum Kombinieren von Datenströmen sehr begrenzt ist und alle LiveDataObjekte (einschließlich der durch Transformationen erstellten) im Hauptthread beobachtet werden. Hier ist ein Beispielcode, der zeigt , wie Repositorydas Beibehalten in LiveDataeinem den Hauptthread blockiert:

class UserRepository {
    
    

    // DON'T DO THIS! LiveData objects should not live in the repository.
    LiveData<List<User>> getUsers() {
    
    
        ...
    }

    LiveData<List<User>> getNewPremiumUsers() {
    
    
    return Transformations.map(getUsers(),
        // This is an expensive call being made on the main thread and may cause
        // noticeable jank in the UI!
        users -> users.stream()
            .filter(User::isPremium)
            .filter(user ->
                user.getTimeCreated() > dao.getLastSyncedTime())
            .collect(Collectors.toList()));
    }
}

Wenn Sie den Datenfluss in anderen Ebenen Ihrer App verwenden müssen, ziehen Sie die Verwendung von Kotlin Flow in Betracht und konvertieren Sie dann Kotlin Flow in die Verwendung asLiveData()in . Erfahren Sie in diesem Codelab mehr über die Verwendung von Kotlin mit Kotlin . Erwägen Sie für in Java erstellte Codebasen die Verwendung von Executoren mit Rückrufen oder .ViewModelLiveDataFlowLiveDataRxJava

5. LiveData erweitern

Ein Beobachter gilt als aktiv, wenn seine Lebensdauer im STARTEDoder- RESUMEDZustand ist. LiveDataDer folgende Beispielcode veranschaulicht, wie LiveDataeine Klasse erweitert wird:

public class StockLiveData extends LiveData<BigDecimal> {
    
    
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
    
    
        @Override
        public void onPriceChanged(BigDecimal price) {
    
    
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
    
    
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
    
    
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
    
    
        stockManager.removeUpdates(listener);
    }
}

Die Price-Listener-Implementierung in diesem Beispiel umfasst die folgenden wichtigen Methoden:

  • LiveDataDie Methode wird aufgerufen, wenn das Objekt aktive Beobachter hat onActive(). Das bedeutet, dass Sie mit dieser Methode beginnen müssen, Aktienkursaktualisierungen zu beobachten.
  • LiveDataDie Methode wird aufgerufen, wenn das Objekt keine aktiven Beobachter hat onInactive(). Da keine Beobachter zuhören, gibt es keinen Grund, StockManagerdie Verbindung zum Dienst aufrechtzuerhalten.
  • setValue(T)Die Methode aktualisiert LiveDataden Wert der Instanz und benachrichtigt aktive Beobachter über die Änderung.

Sie können StockLiveDatadie Klasse wie folgt verwenden:

public class MyFragment extends Fragment {
    
    
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    
    
        super.onViewCreated(view, savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(getViewLifecycleOwner(), price -> {
    
    
            // Update the UI.
        });
    }
}

observe()Der Methode wird Fragmentdie zugehörige Ansicht LifecycleOwnerals erster Parameter übergeben. Dadurch wird angezeigt, dass dieser Beobachter an das mit dem Eigentümer verknüpfte Objekt gebunden ist Lifecycle, was bedeutet:

  • Wenn Lifecycledas Objekt nicht aktiv ist, wird der Beobachter nicht aufgerufen, selbst wenn sich der Wert ändert.
  • LifecycleBeobachter werden automatisch entfernt, wenn das Objekt zerstört wird .

LiveDataDie Tatsache, dass Objekte lebenszyklusbewusst sind, bedeutet, dass Sie diese Objekte zwischen mehreren und teilen Activitykönnen . Um das Beispiel einfach zu halten, können Sie die Klasse wie folgt als Singleton implementieren:FragmentServiceLiveData

public class StockLiveData extends LiveData<BigDecimal> {
    
    
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
    
    
        @Override
        public void onPriceChanged(BigDecimal price) {
    
    
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
    
    
        if (sInstance == null) {
    
    
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
    
    
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
    
    
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
    
    
        stockManager.removeUpdates(listener);
    }
}

und Sie können Fragmentes im Inneren wie folgt verwenden:

public class MyFragment extends Fragment {
    
    
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    
    
        super.onViewCreated(view, savedInstanceState);
        StockLiveData.get(symbol).observe(getViewLifecycleOwner(), price -> {
    
    
            // Update the UI.
        });
    }
}

Mehrere Fragmentund Activitybeobachtbare MyPriceListenerInstanzen. LiveDataStellen Sie eine Verbindung zu einem oder mehreren Systemdiensten nur her, wenn diese sichtbar und aktiv sind .

6. LiveData konvertieren

Möglicherweise möchten Sie LiveDataÄnderungen an den im Objekt gespeicherten Werten vornehmen, bevor Sie es an Beobachter senden, oder Sie müssen möglicherweise eine andere LiveDataInstanz basierend auf dem Wert einer anderen Instanz zurückgeben. LifecycleDas Paket stellt Transformationsdie Klasse bereit, die Hilfsmethoden für diese Situationen enthält.

  • Transformations.map()
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    
    
    user.name + " " + user.lastName
});
  • Transformations.switchMap()

    Ähnlich map()wie wendet eine Funktion auf den im LiveDataObjekt gespeicherten Wert an und entpackt das Ergebnis und sendet es weiter. Eine an übergebene Funktion switchMap()muss ein Objekt zurückgeben LiveData, wie im folgenden Beispiel gezeigt:

private LiveData<User> getUser(String id) {
    
    
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

Sie können die Übergangsmethode verwenden, um Informationen während der Lebensdauer des Beobachters zu übermitteln. LiveDataTransformationen werden nicht ausgewertet, es sei denn, ein Beobachter beobachtet das zurückgegebene Objekt. Da Übergänge träge berechnet werden, wird lebenszyklusbezogenes Verhalten implizit ohne zusätzliche explizite Aufrufe oder Abhängigkeiten weitergegeben.

Wenn Sie der Meinung sind, dass Sie ein Objekt innerhalb eines Objekts ViewModelbenötigen Lifecycle, ist eine Konvertierung möglicherweise die bessere Lösung. Angenommen, Sie verfügen über eine Schnittstellenkomponente, die eine Adresse entgegennimmt und die Postleitzahl für diese Adresse zurückgibt. Sie können für diese Komponente eine einfache Implementierung implementieren ViewModel, wie im folgenden Beispielcode gezeigt:

class MyViewModel extends ViewModel {
    
    
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
    
    
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
    
    
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

Die Schnittstellenkomponente muss dann bei jedem Aufruf die Registrierung des vorherigen LiveDataObjekts aufheben und sich bei der neuen Instanz registrieren. getPostalCode()Wenn die UI-Komponente neu erstellt wird, löst sie außerdem einen weiteren repository.getPostCode()Aufruf der Methode aus, anstatt das Ergebnis des vorherigen Aufrufs zu verwenden.

Sie können eine Postleitzahlensuche auch als Transformation einer Adresseingabe implementieren, wie im folgenden Beispiel gezeigt:

class MyViewModel extends ViewModel {
    
    
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
    
    
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
    
    
      this.repository = repository
  }

  private void setInput(String address) {
    
    
      addressInput.setValue(address);
  }
}

In diesem Fall postalCodewerden die Felder als addressInputTransformationen von definiert. Solange Ihrer App postalCodeein aktiver Beobachter mit dem Feld zugeordnet ist, wird der Feldwert addressInputbei jeder Änderung neu berechnet und abgerufen.

Dieser Mechanismus ermöglicht es Anwendungen auf niedrigerer Ebene, Objekte zu erstellen, die bei Bedarf träge berechnet werden LiveData. ViewModelObjekte können leicht LiveDataReferenzen auf Objekte erhalten und dann darauf basierende Transformationsregeln definieren.

  • Schaffen Sie eine neue Transformation

Es gibt ein Dutzend verschiedener spezifischer Transformationen, die in Ihrer App nützlich sein könnten, aber sie werden nicht standardmäßig bereitgestellt. MediatorLiveDataUm Ihre eigenen Transformationen zu implementieren, können Sie die Klasse verwenden , die auf andere LiveDataObjekte lauscht und von ihnen ausgegebene Ereignisse verarbeitet. MediatorLiveDataGibt seinen Zustand LiveDatakorrekt an das Quellobjekt weiter. Weitere Informationen zu diesem Muster finden Sie in Transformationsder Referenzdokumentation für die Klasse.

7. Führen Sie mehrere LiveData-Quellen zusammen

MediatorLiveDataIst LiveDataeine Unterklasse, die es Ihnen ermöglicht, mehrere LiveDataQuellen zu kombinieren. Der Beobachter für das Objekt wird immer dann ausgelöst, wenn sich eines der ursprünglichen LiveDataQuellobjekte ändert MediatorLiveData.

Wenn Ihre Schnittstelle beispielsweise ein Objekt enthält, das über eine lokale Datenbank oder das Netzwerk aktualisiert werden kann LiveData, können Sie MediatorLiveDatadem Objekt die folgende Quelle hinzufügen:

  • Objekte , die mit in der Datenbank gespeicherten Daten verknüpft sind LiveData.
  • Ein Objekt , das mit Daten verknüpft ist, auf die über das Netzwerk zugegriffen wird LiveData.

Ihre Aktivität MediatorLiveDatakann Aktualisierungen von beiden Quellen erhalten, indem Sie einfach das Objekt beobachten.

Ich denke du magst

Origin blog.csdn.net/klylove/article/details/122039737
Empfohlen
Rangfolge