Paging 2-Bibliothek – Übersicht Teil von Android Jetpack.

Mit der Paging Library können Sie kleine Datenblöcke gleichzeitig laden und anzeigen. Durch das Laden von Teildaten bei Bedarf wird die Nutzung von Netzwerkbandbreite und Systemressourcen reduziert.

In diesem Leitfaden finden Sie mehrere Konzeptbeispiele der Bibliothek sowie eine Übersicht über ihre Funktionsweise. Vollständige Beispiele für die Funktionsweise dieser Bibliothek finden Sie im Codelab und anhand der Beispiele im Abschnitt Zusätzliche Ressourcen.

Einrichten

Fügen Sie der Datei build.gradle der App die folgenden Abhängigkeiten hinzu, um Paging-Komponenten in Ihre Android-App zu importieren:

Groovig

dependencies {
  def paging_version = "2.1.2"

  implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx
}

Kotlin

dependencies {
  val paging_version = "2.1.2"

  implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx
}

Bibliotheksarchitektur

In diesem Abschnitt werden die Hauptkomponenten der Paging-Bibliothek beschrieben und gezeigt.

Seitenliste

Die Hauptkomponente der Paging Library ist die PagedList-Klasse, die Blöcke der App-Daten – sogenannte Seiten – lädt. Wenn mehr Daten benötigt werden, werden sie in das vorhandene PagedList-Objekt eingefügt. Wenn sich geladene Daten ändern, wird eine neue Instanz von PagedList von einem LiveData- oder RxJava2-basierten Objekt an den beobachtbaren Dateninhaber ausgegeben. Wenn PagedList-Objekte generiert werden, werden deren Inhalte in der UI Ihrer App angezeigt. Dabei werden die Lebenszyklus der UI-Controller berücksichtigt.

Das folgende Code-Snippet zeigt, wie Sie das Ansichtsmodell Ihrer App so konfigurieren können, dass Daten mit einem LiveData-Inhaber von PagedList-Objekten geladen und präsentiert werden:

Kotlin

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

Java

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    // Creates a PagedList object with 50 items per page.
    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
                concertDao.concertsByDate(), 50).build();
    }
}

Daten

Jede Instanz von PagedList lädt einen aktuellen Snapshot der App-Daten aus dem entsprechenden DataSource-Objekt. Daten fließen vom Back-End oder der Datenbank Ihrer Anwendung zum PagedList-Objekt.

Im folgenden Beispiel wird die Room Persistence Library verwendet, um die Daten Ihrer Anwendung zu organisieren. Wenn Sie Ihre Daten jedoch auf andere Weise speichern möchten, können Sie auch Ihre eigene Datenquellen-Factory angeben.

Kotlin

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

Java

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a
    // PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

Weitere Informationen zum Laden von Daten in PagedList-Objekte finden Sie unter Auslagerungsdaten laden.

UI

Die Klasse PagedList arbeitet mit einem PagedListAdapter, um Elemente in einen RecyclerView zu laden. Diese Klassen arbeiten zusammen, um Inhalte beim Laden abzurufen und anzuzeigen, nicht sichtbare Inhalte vorab abzurufen und Inhaltsänderungen zu animieren.

Weitere Informationen finden Sie unter Seitenierte Listen aufrufen.

Verschiedene Datenarchitekturen unterstützen

Die Paging Library unterstützt die folgenden Datenarchitekturen:

  • Sie wird nur von einem Backend-Server bereitgestellt.
  • Nur in einer Datenbank auf dem Gerät gespeichert
  • Eine Kombination der anderen Quellen, wobei die Datenbank auf dem Gerät als Cache verwendet wird.

Abbildung 1 zeigt, wie Daten in jedem dieser Architekturszenarien fließen. Bei einer reinen Netzwerk- oder reinen Datenbanklösung fließen die Daten direkt an das UI-Modell Ihrer Anwendung. Wenn Sie einen kombinierten Ansatz verwenden, fließen Daten von Ihrem Back-End-Server zu einer On-Device-Datenbank und dann zum UI-Modell Ihrer Anwendung. Ab und zu werden dem Endpunkt jedes Datenflusses keine Daten mehr zum Laden zur Verfügung stehen. Daher fordert er mehr Daten von der Komponente an, die die Daten bereitgestellt hat. Wenn beispielsweise eine Datenbank auf dem Gerät keine Daten mehr hat, fordert sie mehr Daten vom Server an.

Diagramme von Datenflüssen
Abbildung 1. Wie Daten durch die Architekturen fließen, die von der Paging Library unterstützt werden

Der Rest dieses Abschnitts enthält Empfehlungen zum Konfigurieren der einzelnen Anwendungsfälle für den Datenfluss.

Nur Netzwerk

Verwenden Sie zum Anzeigen von Daten von einem Back-End-Server die synchrone Version der Retrofit API, um Informationen in Ihr eigenes benutzerdefiniertes DataSource-Objekt zu laden.

Nur Datenbank

Richten Sie Ihr RecyclerView so ein, dass der lokale Speicher überwacht wird, vorzugsweise mit der Room Persistence Library. Wenn also Daten in die Datenbank Ihrer Anwendung eingefügt oder geändert werden, werden diese Änderungen automatisch in der RecyclerView wiedergegeben, in der diese Daten angezeigt werden.

Netzwerk und Datenbank

Nachdem Sie mit der Beobachtung der Datenbank begonnen haben, können Sie mit PagedList.BoundaryCallback überwachen, ob die Datenbank die Daten aufgebraucht hat. Sie können dann weitere Elemente aus Ihrem Netzwerk abrufen und in die Datenbank einfügen. Wenn Ihre Benutzeroberfläche die Datenbank beobachtet, brauchen Sie nichts weiter zu tun.

Netzwerkfehler beheben

Wenn Sie die Daten, die Sie über die Paging Library anzeigen, über ein Netzwerk abrufen oder durchblättern, ist es wichtig, das Netzwerk nicht immer als „verfügbar“ oder „nicht verfügbar“ zu behandeln, da viele Verbindungen zeitweise oder instabil sind:

  • Ein bestimmter Server antwortet möglicherweise nicht auf eine Netzwerkanfrage.
  • Das Gerät ist möglicherweise mit einem langsamen oder schwachen Netzwerk verbunden.

Stattdessen sollte Ihre Anwendung jede Anfrage auf Fehler prüfen und in Fällen, in denen das Netzwerk nicht verfügbar ist, so reibungslos wie möglich wiederhergestellt werden. Sie können beispielsweise eine Schaltfläche „Wiederholen“ zur Verfügung stellen, über die Nutzer auswählen können, wenn der Schritt zur Datenaktualisierung nicht funktioniert. Wenn während des Daten-Pagings ein Fehler auftritt, sollten Sie die Paginierungsanfragen am besten automatisch wiederholen.

Vorhandene App aktualisieren

Wenn Ihre Anwendung bereits Daten aus einer Datenbank oder einer Back-End-Quelle verwendet, ist ein direktes Upgrade auf Funktionen der Paging Library möglich. In diesem Abschnitt wird gezeigt, wie Sie eine App mit einem gängigen bereits vorhandenen Design aktualisieren.

Benutzerdefinierte Paging-Lösungen

Wenn Sie benutzerdefinierte Funktionen zum Laden kleiner Teilmengen von Daten aus der Datenquelle Ihrer Anwendung verwenden, können Sie diese Logik durch die Logik aus der PagedList-Klasse ersetzen. Instanzen von PagedList bieten integrierte Verbindungen zu gemeinsamen Datenquellen. Diese Instanzen stellen auch Adapter für RecyclerView-Objekte bereit, die Sie in die UI Ihrer App aufnehmen können.

Daten, die mithilfe von Listen statt von Seiten geladen werden

Wenn Sie eine speicherinterne Liste als unterstützende Datenstruktur für den Adapter der UI verwenden, sollten Sie Datenaktualisierungen mit einer PagedList-Klasse beobachten, wenn die Anzahl der Elemente in der Liste groß werden kann. Instanzen von PagedList können entweder LiveData<PagedList> oder Observable<List> verwenden, um Datenaktualisierungen an die UI Ihrer Anwendung zu übergeben und so Ladezeiten und Arbeitsspeichernutzung zu minimieren. Noch besser: Wenn Sie in Ihrer App ein List-Objekt durch ein PagedList-Objekt ersetzen, sind keine Änderungen an der UI-Struktur der App oder der Logik zur Datenaktualisierung erforderlich.

Verknüpfen Sie mithilfe von CursorAdapter einen Datencursor mit einer Listenansicht.

Deine Anwendung kann ein CursorAdapter verwenden, um Daten aus einem Cursor mit einem ListView zu verknüpfen. In diesem Fall müssen Sie normalerweise von einem ListView zu einem RecyclerView als Listen-UI-Container Ihrer Anwendung migrieren und dann die Komponente Cursor durch Room oder PositionalDataSource ersetzen, je nachdem, ob Instanzen von Cursor auf eine SQLite-Datenbank zugreifen.

In einigen Situationen, z. B. beim Arbeiten mit Instanzen von Spinner, geben Sie nur den Adapter an. Eine Bibliothek übernimmt die in diesen Adapter geladenen Daten und zeigt sie für Sie an. Ändern Sie in diesen Situationen den Typ der Daten des Adapters in LiveData<PagedList> und verpacken Sie diese Liste dann in ein ArrayAdapter-Objekt, bevor Sie versuchen, diese Elemente durch eine Bibliotheksklasse in einer UI aufzublähen.

Inhalte mit AsyncListUtil asynchron laden

Wenn Sie AsyncListUtil-Objekte verwenden, um Informationsgruppen asynchron zu laden und anzuzeigen, können Sie Daten mit der Paginierungsbibliothek einfacher laden:

  • Ihre Daten müssen nicht positionalisiert sein. Mit der Paging Library können Sie Daten mithilfe von Schlüsseln, die vom Netzwerk bereitgestellt werden, direkt aus Ihrem Back-End laden.
  • Ihre Daten können unglaublich groß sein. Mit der Paging Library können Sie Daten in Seiten laden, bis keine Daten mehr vorhanden sind.
  • Sie können Ihre Daten besser im Blick behalten. Die Paging-Bibliothek kann Ihre Daten, die die ViewModel Ihrer App enthält, in einer beobachtbaren Datenstruktur präsentieren.

Datenbankbeispiele

Die folgenden Code-Snippets zeigen verschiedene Möglichkeiten, wie alle Teile zusammenwirken können.

Ausgelagerte Daten mit LiveData beobachten

Das folgende Code-Snippet zeigt, wie alle Teile zusammenwirken. Wenn Konzertereignisse in der Datenbank hinzugefügt, entfernt oder geändert werden, werden die Inhalte im RecyclerView automatisch und effizient aktualisiert:

Kotlin

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

class ConcertActivity : AppCompatActivity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: ConcertViewModel by viewModels()
        val recyclerView = findViewById(R.id.concert_list)
        val adapter = ConcertAdapter()
        viewModel.concertList.observe(this, PagedList(adapter::submitList))
        recyclerView.setAdapter(adapter)
    }
}

class ConcertAdapter() :
        PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {
    fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {
        val concert: Concert? = getItem(position)

        // Note that "concert" is a placeholder if it's null.
        holder.bindTo(concert)
    }

    companion object {
        private val DIFF_CALLBACK = object :
                DiffUtil.ItemCallback<Concert>() {
            // Concert details may have changed if reloaded from the database,
            // but ID is fixed.
            override fun areItemsTheSame(oldConcert: Concert,
                    newConcert: Concert) = oldConcert.id == newConcert.id

            override fun areContentsTheSame(oldConcert: Concert,
                    newConcert: Concert) = oldConcert == newConcert
        }
    }
}

Java

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
            concertDao.concertsByDate(), /* page size */ 50).build();
    }
}

public class ConcertActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ConcertViewModel viewModel =
                new ViewModelProvider(this).get(ConcertViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.concert_list);
        ConcertAdapter adapter = new ConcertAdapter();
        viewModel.concertList.observe(this, adapter::submitList);
        recyclerView.setAdapter(adapter);
    }
}

public class ConcertAdapter
        extends PagedListAdapter<Concert, ConcertViewHolder> {
    protected ConcertAdapter() {
        super(DIFF_CALLBACK);
    }

    @Override
    public void onBindViewHolder(@NonNull ConcertViewHolder holder,
            int position) {
        Concert concert = getItem(position);
        if (concert != null) {
            holder.bindTo(concert);
        } else {
            // Null defines a placeholder item - PagedListAdapter automatically
            // invalidates this row when the actual object is loaded from the
            // database.
            holder.clear();
        }
    }

    private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<Concert>() {
        // Concert details may have changed if reloaded from the database,
        // but ID is fixed.
        @Override
        public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
            return oldConcert.getId() == newConcert.getId();
        }

        @Override
        public boolean areContentsTheSame(Concert oldConcert,
                Concert newConcert) {
            return oldConcert.equals(newConcert);
        }
    };
}

Ausgelagerte Daten mit RxJava2 beobachten

Wenn Sie RxJava2 anstelle von LiveData verwenden möchten, können Sie stattdessen ein Observable- oder Flowable-Objekt erstellen:

Kotlin

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: Observable<PagedList<Concert>> =
            concertDao.concertsByDate().toObservable(pageSize = 50)
}

Java

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final Observable<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;

        concertList = new RxPagedListBuilder<>(
                concertDao.concertsByDate(), /* page size */ 50)
                        .buildObservable();
    }
}

Sie können die Beobachtung der Daten dann mit dem Code im folgenden Snippet starten und beenden:

Kotlin

class ConcertActivity : AppCompatActivity() {
    private val adapter = ConcertAdapter()

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val viewModel: ConcertViewModel by viewModels()

    private val disposable = CompositeDisposable()

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val recyclerView = findViewById(R.id.concert_list)
        recyclerView.setAdapter(adapter)
    }

    override fun onStart() {
        super.onStart()
        disposable.add(viewModel.concertList
                .subscribe(adapter::submitList)))
    }

    override fun onStop() {
        super.onStop()
        disposable.clear()
    }
}

Java

public class ConcertActivity extends AppCompatActivity {
    private ConcertAdapter adapter = new ConcertAdapter();
    private ConcertViewModel viewModel;

    private CompositeDisposable disposable = new CompositeDisposable();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        RecyclerView recyclerView = findViewById(R.id.concert_list);

        viewModel = new ViewModelProvider(this).get(ConcertViewModel.class);
        recyclerView.setAdapter(adapter);
    }

    @Override
    protected void onStart() {
        super.onStart();
        disposable.add(viewModel.concertList
                .subscribe(adapter.submitList(flowableList)
        ));
    }

    @Override
    protected void onStop() {
        super.onStop();
        disposable.clear();
    }
}

Der Code für ConcertDao und ConcertAdapter ist für eine RxJava2-basierte Lösung identisch mit dem für eine LiveData-basierte Lösung.

Feedback geben

Teilen Sie uns Ihr Feedback und Ihre Ideen über diese Ressourcen mit:

Problemverfolgung
Melden Sie Probleme, damit wir sie beheben können.

Weitere Informationen

Weitere Informationen zur Paging Library finden Sie in den folgenden Ressourcen.

Produktproben

Codelabs

Videos