DisposableEffect — τι είναι, απελευθέρωση πόρων στο Jetpack Compose

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-06-30 Χρόνος ανάγνωσης: 9 λεπ

DisposableEffect είναι μια composable-συνάρτηση στο Jetpack Compose που προορίζεται για λειτουργίες που απαιτούν ρητή αρχικοποίηση και επακόλουθο καθαρισμό πόρων. Σε αντίθεση με άλλα side-effect API, το DisposableEffect παρέχει ένα μπλοκ onDispose που εκτελείται εγγυημένα όταν το component εξέρχεται από τη σύνθεση ή όταν αλλάζει το κλειδί. Αυτό το καθιστά απαραίτητο για εργασία με εγγενείς συνδρομές, ακροατές αισθητήρων και πόρους υλικού. Σύμφωνα με το Android Developers Documentation (2025), το DisposableEffect συνιστάται σε όλα τα σενάρια όπου απαιτείται ζεύγος setup/teardown, ανάλογο με onStart/onStop στον κύκλο ζωής Activity.

Κύρια σημεία

  • DisposableEffect — side-effect API για ρύθμιση και εγγυημένο καθαρισμό πόρων.
  • onDispose — υποχρεωτικό μπλοκ που εκτελείται κατά την έξοδο από τη σύνθεση ή την αλλαγή κλειδιού.
  • Συγχρονισμός — σε αντίθεση με το LaunchedEffect, το DisposableEffect λειτουργεί σύγχρονα χωρίς coroutines.
  • Καθαρισμός — τυπικά σενάρια: διαγραφή από LiveData, κλείσιμο sockets, ακύρωση εγγραφής BroadcastReceiver.
  • Κλειδιά — κατά την αλλαγή κλειδιού εκτελείται onDispose για την παλιά τιμή και εκ νέου αρχικοποίηση με τη νέα.

Τι είναι το DisposableEffect στο Jetpack Compose

DisposableEffect είναι ένα βασικό εργαλείο για τη διαχείριση πόρων στο Jetpack Compose. Το κύριο χαρακτηριστικό του είναι η εγγυημένη κλήση του μπλοκ onDispose κατά το τέλος του κύκλου ζωής του composable-component. Αυτή η συμπεριφορά είναι κρίσιμη για την ανάπτυξη Android, όπου οι μη κλεισμένες συνδρομές σε υπηρεσίες συστήματος μπορεί να οδηγήσουν σε διαρροές μνήμης και κατάρρευση της εφαρμογής.

Σε αντίθεση με το LaunchedEffect, που λειτουργεί σε ασύγχρονο περιβάλλον coroutine, το DisposableEffect εκτελείται σύγχρονα. Αυτό σημαίνει ότι δεν μπορούν να κληθούν συναρτήσεις suspend μέσα σε αυτό. Ο συγχρονισμός εξασφαλίζει προβλεψιμότητα: μπορείτε να είστε σίγουροι ότι ο κώδικας αρχικοποίησης θα εκτελεστεί πριν από την πρώτη απόδοση, και ο κώδικας καθαρισμού — πριν αφαιρεθεί το component από τη μνήμη.

Σύμφωνα με την Τεκμηρίωση Jetpack Compose (2025), το DisposableEffect πρέπει να χρησιμοποιείται σε τέσσερα κύρια σενάρια: (1) εγγραφή σε υπηρεσίες συστήματος (αισθητήρες, LocationManager), (2) καταχώρηση BroadcastReceiver, (3) εργασία με βιβλιοθήκες βασισμένες σε callback που δεν υποστηρίζουν coroutines, (4) σύνδεση components Compose με συστήματα Legacy View μέσω AndroidView.

kotlin
class SensorManager(private val context: Context) {
    fun startListening(callback: (Float) -> Unit) { /* register */ }
    fun stopListening() { /* cancel */ }
}

@Composable
fun SensorDisplay() {
    val sensorManager = remember { SensorManager(context) }
    var value by remember { mutableStateOf(0f) }
    
    DisposableEffect(Unit) {
        sensorManager.startListening { value = it }
        onDispose { sensorManager.stopListening() }
    }
    
    Text("Αισθητήρας: $value")
}

Πώς λειτουργεί το DisposableEffect με onDispose

Ο εσωτερικός μηχανισμός του DisposableEffect βασίζεται στις φάσεις του κύκλου ζωής της σύνθεσης. Όταν ένα composable-component εισέρχεται στη σύνθεση, το DisposableEffect καλεί το μεταβιβαζόμενο μπλοκ κώδικα. Αυτό το μπλοκ επιστρέφει ένα αντικείμενο DisposableEffectResult που περιέχει το lambda onDispose. Η σύνθεση αποθηκεύει αυτό το αποτέλεσμα και καλεί το onDispose τη στιγμή που το component εγκαταλείπει τη σύνθεση — ανεξάρτητα από την αιτία (πλοήγηση, αλλαγή κατάστασης γονέα, αφαίρεση από LazyColumn).

Ο μηχανισμός κλειδιών στο DisposableEffect λειτουργεί ανάλογα με το LaunchedEffect: κατά την αλλαγή οποιουδήποτε κλειδιού, πρώτα εκτελείται το onDispose για την παλιά κατάσταση, στη συνέχεια το μπλοκ αρχικοποίησης επανεκκινείται με τα νέα κλειδιά. Αυτό επιτρέπει την εκ νέου διαμόρφωση του πόρου όταν αλλάζουν οι παράμετροί του. Για παράδειγμα, αν το κλειδί είναι το URL του socket, κατά την αλλαγή του το παλιό socket κλείνει και ένα νέο ανοίγει.

Σημαντικό: το μπλοκ onDispose είναι υποχρεωτικό στοιχείο του DisposableEffect. Αν δεν κληθεί το onDispose μέσα στο μπλοκ, ο κώδικας δεν θα μεταγλωττιστεί. Αυτή η απαίτηση του compiler διασφαλίζει ότι ο προγραμματιστής δεν θα ξεχάσει να προβλέψει τον καθαρισμό του πόρου, που είναι συνηθισμένη αιτία σφαλμάτων στη χειροκίνητη διαχείριση συνδρομών.

kotlin
// Σωστή χρήση με κλειδί
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// Πολλαπλοί πόροι σε ένα DisposableEffect
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect κατά των διαρροών μνήμης

Οι διαρροές μνήμης σε εφαρμογές Android προκύπτουν συχνά από μη καταχωρημένους ακροατές και συνδρομές που συνεχίζουν να κρατούν αναφορά σε Activity ή Context μετά το κλείσιμο της οθόνης. DisposableEffect λύνει αυτό το πρόβλημα σε επίπεδο framework: αν ο προγραμματιστής χρησιμοποίησε DisposableEffect για την καταχώρηση ενός ακροατή, το onDispose θα ακυρώσει εγγυημένα τη συνδρομή σε οποιοδήποτε σενάριο τερματισμού του component.

Αυτό είναι ιδιαίτερα κρίσιμο για LazyColumn και LazyGrid, όπου τα στοιχεία δημιουργούνται και καταστρέφονται συνεχώς κατά την κύλιση. Χωρίς DisposableEffect, κάθε στοιχείο που εξαφανίζεται από το ορατό πεδίο θα άφηνε μια ενεργή συνδρομή. Με DisposableEffect, το onDispose καλείται για κάθε εκφορτωμένο στοιχείο, διασφαλίζοντας ότι οι πόροι απελευθερώνονται αμέσως μόλις το στοιχείο εγκαταλείψει την οθόνη.

Σύμφωνα με το Android Performance Patterns (Google, 2025), η χρήση του DisposableEffect για όλες τις εγγενείς συνδρομές μειώνει τον αριθμό των διαρροών μνήμης σε εφαρμογές Compose κατά 60–70% σε σύγκριση με τη χειροκίνητη διαχείριση μέσω callbacks κύκλου ζωής. Το σύστημα παρακολουθεί το ίδιο τη στιγμή εξόδου από τη σύνθεση και εγγυάται την εκτέλεση του onDispose ακόμη και κατά το επείγον κλείσιμο της οθόνης.

ΠόροςΤι κάνει το DisposableEffectΧωρίς DisposableEffect
BroadcastReceiverregister + onDispose → unregisterReceiver παραμένει ενεργός
SensorManagerregisterListener + onDispose → unregisterΑισθητήρας συνεχίζει να στέλνει δεδομένα
Observable (όχι Flow)subscribe + onDispose → unsubscribeCallback κρατά αναφορά
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackΔιαρροή callback
Socket / Channelopen + onDispose → closeΣύνδεση παραμένει ανοιχτή

Εγγραφή σε αισθητήρες μέσω DisposableEffect

Ένα από τα πιο χαρακτηριστικά παραδείγματα χρήσης του DisposableEffect — εργασία με αισθητήρες συσκευής (επιταχυνσιόμετρο, γυροσκόπιο, μαγνητόμετρο). Οι αισθητήρες απαιτούν υποχρεωτική ακύρωση εγγραφής κατά τον τερματισμό, διαφορετικά συνεχίζουν να καταναλώνουν ενέργεια μπαταρίας και να στέλνουν δεδομένα ακόμα και μετά το κλείσιμο της οθόνης.

Πρακτικό παράδειγμα: εφαρμογή μέτρησης γωνίας κλίσης. Το DisposableEffect(Unit) καταχωρεί έναν ακροατή επιταχυνσιόμετρου κατά την εμφάνιση του component και ακυρώνει την εγγραφή στο onDispose. Τα δεδομένα του αισθητήρα μεταφέρονται στην κατάσταση μέσω mutableStateOf, το οποίο ενημερώνει αυτόματα το UI. Αν η οθόνη κυλίεται στο LazyColumn και το στοιχείο εξαφανιστεί, το onDispose ενεργοποιείται άμεσα — ο αισθητήρας σταματά να στέλνει δεδομένα για αυτό το στοιχείο.

Κατά την αλλαγή τύπου αισθητήρα (π.χ. από επιταχυνσιόμετρο σε γυροσκόπιο), το κλειδί sensorType αλλάζει, το onDispose ακυρώνει την παλιά συνδρομή και το νέο μπλοκ DisposableEffect καταχωρεί τον νέο αισθητήρα. Χωρίς κλειδιά, θα έπρεπε να ελέγχετε χειροκίνητα ποιος αισθητήρας είχε καταχωρηθεί προηγουμένως και να καλείτε το unregisterListener με τον σωστό listener — που είναι επιρρεπές σε σφάλματα.

kotlin
@Composable
fun SensorReadingScreen(sensorType: Int) {
    val context = LocalContext.current
    val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    var sensorValue by remember { mutableStateOf(0f) }
    
    DisposableEffect(sensorType) {
        val sensor = sensorManager.getDefaultSensor(sensorType)
        val listener = SensorEventListener { event, _ ->
            sensorValue = event.values[0]
        }
        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
        
        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }
    
    Text("Τιμή: $sensorValue")
}

Καταχώρηση BroadcastReceiver μέσω DisposableEffect

BroadcastReceiver είναι ένα κλασικό παράδειγμα API που απαιτεί υποχρεωτικό ζεύγος register / unregister. Σε εφαρμογή Compose, το DisposableEffect είναι ιδανικό για την καταχώρηση ενός δέκτη για τη διάρκεια ζωής μιας συγκεκριμένης οθόνης. Κατά την είσοδο στην οθόνη, ο BroadcastReceiver καταχωρείται με το κατάλληλο IntentFilter, κατά την έξοδο — ακυρώνεται αυτόματα στο onDispose.

Τυπικό σενάριο — παρακολούθηση κατάστασης δικτύου. Το DisposableEffect καταχωρεί έναν δέκτη στο ConnectivityManager που ειδοποιεί για αλλαγές στη σύνδεση δικτύου. Κατά την αλλαγή κατάστασης (WiFi / δεδομένα κινητής / χωρίς δίκτυο) η κατάσταση του composable ενημερώνεται και το UI εμφανίζει τον αντίστοιχο δείκτη. Όταν η οθόνη κλείνει, το onDispose ακυρώνει εγγυημένα την καταχώρηση — ακόμα κι αν η εφαρμογή πάει στο παρασκήνιο.

Για δέκτες με ContextCompat.registerReceiver και σημαία RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) η χρήση του DisposableEffect καθίσταται υποχρεωτική, καθώς το σύστημα απαιτεί ρητό καθορισμό του πεδίου δράσης του δέκτη. Το DisposableEffect διασφαλίζει ότι το πεδίο δράσης περιορίζεται στη διάρκεια ζωής της οθόνης, το οποίο συμβαδίζει με τις απαιτήσεις ασφαλείας των νέων εκδόσεων Android.

kotlin
@Composable
fun NetworkStatusBanner() {
    val context = LocalContext.current
    var isConnected by remember { mutableStateOf(true) }
    
    DisposableEffect(Unit) {
        val receiver = BroadcastReceiver { _, _ ->
            val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            isConnected = cm.getActiveNetwork() != null
        }
        IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
            context.registerReceiver(receiver, filter)
        }
        
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }
    
    if (!isConnected) { ... }
}

Συνηθισμένα λάθη με το DisposableEffect

Το πρώτο κρίσιμο λάθος — έλλειψη κλήσης onDispose. Ο κώδικας μέσα στο μπλοκ DisposableEffect πρέπει να καλεί το onDispose, διαφορετικά θα προκύψει σφάλμα μεταγλώττισης. Ωστόσο, οι προγραμματιστές μερικές φορές προσπαθούν να το παρακάμψουν τοποθετώντας το onDispose σε μια συνθήκη: if (condition) { onDispose { ... } }. Τέτοιος κώδικας θα μεταγλωττιστεί, αλλά το onDispose δεν θα καταχωρηθεί αν η συνθήκη δεν εκπληρωθεί — ο πόρος δεν θα απελευθερωθεί ποτέ.

Το δεύτερο λάθος — χρήση του DisposableEffect για ασύγχρονες λειτουργίες. Καθώς το DisposableEffect είναι σύγχρονο, δεν μπορούν να γραφτούν κλήσεις delay() ή await() μέσα σε αυτό. Αν απαιτείται ασύγχρονη αρχικοποίηση με επακόλουθο καθαρισμό, χρησιμοποιήστε συνδυασμό LaunchedEffect (για φόρτωση δεδομένων) και DisposableEffect (για ρύθμιση/καθαρισμό εγγενών πόρων) ή έναν ξεχωριστό μηχανισμό με rememberCoroutineScope.

Το τρίτο λάθος — δημιουργία νέων αντικειμένων μέσα στο DisposableEffect χωρίς remember. Αν μέσα στο efffect δημιουργούνται αντικείμενα (αισθητήρας, listener, δέκτης) σε κάθε κλήση και τα κλειδιά αλλάζουν συχνά, αυτό οδηγεί σε υπερβολική δημιουργία αντικειμένων και συλλογή απορριμμάτων. Καλύτερα να μεταφέρετε τη δημιουργία αντικειμένων σε remember ή remember { ... } εκτός DisposableEffect, και μέσα στο effect μόνο να καταχωρείτε και να ακυρώνετε.

Συχνές Ερωτήσεις

Ποια είναι η διαφορά μεταξύ DisposableEffect και LaunchedEffect;

DisposableEffect λειτουργεί σύγχρονα και παρέχει onDispose για ρητό καθαρισμό πόρων. Το LaunchedEffect λειτουργεί ασύγχρονα σε ένα coroutine και το ακυρώνει αυτόματα κατά την αλλαγή κλειδιού ή έξοδο από τη σύνθεση. Αν ο πόρος απαιτεί κλήση μεθόδου cleanup (close, unregister, dispose) — χρησιμοποιήστε DisposableEffect. Αν η λειτουργία είναι συνάρτηση suspend — χρησιμοποιήστε LaunchedEffect.

Είναι υποχρεωτικό το μπλοκ onDispose στο DisposableEffect;

Ναι, το onDispose είναι υποχρεωτικό — ο μεταγλωττιστής Kotlin απαιτεί την κλήση του μέσα στο μπλοκ DisposableEffect. Αν το onDispose δεν κληθεί, ο κώδικας δεν θα μεταγλωττιστεί. Αυτό έγινε σκόπιμα για να αποτραπεί η λήθη των προγραμματιστών και να διασφαλιστεί ότι κάθε ανοιχτός πόρος θα κλείσει κατά την έξοδο από τη σύνθεση.

Πώς χειριζόμαστε σφάλματα μέσα στο DisposableEffect;

Χρησιμοποιήστε try-catch μέσα στο μπλοκ DisposableEffect. Αν η καταχώρηση του πόρου μπορεί να προκαλέσει εξαίρεση (π.χ. ο αισθητήρας δεν βρέθηκε), τυλίξτε την σε try και χειριστείτε το σφάλμα στο UI μέσω ξεχωριστής κατάστασης. Το onDispose πρέπει να κληθεί ανεξάρτητα από την επιτυχία της αρχικοποίησης — τοποθετήστε το σε μπλοκ finally ή στο τέλος του τμήματος try.

Μπορεί να χρησιμοποιηθεί το DisposableEffect για εγγραφή σε Flow;

Δεν συνιστάται. Για Flow είναι καλύτερο να χρησιμοποιήσετε το LaunchedEffect με collectLatest ή τη μέθοδο .collectAsState() με Lifecycle.repeatOnLifecycle. Το DisposableEffect δεν υποστηρίζει συναρτήσεις suspend, οπότε η εγγραφή σε Flow μέσα του θα απαιτούσε εκκίνηση ξεχωριστού coroutine μέσω CoroutineScope, το οποίο περιπλέκει τον κώδικα και αυξάνει τον κίνδυνο διαρροών.

Πόσα DisposableEffect μπορούν να υπάρχουν σε ένα composable;

Δεν υπάρχουν περιορισμοί, αλλά συνιστάται η ομαδοποίηση σχετικών πόρων σε ένα DisposableEffect με πολλαπλές λειτουργίες μέσα και ένα onDispose. Αν οι πόροι είναι ανεξάρτητοι (π.χ. αισθητήρας και BroadcastReceiver), καλύτερα να χωριστούν σε ξεχωριστά DisposableEffect με διαφορετικά κλειδιά — αυτό απλοποιεί τον εντοπισμό σφαλμάτων και αποτρέπει την ανεπιθύμητη αναδημιουργία όλων των πόρων κατά την αλλαγή ενός κλειδιού.

Σύνοψη

  • DisposableEffect — side-effect API Jetpack Compose για σύγχρονη αρχικοποίηση με εγγυημένο καθαρισμό μέσω onDispose.
  • onDispose — υποχρεωτικό μπλοκ που εκτελείται κατά την έξοδο από τη σύνθεση ή αλλαγή κλειδιού, αποτρέποντας διαρροές μνήμης.
  • Κλειδιά — κατά την αλλαγή κλειδιού πρώτα εκτελείται onDispose για την παλιά τιμή, στη συνέχεια εκ νέου αρχικοποίηση με τη νέα.
  • Συγχρονισμός — το DisposableEffect εκτελείται σύγχρονα, οι συναρτήσεις suspend δεν είναι διαθέσιμες μέσα του.
  • Τυπικά σενάρια — BroadcastReceiver, αισθητήρες, εγγενείς ακροατές, βιβλιοθήκες βασισμένες σε callback, ενσωμάτωση AndroidView.
  • Διαρροές — το DisposableEffect μειώνει τον αριθμό διαρροών κατά 60–70% σε σύγκριση με τη χειροκίνητη διαχείριση callbacks κύκλου ζωής.
  • Σφάλματα — κύριοι κίνδυνοι: υπό συνθήκη κλήση onDispose, χρήση για async λειτουργίες, δημιουργία αντικειμένων χωρίς remember μέσα στο effect.

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης