onPause — είναι η μέθοδος του κύκλου ζωής Android που καλείται όταν το Activity χάνει την εστίαση εισόδου, αλλά παραμένει μερικώς ορατό στην οθόνη. Το σύστημα καλεί την onPause πριν ένα νέο Activity βγει στο προσκήνιο, κατά το άνοιγμα παραθύρου διαλόγου, όταν πατιέται το κουμπί "Πρόσφατες εφαρμογές" ή κατά την είσοδο κλήσης. Αυτή η μέθοδος — το τελευταίο εγγυημένο σημείο για αποθήκευση δεδομένων χρήστη, καθώς μετά από onStop και onDestroy το σύστημα μπορεί να τερματίσει τη διαδικασία χωρίς πρόσθετες κλήσεις. Μέσα στην onPause ο προγραμματιστής αποθηκεύει πρόχειρα, θέτει σε παύση κινούμενα σχέδια, απελευθερώνει την κάμερα και γράφει την τρέχουσα κατάσταση UI στο SharedPreferences. Περισσότερα για τον πλήρη κύκλο ζωής Activity διαβάστε στο άρθρο Activity Lifecycle.
Κύρια σημεία
onPause — η τέταρτη μέθοδος του κύκλου ζωής Activity που καλείται όταν η οθόνη χάνει την εστίαση εισόδου, αλλά παραμένει μερικώς ορατή για τον χρήστη. Αυτή είναι μια "μεταβατική" κατάσταση μεταξύ της ενεργής λειτουργίας της εφαρμογής και της απόκρυψής της. Το σύστημα καλεί την onPause στα ακόλουθα σενάρια: άνοιγμα άλλου Activity (νέα οθόνη καλύπτει την τρέχουσα), εμφάνιση παραθύρου διαλόγου (Dialog, PopupWindow, Snackbar δεν καλούν onPause, αλλά το DialogFragment καλεί), πάτημα του κουμπιού "Πρόσφατες εφαρμογές", εισερχόμενη κλήση, πάτημα του κουμπιού "Τροφοδοσία" για κλείδωμα οθόνης.
Το κύριο καθήκον της onPause — προετοιμασία της εφαρμογής για πιθανή απόκρυψη ή καταστροφή. Αυτό είναι το τελευταίο σημείο στον κύκλο ζωής όπου ο προγραμματιστής μπορεί να είναι σίγουρος ότι ο κώδικάς του θα εκτελεστεί πριν το σύστημα συνεχίσει τη μετάβαση σε άλλο στοιχείο. Μετά την onPause το σύστημα καλεί την onStop (αν το Activity κρύβεται εντελώς), μετά το οποίο η καταστροφή της διαδικασίας μπορεί να συμβεί ανά πάσα στιγμή χωρίς πρόσθετες ειδοποιήσεις.
Σύμφωνα με την τεκμηρίωση Android Developers (2025), η onPause πρέπει να είναι όσο το δυνατόν πιο ελαφριά και γρήγορη. Μέχρι η onPause να επιστρέψει τον έλεγχο, το σύστημα δεν μπορεί να ξεκινήσει το επόμενο Activity — αυτό σημαίνει ότι ο χρήστης βλέπει καθυστέρηση στη μετάβαση μεταξύ οθονών. Η Google συνιστά η onPause να ολοκληρώνεται σε λιγότερο από 100 χιλιοστά του δευτερολέπτου, και όλες οι χρονοβόρες λειτουργίες (αποθήκευση σε βάση δεδομένων, εγγραφή σε δίσκο) να εκτελούνται ασύγχρονα μέσω coroutine ή apply().
Στο Activity, η μέθοδος onPause καλείται κάθε φορά που η οθόνη σταματά να είναι ενεργή, αλλά μπορεί να συνεχίσει να εμφανίζεται μερικώς. Τυπικό παράδειγμα: ο χρήστης ανοίγει την εφαρμογή "Χάρτες", πατά "Κοινοποίηση τοποθεσίας" και πάνω από τους Χάρτες ανοίγει ένας διάλογος συστήματος επιλογής εφαρμογής. Το Activity των Χαρτών λαμβάνει onPause, αλλά παραμένει ορατό κάτω από τον διάλογο. Όταν ο διάλογος κλείσει, οι Χάρτες λαμβάνουν onResume χωρίς κλήση onStart (η οθόνη δεν είχε κρυφτεί εντελώς).
class NoteEditorActivity : AppCompatActivity() {
private var binding: ActivityNoteEditorBinding? = null
private val prefs by lazy {
getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
}
override fun onPause() {
super.onPause()
// Αποθηκεύουμε το πρόχειρο σημείωσης — ασύγχρονα
prefs.edit()
.putString("draft_title", binding?.titleInput?.text.toString())
.putString("draft_body", binding?.bodyInput?.text.toString())
.putLong("draft_timestamp", System.currentTimeMillis())
.apply()
// Θέτουμε σε παύση το βίντεο
binding?.videoPlayer?.pause()
// Απελευθερώνουμε αποκλειστικούς πόρους
releaseCamera()
releaseAudioFocus()
}
override fun onResume() {
super.onResume()
// Επαναφέρουμε το πρόχειρο
binding?.titleInput?.setText(prefs.getString("draft_title", ""))
binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
acquireCamera()
acquireAudioFocus()
}
}
Το παράδειγμα NoteEditorActivity δείχνει τη σωστή εργασία με onPause: αποθήκευση πρόχειρου στο SharedPreferences μέσω apply(), παύση του αρχείου βίντεο, απελευθέρωση κάμερας και εστίασης ήχου. Κάθε κλήση — ελαφριά και γρήγορη, χωρίς να μπλοκάρει το νήμα UI αρκετά για ANR. Προσέξτε τη σειρά: η super.onPause() καλείται στην πρώτη γραμμή — αυτό εγγυάται ότι η λογική συστήματος θα εκτελεστεί ακόμα και σε εξαίρεση στον κώδικα χρήστη.
onPause — το τελευταίο σημείο όπου ο προγραμματιστής μπορεί να αποθηκεύσει εγγυημένα δεδομένα χρήστη πριν η εφαρμογή κρυφτεί ή τερματιστεί από το σύστημα. Μετά την onStop το σύστημα μπορεί να καταστρέψει τη διαδικασία σε έλλειψη μνήμης χωρίς να καλέσει την onDestroy. Η μέθοδος onSaveInstanceState() καλείται μετά την onPause, αλλά το Bundle της δεν προορίζεται για μακροπρόθεσμη αποθήκευση — ζει μόνο μέχρι το επόμενο onCreate.
SharedPreferences με ασύγχρονο apply() — ο βέλτιστος τρόπος αποθήκευσης μικρού όγκου δεδομένων στην onPause. Σε αντίθεση με την commit(), η οποία σύγχρονα γράφει δεδομένα στο δίσκο και επιστρέφει boolean, η apply() αποθηκεύει αμέσως δεδομένα στη μνήμη και προγραμματίζει ασύγχρονη εγγραφή στο δίσκο. Αυτό διαρκεί λιγότερο από 1 χιλιοστό του δευτερολέπτου στο νήμα UI έναντι 10–100 χιλιοστών για την commit().
override fun onPause() {
super.onPause()
// ❌ Κακό: σύγχρονη εγγραφή μπλοκάρει το νήμα
// prefs.edit().putInt("score", score).commit()
// ✅ Καλό: ασύγχρονη εγγραφή
prefs.edit().putInt("score", score).apply()
// Για σύνθετα αντικείμενα — προσωρινή αποθήκευση σε ViewModel
viewModel.saveState()
}
Για δομημένα δεδομένα (SQLite μέσω Room) στην onPause χρησιμοποιούνται coroutine με lifecycleScope. Το ViewModelScope ακυρώνει αυτόματα το coroutine κατά την καταστροφή του ViewModel, αποτρέποντας την εγγραφή σε κλειστή βάση δεδομένων. Η εγγραφή μέσω Room με coroutine διαρκεί 5–15 χιλιοστά και δεν μπλοκάρει το νήμα UI.
// Στο ViewModel:
fun saveDraft(title: String, body: String) {
viewModelScope.launch(Dispatchers.IO) {
noteDao.insert(NoteDraft(title = title, body = body))
}
}
// Στο Activity.onPause:
viewModel.saveDraft(
binding?.titleInput?.text.toString(),
binding?.bodyInput?.text.toString()
)
onPause στο Fragment καλείται όταν το Fragment σταματά να είναι ενεργό, αλλά μπορεί να παραμείνει ορατό. Αυτό συμβαίνει όταν: το Fragment αντικαθίσταται από άλλο Fragment μέσω FragmentTransaction; το Fragment σταματά να είναι η τρέχουσα σελίδα στο ViewPager; το Activity που περιέχει το Fragment λαμβάνει onPause. Η αλληλεπίδραση μεταξύ onPause του Activity και onPause του Fragment είναι αυστηρά ιεραρχική: πρώτα το Activity λαμβάνει onPause, μετά όλα τα Fragment του.
class MapFragment : Fragment() {
private var mapController: MapController? = null
override fun onPause() {
super.onPause()
mapController?.stopFollowMode()
binding?.mapContainer?.alpha = 0.7f
}
override fun onResume() {
super.onResume()
binding?.mapContainer?.alpha = 1.0f
if (isVisible) {
mapController?.startFollowMode()
}
}
}
Ιδιαιτερότητα εργασίας με χάρτες στην onPause: τα Google Maps και Yandex Maps καταναλώνουν σημαντικούς πόρους GPU σε ενεργή λειτουργία παρακολούθησης (follow mode). Κατά την απώλεια εστίασης, έχει νόημα να απενεργοποιήσετε την κίνηση του χάρτη και να μειώσετε τη συχνότητα ενημέρωσης δεικτών, και κατά την επιστροφή εστίασης — να επαναφέρετε την πλήρη λειτουργικότητα. Αυτό βελτιώνει την απόδοση και μειώνει την κατανάλωση ενέργειας κατά την εναλλαγή μεταξύ οθονών.
Μία από τις πιο συνηθισμένες συγχύσεις μεταξύ αρχάριων προγραμματιστών Android — η μη κατανόηση της διαφοράς μεταξύ onPause και onStop. Ας εξετάσουμε κάθε σενάριο και ας προσδιορίσουμε τη σωστή μέθοδο.
| Σενάριο | onPause | onStop |
|---|---|---|
| Άνοιγμα παραθύρου διαλόγου | Καλείται | Δεν καλείται |
| Άνοιγμα νέου Activity (μη διαφανούς) | Καλείται | Καλείται |
| Πάτημα κουμπιού "Αρχική" | Καλείται | Καλείται |
| Κλείδωμα οθόνης | Καλείται | Καλείται |
| Εισερχόμενη κλήση | Καλείται | Καλείται |
| Διαφανές Activity πάνω από το τρέχον | Καλείται | Δεν καλείται |
| Split Screen (μισή οθόνη) | Καλείται | Δεν καλείται |
| PiP (Picture-in-Picture) | Καλείται | Δεν καλείται |
Κύριος κανόνας: η onPause καλείται σε κάθε απώλεια εστίασης, η onStop — μόνο σε πλήρη απώλεια ορατότητας. Αν το Activity παραμένει ορατό (ακόμα και μερικώς), η onStop δεν καλείται. Αυτό είναι κρίσιμο για λειτουργίες Split Screen, PiP και διαφανή Activity — εδώ οι onPause/onResume λειτουργούν, αλλά οι onStart/onStop όχι.
onPause — η πιο κρίσιμη χρονικά μέθοδος του κύκλου ζωής, καθώς μπλοκάρει την απόδοση του επόμενου Activity. Το σύστημα περιμένει την ολοκλήρωση της onPause του τρέχοντος Activity πριν εμφανίσει το νέο. Αν η onPause διαρκεί περισσότερο από 100 χιλιοστά του δευτερολέπτου, ο χρήστης παρατηρεί καθυστέρηση μετάβασης; αν περισσότερο από 5 δευτερόλεπτα — το σύστημα εμφανίζει ANR.
Ο Οδηγός Απόδοσης Android Google (2025) δίνει τις ακόλουθες συστάσεις για onPause: μην εκτελείτε αιτήματα δικτύου — πρέπει να ακυρωθούν ή να μεταφερθούν στο WorkManager; μην γράφετε μεγάλα αρχεία στο δίσκο — χρησιμοποιήστε BufferedWriter σε νήμα παρασκηνίου; μην εκτελείτε σύνθετα ερωτήματα SQL — οι λειτουργίες Room πρέπει να είναι ασύγχρονες μέσω coroutine; αποφύγετε τη δημιουργία νέων αντικειμένων — η συλλογή σκουπιδιών στην onPause επιδεινώνει την καθυστέρηση; χρησιμοποιήστε apply() αντί για commit() για SharedPreferences.
override fun onPause() {
super.onPause()
// ❌ Κακό: αίτημα HTTP μπλοκάρει το UI
// val response = api.syncSave(data).execute()
// ❌ Κακό: σύγχρονη εγγραφή σε αρχείο
// FileOutputStream(file).write(data)
// ✅ Καλό: ασύγχρονη αποθήκευση
lifecycleScope.launch {
withContext(Dispatchers.IO) {
api.saveData(data)
fileDao.write(data)
}
}
// ✅ Καλό: ελαφριά εγγραφή σε SharedPreferences
prefs.edit().putString("key", value).apply()
}
Η προφίλ της onPause μέσω Android Studio Profiler (γραφικό CPU) δείχνει τον ακριβή χρόνο εκτέλεσης. Αν η onPause διαρκεί περισσότερο από 100 ms, ο Profiler επισημαίνει τη μέθοδο με κίτρινο, και περισσότερο από 500 ms — με κόκκινο. Σε εμπορικά έργα της IT Sectr χρησιμοποιούμε δοκιμές Macrobenchmark που ελέγχουν αυτόματα τον χρόνο μετάβασης μεταξύ Activity και σηματοδοτούν υποβάθμιση απόδοσης στο pipeline CI.
Ακόμα και έμπειροι προγραμματιστές κάνουν λάθη στην onPause. Ας εξετάσουμε πέντε τυπικά προβλήματα και τις λύσεις τους.
Η κλήση Room DAO με σύγχρονο ερώτημα (.executeAsObservable() χωρίς coroutine) στην onPause μπλοκάρει το νήμα UI για 10–50 ms. Αν εκείνη τη στιγμή συμβεί GC ή ανταγωνισμός για εγγραφή στη βάση δεδομένων, η καθυστέρηση μπορεί να φτάσει 200–500 ms. Λύση: χρησιμοποιήστε coroutine με Dispatchers.IO ή apply() για SharedPreferences.
Η onPause δεν είναι μέρος για εγγραφή ακροατών. Αν εγγράψετε ένα BroadcastReceiver στην onPause, θα παραμείνει ενεργό όταν το Activity δεν είναι πλέον ορατό. Η εγγραφή πρέπει να γίνεται μόνο σε onStart/onResume, και σε onPause/onStop — μόνο απεγγραφή. Εξαίρεση — API που βασίζονται σε Intent και απαιτούν εγγραφή πριν από την κλήση.
Αν στην onPause συμβεί μη διαχειρισμένη εξαίρεση, το σύστημα δεν καλεί τις onStop και onDestroy. Το Activity παραμένει παγωμένο σε απροσδιόριστη κατάσταση και η onResume κατά την επιστροφή μπορεί να μην αποκαταστήσει σωστά τους απελευθερωμένους πόρους. Λύση: τυλίξτε κρίσιμες λειτουργίες σε try/catch με καταγραφή μέσω Log.e().
Δεν χρειάζεται να αποθηκεύετε στην onPause δεδομένα που μπορούν εύκολα να ανακτηθούν. Για παράδειγμα, τα αποτελέσματα αιτημάτων API αποθηκεύονται προσωρινά σε Room ή DataStore τη στιγμή λήψης, όχι στην onPause. Αποθηκεύστε μόνο όσα ο χρήστης εισήγαγε χειροκίνητα και δεν μπορεί να ανακτήσει αυτόματα — κείμενο σε πεδία, επιλεγμένα στοιχεία, θέση κύλισης.
Το super.onPause() πρέπει να καλείται, αλλά σε αντίθεση με το onCreate, η απουσία του δεν προκαλεί άμεσο crash. Το σύστημα "συγχωρεί" την παράλειψη του super στην onPause, αλλά η εσωτερική μηχανή καταστάσεων μεταβαίνει σε εσφαλμένη κατάσταση. Η επόμενη κλήση onResume μπορεί να μην αποκαταστήσει την εστίαση εισόδου και το Activity παραμένει "παγωμένο". Πάντα να καλείτε το super.onPause() όσο το δυνατόν νωρίτερα.
Συχνές Ερωτήσεις
Η κλήση finish() στην onPause θα τερματίσει το Activity αμέσως μετά την επιστροφή από τη μέθοδο. Αυτό είναι σωστό σενάριο αν κατά την απώλεια εστίασης πρέπει να κλείσει η οθόνη (για παράδειγμα, οθόνη εξουσιοδότησης κατά την ελαχιστοποίηση της εφαρμογής). Ωστόσο, η finish() ξεκινά τον πλήρη κύκλο τερματισμού: onStop → onDestroy, που προσθέτει καθυστέρηση στη μετάβαση. Χρησιμοποιήστε finish() στην onPause μόνο όταν είναι πραγματικά απαραίτητο.
onPause — για αποθήκευση δεδομένων που πρέπει να επιβιώσουν τον τερματισμό της διαδικασίας (πρόχειρα σε SharedPreferences/Room). onSaveInstanceState — για αποθήκευση προσωρινής κατάστασης UI που χρειάζεται μόνο μέχρι το επόμενο onCreate (θέση κύλισης, επιλεγμένη καρτέλα). Το Bundle της onSaveInstanceState δεν αποθηκεύεται κατά τον πλήρη τερματισμό της εφαρμογής — υπάρχει μόνο στη μνήμη. Τα δεδομένα onPause αποθηκεύονται στο δίσκο και επιβιώνουν από επανεκκίνηση.
Δεν συνιστάται. Το άνοιγμα διαλόγου ή αναδυόμενου παραθύρου στην onPause οδηγεί σε WindowLeakException αν το Activity έχει ήδη τερματιστεί. Αν χρειάζεται να εμφανίσετε ειδοποίηση κατά την απώλεια εστίασης, χρησιμοποιήστε το NotificationManager (ειδοποιήσεις συστήματος) — είναι ασφαλές και αναμενόμενο για τον χρήστη. Για καθυστερημένες ενέργειες, χρησιμοποιήστε AlarmManager ή WorkManager.
Η onPause καλείται εγγυημένα πριν το Activity σταματήσει να είναι ενεργό. Η onStop μπορεί να μην κληθεί αν το σύστημα σκοτώσει τη διαδικασία για να ελευθερώσει μνήμη — σε αυτήν την περίπτωση ούτε η onDestroy καλείται. Η onPause είναι η μοναδική μέθοδος μετά την onResume που καλείται πάντα, ανεξάρτητα από τον λόγο απώλειας εστίασης. Γι' αυτό όλα τα κρίσιμα δεδομένα αποθηκεύονται ακριβώς στην onPause.
Για τη δοκιμή της onPause χρησιμοποιείται Robolectric ή FragmentScenario από το AndroidX Test. FragmentScenario.create() → moveToState(State.STARTED) → moveToState(State.RESUMED) → moveToState(State.STARTED) καλεί διαδοχικά την onPause. Στη συνέχεια ελέγχεται αν τα δεδομένα αποθηκεύτηκαν στο SharedPreferences ή αν η κάμερα απελευθερώθηκε μέσω αντικειμένου mock. Το Robolectric 4.12+ υποστηρίζει εξομοίωση onPause/onResume χωρίς φυσική συσκευή.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης