Η σελιδοποίηση — τεχνική φόρτωσης δεδομένων σελίδα-σελίδα, που χρησιμοποιείται σε εφαρμογές κινητών και διαδικτυακές υπηρεσίες για εργασία με μεγάλα σύνολα εγγραφών. Σύμφωνα με το 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης