Пагинацията — техника за странично зареждане на данни, използвана в мобилни приложения и уеб услуги за работа с големи набори от записи. Според Android Developers Documentation (2025), правилното имплементиране на пагинация намалява натоварването на API, спестява трафик и подобрява потребителското изживяване. Постраничното зареждане позволява на приложението да показва съдържание постепенно, без да чака пълното зареждане на всички данни.
Основни моменти
Пагинацията (от англ. pagination — разделяне на страници) — техника за разделяне на голям набор от данни на последователни порции (страници). В мобилните приложения пагинацията се използва при зареждане на списъци с съобщения, новинарски емисии, каталози с продукти, история на поръчки и всякакви други колекции с потенциално неограничен брой записи.
Без пагинация приложението е принудено да зарежда всички данни наведнъж, което води до дълго чакане, голям разход на трафик и нестабилна работа на слаби устройства. API заявка с пагинация връща само една порция данни и мета-информация за зареждане на следващата — по този начин приложението контролира обема на получената информация.
Основните метрики на пагинацията: размер на страницата (page size) — брой записи на една страница (обикновено 10-50), и номер на страница или курсор — указател на текущата позиция в набора. Изборът на размер на страницата зависи от типа данни: за компактни елементи (имена) са достатъчни 20-30, за картички с изображения — 10-15.
Мобилните устройства имат ограничени ресурси: обем на RAM, скорост на процесора и лимити на трафик. Пагинацията решава три ключови задачи: намаляване на консумацията на памет (в паметта се съхраняват само видимите елементи), ускоряване на първото показване (първата порция се зарежда по-бързо от целия набор) и икономия на трафик (данните се зареждат само когато потребителят превърта списъка).
Съществуват четири основни вида пагинация, всеки от които решава специфични задачи. Изборът на метод зависи от изискванията за консистентност на данните, архитектура на API, тип на съхранение и допустимата сложност на имплементация от страна на клиента и сървъра.
| Тип | Принцип на работа | Стабилност | Скорост при големи обеми |
|---|---|---|---|
| Offset | LIMIT + OFFSET в SQL | ниска | спада с нарастване на OFFSET |
| Cursor | WHERE id > last_id | висока | стабилна (O(log n)) |
| Keyset | WHERE key > last_key | висока | стабилна (O(log n)) |
| Time-based | WHERE created_at < last_time | средна | стабилна с индекс |
Offset пагинацията е подходяща за статични или рядко актуализирани набори от данни, когато е важна простата имплементация. Cursor и Keyset — за динамични данни с чести вмъквания. Time-based — за хронологични емисии, където записите са подредени по време на създаване. Стандартът GraphQL Relay използва cursor пагинация като единствен препоръчителен метод.
Offset пагинацията — най-простият вид постранично зареждане. Клиентът предава параметри page и limit (или offset и limit), сървърът прилага SQL OFFSET и LIMIT. Например page=2, limit=20 ще върне записи от 21 до 40. Този метод е интуитивно разбираем и лесно се имплементира на всеки технологичен стек.
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items")
async def get_items(
page: int = Query(default=1, ge=1),
limit: int = Query(default=20, le=100)
):
offset = (page - 1) * limit
items = await fetch_items(offset, limit)
total = await count_items()
return {
"items": items,
"total": total,
"page": page,
"pages": (total + limit - 1) // limit
}
Основният недостатък на Offset пагинацията — проблемът с пропуснати и дублиращи се записи. Ако между две заявки се добавят нови записи в таблицата, OFFSET се измества: потребителят може да види един и същ запис два пъти или да пропусне нов. Това е критично за новинарски емисии и чатове, където консистентността е важна.
Друг проблем — спад на производителността при големи OFFSET. Базата данни трябва да сканира и прескочи първите offset записа преди да върне резултата. При offset=100000 дори с LIMIT 20, сървърът ще отдели забележимо време за сканиране. PostgreSQL и MySQL демонстрират линейно намаляване на скоростта с нарастване на OFFSET.
Offset пагинацията остава най-добрият избор за: административни панели (данните се променят рядко, необходима е навигация между страници), отчети и исторически логове (фиксиран отрязък от данни), каталози с филтриране (може да се премине на произволна страница). Offset е и най-лесният за имплементиране от страна на клиента — RecyclerView с Paging 3 го поддържа веднага.
Keyset пагинацията използва уникален ключ (обикновено първичен ключ) за филтриране на записи. Вместо OFFSET заявката използва WHERE id > last_seen_id. Това осигурява стабилна производителност независимо от броя записи и липса на дубликати при вмъквания, тъй като новите записи винаги имат по-голямо id.
-- Offset-пагинация (проблемная)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;
-- Keyset-пагинация (стабильная)
SELECT * FROM posts
WHERE id > 100
ORDER BY id
LIMIT 20;
Time-based пагинацията (или времеви курсор) използва времевия маркер created_at за навигация. Клиентът предава timestamp на последния зареден запис, сървърът връща записи, създадени преди или след този маркер. Методът е популярен в социалните мрежи и новинарските емисии, където редът на записите се определя от времето на публикуване.
Характеристика на Time-based пагинацията — възможни дубликати, ако два записа са създадени в една и съща милисекунда. За отстраняване на този проблем се комбинира time-based ключ с уникално id: WHERE (created_at, id) < (last_time, last_id). Такъв съставен курсор гарантира уникалността на всеки запис и точен ред.
Keyset пагинацията изисква колона с уникална и монотонно нарастваща стойност (автоинкрементиращо id, UUID v7). Time-based е подходяща за всяка таблица с created_at, но изисква допълнителна обработка на дубликати. Основната разлика: Keyset работи стабилно при всякакви операции на вмъкване, докато Time-based е чувствителна към идентични времеви маркери.
Изборът на тип пагинация зависи от характера на данните и изискванията за потребителско изживяване. По-долу са дадени препоръки за типични сценарии в мобилното разработване. Универсално решение не съществува — всеки метод има област на приложение, в която е оптимален.
Библиотеката Android Paging 3 поддържа всички типове пагинация чрез PagingSource. За Offset — PagingSource с ключ Int (page), за Cursor — с ключ String или Long (cursor). PagingSource автоматично управлява зареждането, кеширането и повторните опити при грешки.
class PostPagingSource(
private val api: PostApi
) : PagingSource<Long, Post>() {
override suspend fun load(
params: LoadParams<Long>
): LoadResult<Long, Post> {
val cursor = params.key ?: Long.MAX_VALUE
return try {
val response = api.getPosts(cursor, params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = response.items.lastOrNull()?.id
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Long, Post>): Long? {
return state.anchorPosition?.let {
state.closestItemToPosition(it)?.id
}
}
}
Размерът на страницата влияе на скоростта на зареждане и възприемането на производителност. За мобилни приложения оптималният диапазон е 10-25 елемента на страница. По-малко от 10 — твърде чести заявки към API и рязко превъртане. Повече от 25 — дълго зареждане на първата порция при бавни мрежи.
За изображения и видео размерът на страницата се намалява до 5-10, тъй като всеки елемент изисква допълнително време за зареждане на медия. За списъци с текстови елементи (коментари, логове) размерът може да се увеличи до 30-50 записа. Препоръчва се размерът на страницата да бъде конфигурируем чрез API, за да може клиентът да се адаптира към различни мрежови условия.
Често задавани въпроси
Пагинацията — зареждане на данни на порции, а не всички наведнъж. Като в книга: четете една страница, после прелиствате на следващата. В приложението това означава, че при превъртане на списъка се зарежда следващата порция данни, а не целият списък наведнъж, което спестява трафик и памет.
Offset брои записи: „пропусни 20, върни следващите 10„. Ако между зарежданията се добави нов запис — номерацията се измества. Cursor използва уникален идентификатор на последния запис: „върни 10 записа след ID = 100„. Новите записи не влияят на позицията.
За мобилни приложения оптимално е 10-25 елемента. За списъци с изображения — 5-10, за текстови емисии — 20-30. Размерът зависи от средния размер на един елемент: колкото по-тежък е елементът, толкова по-малка трябва да бъде страницата за бързо показване.
Използвайте библиотеката Paging 3 от Android Jetpack. Тя предоставя PagingSource за зареждане, PagingData за реактивен поток и PagingDataAdapter за автоматично зареждане при превъртане. Библиотеката поддържа Offset, Cursor и Keyset пагинация чрез персонализиран PagingSource.
Безкрайното превъртане — UI модел, при който нова порция данни се зарежда автоматично при приближаване до края на списъка. Пагинацията — механизъм за зареждане на данни на порции, а безкрайното превъртане е един от начините за неговото показване. Алтернативата е бутонът „Зареди още„.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също