Μια αργή εφαρμογή είναι ο κύριος λόγος για τον οποίο οι χρήστες διαγράφουν προγράμματα. Χιλιοστά του δευτερολέπτου καθυστέρησης κατά την εκκίνηση ή την κύλιση μιας λίστας μειώνουν τη διατήρηση κατά δεκάδες τοις εκατό. Η απόδοση (performance) δεν είναι μόνο ταχύτητα, αλλά και σταθερότητα: απουσία ANR, σφαλμάτων και διαρροών μνήμης. Αυτό το άρθρο καλύπτει όλες τις πτυχές της απόδοσης: από τη διαχείριση μνήμης (GC, ARC) έως την προφίλ με εργαλεία. Μάθετε περισσότερα στον επίσημο οδηγό Android Performance.
Κύρια σημεία
Η απόδοση της εφαρμογής συνδέεται άμεσα με το jank — μια αισθητή καθυστέρηση μεταξύ της ενέργειας του χρήστη και της απόκρισης του UI. Κύριες αιτίες: μπλοκάρισμα του κύριου νήματος (βαριές λειτουργίες στο νήμα UI), συχνές ανασχεδιάσεις διάταξης (overdraw), διαρροές μνήμης (συχνό GC), μη βέλτιστοι αλγόριθμοι (O(n²) σε μεγάλα δεδομένα). Ρυθμός καρέ (FPS) — αριθμός καρέ ανά δευτερόλεπτο. Για άνετη εμπειρία χρειάζονται σταθερά 60 FPS (Android) ή 120 FPS (iPhone Pro, iPad Pro). Το VSync συγχρονίζει την απόδοση με το ρυθμό ανανέωσης της οθόνης.
Το jank συμβαίνει όταν η απόδοση ενός καρέ υπερβαίνει τα 16,6 ms (για 60 FPS) ή τα 8,3 ms (για 120 FPS). Η προφίλ GPU (Profile GPU Rendering στο Android, Core Animation στο iOS) δείχνει ποια στάδια απόδοσης καταλαμβάνουν τον περισσότερο χρόνο. Κύρια στάδια: Layout (τοποθέτηση στοιχείων), Draw (σχεδίαση), Display (μεταφορά στο buffer καρέ). Το πιο συνηθισμένο πρόβλημα είναι η διόγκωση διάταξης σε XML, ειδικά με σύνθετα ένθετα ConstraintLayout.
Time-to-Interactive (TTI) — χρόνος που χρειάζεται η εφαρμογή για να είναι πλήρως έτοιμη για αλληλεπίδραση. Το TTI περιλαμβάνει Cold Start, φόρτωση δεδομένων και αρχικοποίηση βιβλιοθηκών. Η Google συνιστά TTI κάτω από 5 δευτερόλεπτα, η Apple — κάτω από 2 δευτερόλεπτα για κύριες οθόνες. Τεμπέλικη φόρτωση — τεχνική καθυστερημένης φόρτωσης περιεχομένου και βιβλιοθηκών, κρίσιμη για τη βελτίωση του TTI. Στην IT Sectr, χρησιμοποιούμε τεμπέλικη αρχικοποίηση από προεπιλογή σε όλα τα έργα.
Το ANR και το Σφάλμα είναι οι κύριοι εχθροί της απόδοσης εφαρμογών κινητού. ANR (Application Not Responding) — παράθυρο διαλόγου στο Android που εμφανίζεται εάν το κύριο νήμα είναι αποκλεισμένο για περισσότερο από 5 δευτερόλεπτα. Αιτίες: σύγχρονα αιτήματα δικτύου στο νήμα UI, εργασία με βάση δεδομένων χωρίς coroutine, αποκωδικοποίηση μεγάλου bitmap χωρίς downsampling, deadlock στο κύριο νήμα. Η στοίβα κλήσεων ANR αποθηκεύεται στο /data/anr/traces.txt και επιτρέπει τον προσδιορισμό της ακριβούς θέσης αποκλεισμού.
Σφάλμα — απροσδόκητος τερματισμός της εφαρμογής. Στο Android — Exception (Java/Kotlin) ή Signal (εγγενής κώδικας). Στο iOS — NSException ή σήμα (EXC_BAD_ACCESS — πρόσβαση σε απελευθερωμένη μνήμη). Εργαλεία αναφοράς σφαλμάτων: Firebase Crashlytics, Sentry, BugSnag. Συλλέγουν stacktrace, δεδομένα συσκευής και βήματα αναπαραγωγής. Stack Overflow — υπερχείλιση στοίβας κλήσεων από άπειρη αναδρομή. OutOfMemoryError — όταν ο σωρός είναι γεμάτος.
StrictMode — εργαλείο Android για τον εντοπισμό παραβιάσεων ασφάλειας νημάτων. Επιτρέπει τον καθορισμό κανόνων: ThreadPolicy (απαγόρευση δίσκου/δικτύου στο κύριο νήμα), VmPolicy (εντοπισμός διαρροών Activity, SQLite, CloseGuard). Το StrictMode πρέπει να ενεργοποιείται μόνο σε debug build — στην έκδοση δεν πρέπει να λειτουργεί. Στο iOS, το αντίστοιχο είναι το Main Thread Checker (Xcode), το οποίο εντοπίζει αυτόματα κλήσεις UIKit που δεν είναι στο κύριο νήμα.
Διαρροή μνήμης (Memory Leak) — κατάσταση όπου ένα αντικείμενο παραμένει στη μνήμη παρόλο που η εφαρμογή δεν το χρησιμοποιεί πλέον. Αυτό μειώνει άμεσα την απόδοση της εφαρμογής. Στο Android, το GC (Garbage Collection) δεν μπορεί να συλλέξει ένα αντικείμενο εάν υπάρχει ισχυρή αναφορά σε αυτό. Τυπικές αιτίες: στατικές αναφορές σε Activity, ακυρωμένες callback/παρατηρητές, εσωτερικές κλάσεις με σιωπηρή αναφορά στην εξωτερική κλάση, Handler με μη καθαρισμένα μηνύματα. LeakCanary — βιβλιοθήκη για αυτόματο εντοπισμό διαρροών.
ARC (Automatic Reference Counting) — μοντέλο διαχείρισης μνήμης στο iOS. Κάθε αντικείμενο έχει έναν μετρητή αναφορών (retain count). Όταν ο μετρητής φτάσει στο μηδέν, η μνήμη απελευθερώνεται. Retain Cycle συμβαίνει όταν δύο αντικείμενα διατηρούν ισχυρές αναφορές το ένα στο άλλο (A → B και B → A). Το ARC δεν θα μηδενίσει ποτέ τους μετρητές. Λύση: αδύναμες (weak) ή αδέσποτες (unowned) αναφορές. Το Weak μηδενίζεται αυτόματα (γίνεται nil) κατά την απελευθέρωση του αντικειμένου. Το Unowned δεν μηδενίζεται αλλά εγγυάται ότι το αντικείμενο είναι ζωντανό.
GC (Garbage Collection) λειτουργεί στο Android (Java/Kotlin). Το GC διακόπτει περιοδικά την εκτέλεση (παύση Stop-the-World) για να βρει και να απελευθερώσει μη προσβάσιμα αντικείμενα. Ενεργοποίηση GC: όταν ο σωρός γεμίσει σε ένα ορισμένο ποσοστό. Το ARC λειτουργεί στο iOS (Swift/Objective-C) και δεν έχει παύσεις — οι μετρητές ενημερώνονται ατομικά σε κάθε ανάθεση. Το ARC είναι πιο προβλέψιμο, αλλά μπορεί να συσσωρεύσει υπερβολικές λειτουργίες retain/release σε υψηλή συχνότητα ανάθεσης.
Αδύναμη αναφορά (Weak Reference) και ισχυρή αναφορά (Strong Reference) — ο τύπος αναφοράς καθορίζει εάν το GC/ARC μπορεί να απελευθερώσει το αντικείμενο. Strong Reference — το αντικείμενο δεν θα συλλεχθεί όσο υπάρχει αυτή η αναφορά. Weak Reference — το GC/ARC μπορεί να συλλέξει το αντικείμενο; η αδύναμη αναφορά γίνεται nil (σε Swift/Java WeakReference). Unowned Reference (Swift) — δεν μηδενίζεται κατά την απελευθέρωση; η πρόσβαση σε αυτήν μετά τον θάνατο του αντικειμένου προκαλεί σφάλμα. Στο Android, χρησιμοποιείται java.lang.ref.WeakReference για αδύναμες αναφορές.
Παράδειγμα εντοπισμού διαρροής στο Android με LeakCanary:
// Утечка: анонимный класс держит ссылку на Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// Используем `this@MainActivity`, сохраняя ссылку на Activity
Log.d("TAG", "Handler received message")
}
}
handler.sendEmptyMessageDelayed(0, 60000)
}
}
// Исправление: статический Handler + WeakReference
class SafeHandler(activity: MainActivity) : Handler() {
private val weakActivity =
WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get() ?: return
Log.d("TAG", "Handler received message")
}
}
Η προφίλ είναι η διαδικασία μέτρησης της απόδοσης της εφαρμογής: CPU, μνήμη, δίκτυο, κατανάλωση ενέργειας. Χωρίς προφίλ, η τυφλή βελτιστοποίηση είναι άχρηστη — δεν θα γνωρίζετε ποιο μέρος του κώδικα είναι πραγματικά αργό.
| Εργαλείο | Πλατφόρμα | Μετρά | Πότε να χρησιμοποιηθεί |
|---|---|---|---|
| Instruments (Time Profiler) | iOS | CPU, κλήσεις συναρτήσεων, χρόνος εκτέλεσης | Βελτιστοποίηση αλγορίθμων, αναζήτηση σημείων συμφόρησης |
| Instruments (Allocations) | iOS | Μνήμη, αριθμός αντικειμένων, retain counts | Αναζήτηση διαρροών και υπερβολικής κατανάλωσης μνήμης |
| Instruments (Leaks) | iOS | Retain cycles, διαρροές μνήμης | Τακτικός έλεγχος πριν από την κυκλοφορία |
| Android Profiler (CPU) | Android | Χρήση CPU, δραστηριότητα νημάτων, traces | Αναζήτηση μπλοκαρισμάτων κύριου νήματος |
| Android Profiler (Memory) | Android | Heap dump, παρακολούθηση κατανομής | Αναζήτηση διαρροών, ανάλυση αντικειμένων |
| Android Profiler (Network) | Android | Κυκλοφορία, ταχύτητα, χρονισμοί αιτημάτων | Βελτιστοποίηση κλήσεων δικτύου |
| LeakCanary | Android | Αυτόματος εντοπισμός διαρροών μνήμης | Σε όλα τα στάδια ανάπτυξης |
| StrictMode | Android | Δίσκος/δίκτυο στο κύριο νήμα, διαρροές | Debug build |
| Traceview / Systrace | Android | Παρακολούθηση μεθόδου, συμβάντα συστήματος | Βαθιά ανάλυση καθυστέρησης |
Instruments (Xcode) — το πιο ισχυρό εργαλείο για iOS. Το Time Profiler δείχνει ποιες συναρτήσεις καταναλώνουν τον περισσότερο CPU. Το Allocations παρακολουθεί τη δημιουργία και απελευθέρωση αντικειμένων. Το Leaks βρίσκει αυτόματα retain cycles. Βήματα προφίλ: (1) εκκινήστε το Instruments; (2) επιλέξτε πρότυπο (Time Profiler για CPU); (3) εκτελέστε το προβληματικό σενάριο; (4) αναλύστε τη στοίβα κλήσεων — η ευρύτερη στήλη είναι η πιο "καυτή" συνάρτηση.
Το Android Profiler είναι ενσωματωμένο στο Android Studio (View → Tool Windows → Profiler). Το CPU Profiler δείχνει το φορτίο κάθε νήματος. Memory Profiler — heap dump και παρακολούθηση κατανομής. Network Profiler — όλα τα αιτήματα HTTP με χρονισμούς. Energy Profiler — κατανάλωση ενέργειας: WakeLock, Location, Network. Για λεπτομερή παρακολούθηση χρησιμοποιείται Systrace (Android 10+) ή Perfetto — παρακολούθηση συστήματος με ακρίβεια μικροδευτερολέπτου.
Η εκκίνηση της εφαρμογής είναι ένας από τους βασικούς δείκτες απόδοσης. Χωρίζεται σε τρεις τύπους: Cold Start — η εφαρμογή ξεκινά από το μηδέν: δημιουργείται η διεργασία, Application.onCreate (Android) / AppDelegate.applicationDidFinishLaunching (iOS), φόρτωση κλάσεων, αρχικοποίηση βιβλιοθηκών. Warm Start — η διεργασία υπάρχει, αλλά το Activity/ViewController έχει καταστραφεί (π.χ. κατά την περιστροφή οθόνης ή επιστροφή από μνήμη). Hot Start — το Activity/ViewController βρίσκεται στη μνήμη, η εφαρμογή απλώς εμφανίζεται (εναλλαγή από άλλη εφαρμογή).
Το Cold Start είναι η πιο σημαντική μέτρηση. Στο Android περιλαμβάνει: (1) launch Activity — φόρτωση XML, αρχικοποίηση View; (2) πρώτο καρέ — χρόνος έως την πρώτη απόδοση. Η Google συνιστά: launch Activity < 200 ms, πρώτο καρέ < 500 ms, TTI < 5 δευτερόλεπτα. Βελτιστοποίηση Cold Start: μειώστε το Application.onCreate (coroutine για τεμπέλικη αρχικοποίηση), χρησιμοποιήστε SplashScreen API (Android 12+), καθυστερήστε την αρχικοποίηση βιβλιοθηκών (WorkManager, DI), αφαιρέστε περιττά ContentProviders.
Στο iOS, το Cold Start περιλαμβάνει: φόρτωση δυαδικού αρχείου Mach-O, dyld (δυναμικός σύνδεσμος), αρχικοποίηση χρόνου εκτέλεσης Objective-C, εκπρόσωπος εφαρμογής, πρώτος ελεγκτής. Chrome Custom Tabs (Android) και Universal Links (iOS) — τεχνολογίες για γρήγορο άνοιγμα εξωτερικού περιεχομένου στην εφαρμογή χωρίς πλήρες Cold Start. Συνιστάται η δοκιμή Cold Start σε πραγματικές συσκευές μεσαίας κατηγορίας.
Το μέγεθος της εφαρμογής είναι παράγοντας απόδοσης για εγκατάσταση και ενημερώσεις. Επηρεάζει τη μετατροπή: κάθε 10 MB μειώνει τη μετατροπή κατά 1%. Το Google Play συνιστά μέγεθος APK κάτω από 150 MB; App Store — κάτω από 200 MB (κυψελωτά δίκτυα — 100 MB). Κύριες μέθοδοι βελτιστοποίησης: συμπίεση εικόνων (WebP αντί PNG εξοικονομεί 25-35%), διανυσματοποίηση (VectorDrawable στο Android, SF Symbols στο iOS), αφαίρεση αχρησιμοποίητου κώδικα (R8/ProGuard), αφαίρεση αχρησιμοποίητων πόρων (lint → unused resources).
App Bundle (Android) — μορφή δημοσίευσης όπου το Google Play δημιουργεί βελτιστοποιημένο APK για κάθε συσκευή. Το App Bundle μειώνει το μέγεθος λήψης κατά 20-40%. Dynamic Delivery — ενότητες που λαμβάνονται κατ' απαίτηση (on-demand feature modules). Στο iOS, το αντίστοιχο είναι οι On-Demand Resources (ODR): πόροι που λαμβάνονται μετά την πρώτη εκκίνηση (επίπεδα παιχνιδιού, βίντεο).
Τεμπέλικη φόρτωση — τεχνική όπου οι ενότητες και οι βιβλιοθήκες δεν φορτώνονται κατά την εκκίνηση, αλλά φορτώνονται ανάλογα με τις ανάγκες. Split APK (Android) και App Slicing (iOS) — διαίρεση της εφαρμογής σε αρχιτεκτονικές υποδοχές: arm64-v8a, x86_64. Βελτιστοποίηση μεγέθους εφαρμογής — συνεχής διαδικασία: αναλύστε τη σύνθεση APK (Analyze APK στο Android Studio), αφαιρέστε διπλά εικονίδια, χρησιμοποιήστε SVG αντί για πολλές πυκνότητες PNG. Στην IT Sectr, συμπεριλαμβάνουμε έλεγχο μεγέθους build στο CI/CD για κάθε MR.
Συχνές Ερωτήσεις
ANR (Application Not Responding) — παράθυρο διαλόγου που εμφανίζεται στο Android εάν το κύριο νήμα είναι αποκλεισμένο για περισσότερο από 5 δευτερόλεπτα. Για να αποφύγετε το ANR, μετακινήστε όλες τις βαριές λειτουργίες (δίκτυο, βάση δεδομένων, επεξεργασία αρχείων) σε νήματα παρασκηνίου. Το αντίστοιχο στο iOS είναι frozen UI, όταν η εφαρμογή σταματά να ανταποκρίνεται στα αγγίγματα.
Διαρροή μνήμης — όταν ένα αντικείμενο δεν μπορεί να απελευθερωθεί επειδή υπάρχουν ακόμη αναφορές σε αυτό. Retain Cycle — κατάσταση στο iOS/Objective-C όπου δύο αντικείμενα αναφέρονται το ένα στο άλλο (A → B → A) και το ARC δεν μπορεί να απελευθερώσει κανένα. Λύση: weak/unowned αναφορές και έγκαιρος καθαρισμός callback.
Για iOS: Instruments (Time Profiler, Allocations, Leaks). Για Android: Android Profiler (CPU, Memory, Network), LeakCanary (διαρροές μνήμης), StrictMode (παραβιάσεις νημάτων). Συνιστάται ο συνδυασμός προφίλ κατά την ανάπτυξη και ενσωμάτωση.
Cold Start — η εφαρμογή ξεκινά από το μηδέν: δημιουργείται η διεργασία, φορτώνονται οι κλάσεις, εκτελείται το Application.onCreate. Warm Start — η διεργασία υπάρχει αλλά το Activity/ViewController αναδημιουργείται. Hot Start — το Activity/ViewController είναι ήδη στη μνήμη, απλώς εμφανίζεται. Το Cold Start είναι το πιο αργό (1-5 δευτερόλεπτα) και είναι κρίσιμο για την εμπειρία χρήστη.
Κύριες μέθοδοι: αφαιρέστε αχρησιμοποίητους πόρους και κώδικα (χρησιμοποιήστε R8/ProGuard), διανυσματοποιήστε εικόνες (VectorDrawable, SF Symbols), συμπιέστε PNG/WebP (Android), χρησιμοποιήστε App Bundle αντί APK, αφαιρέστε περιττές βιβλιοθήκες, χρησιμοποιήστε τεμπέλικη φόρτωση για ενότητες. Η βελτιστοποίηση μεγέθους μπορεί να μειώσει το APK κατά 40-60%.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.