Обзор библиотеки Paging 2 Входит в Android Jetpack.
Библиотека Paging позволяет загружать и показывать данные небольшими фрагментами. Загрузка части данных по запросу снижает нагрузку на сеть и системные ресурсы.
В этом руководстве приведено несколько примеров использования библиотеки и описаны принципы ее работы. Чтобы посмотреть полные примеры работы этой библиотеки, попробуйте выполнить практическую работу и примеры из раздела Дополнительные ресурсы.
Настроить
Чтобы импортировать компоненты Paging в приложение для Android, добавьте в его файл build.gradle следующие зависимости:
Яркий
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 }
Архитектура библиотеки
В этом разделе описываются и показываются основные компоненты библиотеки подкачки.
PagedList
Основной компонент библиотеки Paging – класс PagedList, который загружает фрагменты данных приложения, или страницы. По мере необходимости данные подгружаются в существующий объект PagedList. Если загруженные данные изменяются, новый экземпляр PagedList передается наблюдаемому держателю данных из объекта LiveData или RxJava2. По мере создания объектов PagedList интерфейс приложения представляет их содержимое, соблюдая жизненные циклы контроллеров интерфейса.
В следующем фрагменте кода показано, как настроить модель представления приложения для загрузки и представления данных с помощью держателя LiveData объектов PagedList:
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(); } }
Данные
Каждый экземпляр PagedList загружает актуальный снимок данных приложения из соответствующего объекта DataSource. Данные передаются из серверной части или базы данных приложения в объект PagedList.
В следующем примере для организации данных приложения используется библиотека персистентности Room, но если вы хотите хранить данные другим способом, вы также можете предоставить собственную фабрику источников данных.
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(); }
Подробнее о том, как загружать данные в объекты PagedList, рассказывается в руководстве Как загружать данные с разбивкой по страницам.
Пользовательский интерфейс
Класс PagedList работает с PagedListAdapter, чтобы загружать элементы в RecyclerView. Эти классы работают вместе, чтобы получать и отображать контент по мере его загрузки, предварительно загружать контент вне поля зрения и анимировать изменения контента.
Подробнее о том, как показывать списки с разбивкой на страницы…
Поддержка разных архитектур данных
Библиотека Paging поддерживает следующие архитектуры данных:
- Обслуживается только с бэкенд-сервера.
- Хранятся только в базе данных на устройстве.
- Комбинация других источников, использующая базу данных на устройстве в качестве кеша.
На рисунке 1 показано, как передаются данные в каждом из этих сценариев. В случае решения, предназначенного только для сети или только для базы данных, данные поступают непосредственно в модель пользовательского интерфейса вашего приложения. Если вы используете комбинированный подход, данные передаются с вашего бэкенд-сервера в базу данных на устройстве, а затем в модель пользовательского интерфейса вашего приложения. Время от времени конечная точка каждого потока данных исчерпывает данные для загрузки, и в этот момент она запрашивает дополнительные данные у компонента, который предоставил данные. Например, когда на устройстве заканчиваются данные, оно запрашивает их у сервера.
В оставшейся части этого раздела приведены рекомендации по настройке каждого варианта использования потока данных.
Только сеть
Чтобы показать данные с бэкенд-сервера, используйте синхронную версию Retrofit API для загрузки информации в ваш собственный объект DataSource.
Только база данных
Настройте RecyclerView для отслеживания локального хранилища, желательно с помощью библиотеки персистентности Room. Таким образом, при вставке или изменении данных в базе данных приложения эти изменения автоматически отражаются в RecyclerView, который отображает эти данные.
Сеть и база данных
После того как вы начнете отслеживать базу данных, вы можете прослушивать, когда в базе данных закончатся данные, используя PagedList.BoundaryCallback.
Затем вы можете получить из сети дополнительные элементы и вставить их в базу данных. Если ваш пользовательский интерфейс наблюдает за базой данных, это все, что вам нужно сделать.
Как устранять ошибки сети
При использовании сети для получения или разбивки на страницы данных, которые вы отображаете с помощью библиотеки Paging, важно не рассматривать сеть как "доступную" или "недоступную" все время, поскольку многие подключения являются прерывистыми или нестабильными:
- Сервер может не ответить на сетевой запрос.
- Возможно, устройство подключено к сети с низкой скоростью или слабым сигналом.
Вместо этого приложение должно проверять каждый запрос на наличие ошибок и восстанавливать работу как можно более плавно в случаях, когда сеть недоступна. Например, вы можете добавить кнопку "Повторить", чтобы пользователи могли нажать ее, если обновление данных не сработает. Если на этапе разбивки данных на страницы возникает ошибка, лучше всего автоматически повторить запросы на разбивку.
Обновите существующее приложение
Если ваше приложение уже использует данные из базы данных или серверного источника, вы можете сразу перейти на функции, предоставляемые библиотекой Paging. В этом разделе показано, как обновить приложение с распространенным дизайном.
Специальные решения для пейджинга
Если вы используете специальные функции для загрузки небольших подмножеств данных из источника данных приложения, вы можете заменить эту логику логикой из класса PagedList. Экземпляры PagedList предлагают встроенные подключения к распространенным источникам данных. Эти экземпляры также предоставляют адаптеры для объектов RecyclerView, которые вы можете включить в интерфейс приложения.
Данные загружаются с помощью списков, а не страниц
Если вы используете список в памяти в качестве структуры данных для адаптера пользовательского интерфейса, рекомендуем отслеживать обновления данных с помощью класса PagedList, если в списке может быть много элементов. Экземпляры PagedList могут использовать LiveData<PagedList> или Observable<List>, чтобы передавать обновленные данные в интерфейс приложения, сокращая время загрузки и использование памяти. Более того, замена объекта List на объект PagedList в приложении не требует никаких изменений в структуре пользовательского интерфейса или логике обновления данных.
Связывание курсора данных с представлением списка с помощью CursorAdapter
Приложение может использовать CursorAdapter, чтобы связывать данные из Cursor с ListView. В этом случае обычно нужно перейти с ListView на RecyclerView в качестве контейнера пользовательского интерфейса списка приложения, а затем заменить компонент Cursor на Room или PositionalDataSource в зависимости от того, обращаются ли экземпляры Cursor к базе данных SQLite.
В некоторых случаях, например при работе с экземплярами Spinner, вы предоставляете только сам адаптер. Затем библиотека берет данные, загруженные в этот адаптер, и показывает их вам. В таких ситуациях измените тип данных адаптера на LiveData<PagedList>, а затем упакуйте этот список в объект ArrayAdapter, прежде чем пытаться заставить класс библиотеки раздувать эти элементы в пользовательском интерфейсе.
Как асинхронно загружать контент с помощью AsyncListUtil
Если вы используете объекты AsyncListUtil для асинхронной загрузки и отображения групп информации, библиотека Paging Library упростит загрузку данных.
- Данные не обязательно должны быть позиционными. Библиотека Paging позволяет загружать данные непосредственно из серверной части, используя ключи, предоставляемые сетью.
- Ваши данные могут быть неисчислимо большими. С помощью библиотеки Paging Library вы можете загружать данные на страницы, пока они не закончатся.
- Вы можете легко отслеживать свои данные. Библиотека Paging может представлять данные, которые ViewModel вашего приложения хранит в наблюдаемой структуре данных.
Примеры баз данных
В приведенных ниже фрагментах кода показано несколько возможных способов объединения всех частей.
Наблюдение за данными с разбивкой по страницам с помощью LiveData
В следующем фрагменте кода показано, как все эти элементы работают вместе. Когда в базу данных добавляются, удаляются или изменяются события концерта, контент в RecyclerView автоматически и эффективно обновляется:
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); } }; }
Отслеживание разбитых на страницы данных с помощью RxJava2
Если вы предпочитаете использовать RxJava2 вместо LiveData, вы можете создать объект Observable или Flowable:
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(); } }
Затем вы можете начать и прекратить наблюдение за данными, используя код в следующем фрагменте:
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(); } }
Код для ConcertDao и ConcertAdapter одинаков для решений на основе RxJava2 и LiveData.
Отправить отзыв
Поделитесь с нами своими отзывами и идеями, используя следующие ресурсы:
- Система отслеживания ошибок

- Сообщайте о проблемах, чтобы мы могли их устранить.
Дополнительные ресурсы
Дополнительную информацию о библиотеке Paging Library можно найти в следующих ресурсах:
Образцы
Практические работы
Видео
- Android Jetpack: управление бесконечными списками с помощью RecyclerView и Paging (Google I/O, 2018 г.)
- Android Jetpack: Paging
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Переход на Paging 3
- Как показывать списки с разбивкой по страницам
- Сбор данных со страниц