صفحهبندی — تکنیک بارگذاری دادهها به صورت صفحهبهصفحه است که در اپلیکیشنهای موبایل و سرویسهای وب برای کار با مجموعههای بزرگ رکوردها استفاده میشود. طبق Android Developers Documentation (2025)، پیادهسازی صحیح صفحهبندی بار API را کاهش میدهد، در ترافیک صرفهجویی میکند و تجربه کاربری را بهبود میبخشد. بارگذاری صفحهای به اپلیکیشن اجازه میدهد محتوا را به تدریج و بدون انتظار برای بارگذاری کامل تمام دادهها نمایش دهد.
نکات اصلی
صفحهبندی (از انگلیسی pagination — تقسیم به صفحات) — تکنیکی برای تقسیم یک مجموعه داده بزرگ به بخشهای متوالی (صفحات) است. در اپلیکیشنهای موبایل، صفحهبندی هنگام بارگذاری لیست پیامها، فید خبری، کاتالوگ محصولات، تاریخچه سفارشات و هر مجموعه دیگری با تعداد بالقوه نامحدود رکورد استفاده میشود.
بدون صفحهبندی، اپلیکیشن مجبور به بارگذاری همه دادهها به یکباره است که منجر به انتظار طولانی، مصرف بالای ترافیک و عملکرد ناپایدار در دستگاههای ضعیف میشود. درخواست API با صفحهبندی فقط یک تکه داده و متا-اطلاعات برای بارگذاری تکه بعدی را برمیگرداند — بنابراین اپلیکیشن حجم اطلاعات دریافتی را کنترل میکند.
معیارهای اصلی صفحهبندی: اندازه صفحه (page size) — تعداد رکوردها در یک صفحه (معمولاً 10-50)، و شماره صفحه یا نشانگر — اشارهگر به موقعیت فعلی در مجموعه. انتخاب اندازه صفحه به نوع داده بستگی دارد: برای عناصر فشرده (نامها) 20-30 کافی است، برای کارتهای دارای تصویر — 10-15.
دستگاههای موبایل منابع محدودی دارند: حجم حافظه رم، سرعت پردازنده و محدودیت ترافیک. صفحهبندی سه وظیفه کلیدی را حل میکند: کاهش مصرف حافظه (فقط عناصر قابل مشاهده در حافظه ذخیره میشوند)، تسریع نمایش اولیه (اولین تکه سریعتر از کل مجموعه بارگذاری میشود) و صرفهجویی در ترافیک (دادهها فقط وقتی کاربر لیست را اسکرول میکند بارگذاری میشوند).
چهار نوع اصلی صفحهبندی وجود دارد که هر کدام وظایف خاصی را حل میکنند. انتخاب روش به نیازهای سازگاری دادهها، معماری 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 رکوردها را میشمارد: «۲۰ تا را رد کن، ۱۰ تای بعدی را برگردان». اگر بین بارگذاریها رکورد جدیدی اضافه شود — شمارهبندی به هم میریزد. Cursor از شناسه یکتای آخرین رکورد استفاده میکند: «۱۰ رکورد بعد از ID = 100 را برگردان». رکوردهای جدید روی موقعیت تأثیر نمیگذارند.
برای اپلیکیشنهای موبایل 10-25 عنصر بهینه است. برای لیستهای دارای تصویر — 5-10، برای فیدهای متنی — 20-30. اندازه به اندازه متوسط هر عنصر بستگی دارد: هر چه عنصر سنگینتر باشد، صفحه برای نمایش سریع باید کوچکتر باشد.
از کتابخانه Paging 3 از Android Jetpack استفاده کنید. این کتابخانه PagingSource برای بارگذاری، PagingData برای جریان واکنشگرا و PagingDataAdapter برای بارگذاری خودکار هنگام اسکرول فراهم میکند. کتابخانه از صفحهبندی Offset، Cursor و Keyset از طریق PagingSource سفارشی پشتیبانی میکند.
اسکرول بینهایت — الگوی UI است که در آن تکه جدید داده هنگام نزدیک شدن به انتهای لیست به طور خودکار بارگذاری میشود. صفحهبندی — مکانیزم بارگذاری داده به صورت تکهتکه است، و اسکرول بینهایت یکی از روشهای نمایش آن است. جایگزین — دکمه «بارگذاری بیشتر».
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید