Обзор библиотеки Paging 2. Часть Android Jetpack .

Библиотека подкачки помогает загружать и отображать небольшие фрагменты данных одновременно. Загрузка частичных данных по требованию снижает использование пропускной способности сети и системных ресурсов.

В этом руководстве представлено несколько концептуальных примеров библиотеки, а также обзор того, как она работает. Чтобы просмотреть полные примеры функционирования этой библиотеки, попробуйте кодовую лабораторию и примеры из раздела дополнительных ресурсов .

Настраивать

Чтобы импортировать компоненты подкачки в приложение 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
}

Котлин

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

Ключевым компонентом библиотеки подкачки является класс PagedList , который загружает фрагменты данных вашего приложения или страницы . Поскольку требуется больше данных, они выгружаются в существующий объект PagedList . Если какие-либо загруженные данные изменяются, новый экземпляр PagedList передается наблюдаемому держателю данных из объекта на основе LiveData или RxJava2. По мере создания объектов PagedList пользовательский интерфейс вашего приложения представляет их содержимое, при этом соблюдая жизненные циклы ваших контроллеров пользовательского интерфейса.

В следующем фрагменте кода показано, как можно настроить модель представления вашего приложения для загрузки и представления данных с помощью держателя LiveData объектов PagedList :

Котлин

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

Ява

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 для организации данных вашего приложения, но если вы хотите хранить данные другим способом, вы также можете предоставить свою собственную фабрику источников данных .

Котлин

@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>
}

Ява

@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 . Эти классы работают вместе для извлечения и отображения контента по мере его загрузки, предварительной выборки контента, находящегося вне поля зрения, и анимации изменений контента.

Дополнительную информацию см. в руководстве по отображению постраничных списков .

Поддержка различных архитектур данных

Библиотека подкачки поддерживает следующие архитектуры данных:

  • Обслуживается только с внутреннего сервера.
  • Хранится только в базе данных на устройстве.
  • Комбинация других источников с использованием базы данных на устройстве в качестве кеша.

На рис. 1 показано, как передаются данные в каждом из этих сценариев архитектуры. В случае решения только для сети или только для базы данных данные передаются непосредственно в модель пользовательского интерфейса вашего приложения. Если вы используете комбинированный подход, данные передаются с вашего внутреннего сервера в базу данных на устройстве, а затем в модель пользовательского интерфейса вашего приложения. Время от времени у конечной точки каждого потока данных заканчиваются данные для загрузки, и в этот момент она запрашивает дополнительные данные от компонента, который предоставил данные. Например, когда в базе данных на устройстве заканчиваются данные, она запрашивает дополнительные данные с сервера.

Диаграммы потоков данных
Рисунок 1. Как данные проходят через каждую из архитектур, поддерживаемых библиотекой подкачки

Оставшаяся часть этого раздела содержит рекомендации по настройке каждого варианта использования потока данных.

Только сеть

Чтобы отобразить данные с внутреннего сервера, используйте синхронную версию Retrofit API для загрузки информации в собственный объект DataSource .

Только база данных

Настройте RecyclerView для наблюдения за локальным хранилищем, желательно с использованием библиотеки постоянства комнаты . Таким образом, всякий раз, когда данные вставляются или изменяются в базе данных вашего приложения, эти изменения автоматически отражаются в RecyclerView , отображающем эти данные.

Сеть и база данных

После того как вы начали наблюдать за базой данных, вы можете прослушивать, когда в базе данных заканчиваются данные, с помощью PagedList.BoundaryCallback . Затем вы можете получить больше элементов из своей сети и вставить их в базу данных. Если ваш пользовательский интерфейс наблюдает за базой данных, это все, что вам нужно сделать.

Обработка сетевых ошибок

При использовании сети для получения или разбиения по страницам данных, которые вы отображаете с помощью библиотеки подкачки, важно не рассматривать сеть как «доступную» или «недоступную» все время, поскольку многие соединения являются прерывистыми или нестабильными:

  • Конкретный сервер может не ответить на сетевой запрос.
  • Возможно, устройство подключено к медленной или слабой сети.

Вместо этого ваше приложение должно проверять каждый запрос на наличие сбоев и максимально корректно восстанавливаться в случаях, когда сеть недоступна. Например, вы можете предоставить пользователям кнопку «Повторить попытку», чтобы они могли выбрать, если шаг обновления данных не работает. Если на этапе пейджинга данных возникает ошибка, лучше всего автоматически повторить запросы на пейджинг.

Обновите существующее приложение

Если ваше приложение уже использует данные из базы данных или внутреннего источника, его можно напрямую обновить до функциональных возможностей, предоставляемых библиотекой подкачки. В этом разделе показано, как обновить приложение, имеющее общий существующий дизайн.

Индивидуальные пейджинговые решения

Если вы используете пользовательские функции для загрузки небольших подмножеств данных из источника данных вашего приложения, вы можете заменить эту логику логикой из класса 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 для асинхронной загрузки и отображения групп информации, библиотека подкачки упрощает загрузку данных:

  • Ваши данные не обязательно должны быть позиционными. Библиотека подкачки позволяет загружать данные непосредственно из серверной части, используя ключи, предоставляемые сетью.
  • Ваши данные могут быть неисчислимо большими. Используя библиотеку подкачки, вы можете загружать данные на страницы до тех пор, пока не останется никаких данных.
  • Вам будет легче наблюдать за своими данными. Библиотека подкачки может представлять ваши данные, хранящиеся в ViewModel вашего приложения, в наблюдаемой структуре данных.

Примеры баз данных

Следующие фрагменты кода показывают несколько возможных способов совместной работы всех частей.

Наблюдение за постраничными данными с помощью LiveData

Следующий фрагмент кода показывает, как все части работают вместе. Когда концертные события добавляются, удаляются или изменяются в базе данных, содержимое RecyclerView автоматически и эффективно обновляется:

Котлин

@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
        }
    }
}

Ява

@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 :

Котлин

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

Ява

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();
    }
}

Затем вы можете начать и прекратить наблюдение за данными, используя код в следующем фрагменте:

Котлин

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()
    }
}

Ява

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 .

Оставьте отзыв

Поделитесь с нами своими отзывами и идеями через эти ресурсы:

Трекер проблем
Сообщайте о проблемах, чтобы мы могли исправить ошибки.

Дополнительные ресурсы

Чтобы узнать больше о библиотеке подкачки, обратитесь к следующим ресурсам.

Образцы

Кодлабы

Видео

{% дословно %} {% дословно %} {% дословно %} {% дословно %}