StrictMode: Τι είναι, λειτουργία αυστηρών κανόνων και εντοπισμός σφαλμάτων στο Android

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

StrictMode είναι ένα ενσωματωμένο εργαλείο προγραμματιστή στο Android SDK που εντοπίζει σε πραγματικό χρόνο και αναφέρει τυχαίες λειτουργίες εισόδου/εξόδου και κλήσεις δικτύου στο κύριο νήμα της εφαρμογής. Δεν διορθώνει σφάλματα, αλλά λειτουργεί ως ανιχνευτής — εκτοξεύει εξαιρέσεις ή γράφει στο LogCat όταν παραβιάζονται οι καθορισμένες πολιτικές. Σύμφωνα με τα δεδομένα του Google, 2024, η σωστή ρύθμιση του StrictMode επιτρέπει τον εντοπισμό έως και 80% των προβλημάτων απόδοσης πριν από τη δημοσίευση της εφαρμογής.

Κύρια σημεία

  • StrictMode — ανιχνευτής παραβιάσεων απόδοσης στο κύριο νήμα του Android
  • Πολιτικές δίσκου (disk_read, disk_write) και δικτύου (network) αποτελούν το βασικό σύνολο ελέγχων
  • Το εργαλείο δεν διορθώνει προβλήματα, αλλά ειδοποιεί για αυτά μέσω LogCat, διαλόγου ή crash
  • Η ρύθμιση γίνεται στο Application.onCreate και χρησιμοποιεί setThreadPolicy + setVmPolicy
  • Λειτουργίες Penalty: εκτόξευση εξαίρεσης (death), καταγραφή, ειδοποίηση στο dropbox

Τι είναι το StrictMode

StrictMode είναι ένα API που αποτελεί μέρος του Android SDK από το API Level 9 (Android 2.3 Gingerbread). Ο σκοπός του είναι να εντοπίζει σε χρόνο εκτέλεσης την τυχαία εκτέλεση βαριών λειτουργιών στο κύριο (UI) νήμα, που μπορεί να μπλοκάρουν την απόδοση της διεπαφής. Το κύριο νήμα είναι υπεύθυνο για την επεξεργασία εισόδου χρήστη, τον υπολογισμό διάταξης και την απόδοση — οποιοδήποτε μπλοκάρισμα μεγαλύτερο από 16 ms οδηγεί σε απώλεια καρέ.

Φιλοσοφία του εργαλείου

StrictMode ακολουθεί την αρχή "fail fast" — να εντοπίζει το πρόβλημα όσο το δυνατόν νωρίτερα, ιδανικά τη στιγμή της πρώτης εμφάνισής του. Αντί να περιμένει παράπονα χρηστών για καθυστερήσεις, ο προγραμματιστής λαμβάνει ένα σήμα (logs, διάλογο ή crash) απευθείας στο στάδιο ανάπτυξης. Το εργαλείο δεν απαιτεί πρόσθετες βιβλιοθήκες ή διαμόρφωση Gradle — αρκούν μερικές γραμμές κώδικα στο Application.onCreate και λειτουργεί αυτόματα σε όλες τις συσκευές.

Κοινό-στόχος

Το StrictMode προορίζεται για όλους τους προγραμματιστές Android, ανεξάρτητα από εμπειρία. Στους αρχάριους βοηθά να διαμορφώσουν σωστές συνήθειες (να μην κάνουν αιτήματα δικτύου στο UI νήμα), στους έμπειρους — να αυτοματοποιήσουν τον ποιοτικό έλεγχο στο CI/CD pipeline. Μεγάλα έργα (Google, Uber, Spotify) ενεργοποιούν το StrictMode σε debug builds με penaltyDeath, ενώ στα release builds το απενεργοποιούν μέσω ελέγχου BuildConfig.DEBUG.

Πώς λειτουργεί το StrictMode

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

Μηχανισμός ανίχνευσης

Κατά την ενεργοποίηση μιας πολιτικής, το StrictMode εισάγει τον χειριστή του στο σημείο εισόδου κλήσεων συστήματος (FileInputStream, FileOutputStream, Socket, URLConnection). Όταν η εφαρμογή καλεί, για παράδειγμα, το URLConnection.openStream στο κύριο νήμα, το StrictMode ελέγχει το τρέχον νήμα — αν είναι main thread, το εργαλείο ενεργοποιείται. Στο Android 6.0+ ο μηχανισμός ενισχύεται: οι κλήσεις δικτύου στο main thread δημιουργούν NetworkOnMainThreadException ακόμα και χωρίς StrictMode, αλλά το StrictMode επιτρέπει τον έλεγχο και του disk I/O.

Ποινή (Penalty)

Κάθε πολιτική μπορεί να έχει τον δικό της τύπο ποινής ή συνδυασμό: penaltyLog — εγγραφή στο LogCat με stack trace, penaltyDialog — εμφάνιση διαλόγου στον χρήστη (μόνο σε debug), penaltyDeath — εκτόξευση εξαίρεσης και crash εφαρμογής, penaltyDropBox — αποθήκευση δεδομένων στο DropBoxManager για μεταγενέστερη ανάλυση. Για CI/CD pipeline συνιστάται penaltyDeath — αυτό εγγυάται ότι καμία συγχώνευση με παραβίαση δεν θα περάσει απαρατήρητη.

kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyLog()
                    .penaltyDeath()
                    .build()
            )
        }
    }
}

Πολιτικές StrictMode

Το StrictMode χωρίζει τις πολιτικές σε δύο επίπεδα: ThreadPolicy (πολιτικές νήματος — τι δεν επιτρέπεται στο κύριο νήμα) και VmPolicy (πολιτικές εικονικής μηχανής — διαρροές μνήμης και πόρων). Και τα δύο επίπεδα ρυθμίζονται ανεξάρτητα και λειτουργούν παράλληλα.

ThreadPolicy: δίσκος και δίκτυο

Σε επίπεδο νήματος, το StrictMode ελέγχει τέσσερις τύπους παραβιάσεων: ανάγνωση από δίσκο (detectDiskReads), εγγραφή σε δίσκο (detectDiskWrites), λειτουργίες δικτύου (detectNetwork) και προσαρμοσμένες αργές κλήσεις (detectCustomSlowCalls). Το disk_read ενεργοποιείται σε οποιαδήποτε ανάγνωση SharedPreferences, SQLite ή αρχείων στο κύριο νήμα. Το network — σε HTTP αιτήματα, WebSocket, Socket συνδέσεις. Στο Android 11+ προστέθηκε το detectUnbufferedIO για ανίχνευση μη ρυθμισμένης εισόδου/εξόδου.

VmPolicy: διαρροές μνήμης

Η VmPolicy ελέγχει διαρροές σε επίπεδο εικονικής μηχανής ART: detectActivityLeaks (Activities που δεν καταστράφηκαν), detectLeakedClosableObjects (μη κλεισμένα Cursor, Stream, Socket), detectLeakedRegistrationObjects (μη ακυρωμένα BroadcastReceiver, ServiceConnection). Εάν η VmPolicy ανιχνεύσει ότι μια Activity δημιουργήθηκε αλλά δεν καταστράφηκε μετά την κλήση onDestroy, εμφανίζει πλήρες stack trace — αυτό εξοικονομεί ώρες εντοπισμού διαρροών μνήμης.

ΠολιτικήΕπίπεδοΤι ανιχνεύει
detectDiskReadsThreadΑνάγνωση SharedPrefs, SQLite, αρχείων στο UI νήμα
detectDiskWritesThreadΕγγραφή σε SharedPrefs, SQLite, αρχεία στο UI νήμα
detectNetworkThreadΟποιεσδήποτε λειτουργίες δικτύου στο UI νήμα
detectActivityLeaksVMActivities που επιβίωσαν του onDestroy
detectLeakedClosableObjectsVMΜη κλεισμένα Cursor, Stream, Socket

Προσαρμοσμένες ετικέτες (customSlowCall)

Μέσω του detectCustomSlowCalls μπορείτε να επισημάνετε δικές σας μεθόδους ως "ύποπτες" και να λαμβάνετε προειδοποίηση όταν ξεπερνούν ένα καθορισμένο όριο. Για παράδειγμα, αν η μέθοδός σας loadUserProfile() συνήθως εκτελείται σε 5 ms αλλά σε ορισμένες περιπτώσεις διαρκεί 200 ms — τυλίξτε την σε StrictMode.noteSlowCall("loadUserProfile"). Εάν η διάρκεια υπερβαίνει το όριο (από προεπιλογή 2000 ms), το StrictMode δημιουργεί ποινή. Το όριο ρυθμίζεται μέσω του setSlowCallDurationThreshold.

Πώς να ρυθμίσετε το StrictMode

Η βασική ρύθμιση του StrictMode απαιτεί 10 γραμμές κώδικα και γίνεται στη μέθοδο onCreate μιας προσαρμοσμένης κλάσης Application. Βασικός κανόνας: το StrictMode ενεργοποιείται μόνο σε debug builds — στα release builds επιβραδύνει την εφαρμογή και μπορεί να δημιουργήσει ψευδείς συναγερμούς.

Βασική διαμόρφωση

Δημιουργήστε μια κλάση που κληρονομεί την Application, καταχωρήστε την στο AndroidManifest.xml μέσω του χαρακτηριστικού android:name και προσθέστε τη διαμόρφωση StrictMode. Το ThreadPolicy.Builder περιλαμβάνει όλους τους ανιχνευτές και όλους τους τύπους ποινής (εκτός από dialog — λειτουργεί μόνο όταν είναι συνδεδεμένος ο εντοπιστής σφαλμάτων). Το VmPolicy.Builder προσθέτει ανιχνευτές διαρροών Activity και Closable αντικειμένων. Για μεγάλα έργα (100+ οθόνες) συνιστάται η ρύθμιση VmPolicy με penaltyDeath στον ανιχνευτή Activity Leaks — είναι αυστηρό αλλά αποτελεσματικό.

kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            StrictMode.setVmPolicy(
                StrictMode.VmPolicy.Builder()
                    .detectActivityLeaks()
                    .detectLeakedClosableObjects()
                    .detectLeakedRegistrationObjects()
                    .penaltyLog()
                    .penaltyDeath()
                    .build()
            )
        }
    }
}

Ενσωμάτωση σε CI/CD

Για αυτόματο έλεγχο σε CI/CD χρησιμοποιήστε penaltyDeath — αν κάποιο τεστ παραβιάσει την πολιτική, η εφαρμογή θα καταρρεύσει με εξαίρεση. Συνδυάστε το με Android Test Orchestrator, ώστε κάθε τεστ να εκτελείται σε καθαρή διεργασία. Για UI τεστ (Espresso, Compose Test) γράψτε ένα προσαρμοσμένο TestRule που παρεμποδίζει παραβιάσεις StrictMode και τις μετατρέπει σε αποτυχία ισχυρισμού. Παράδειγμα: στο @Before ενεργοποιήστε StrictMode, και στο @After ελέγξτε ότι δεν υπήρξαν παραβιάσεις.

Ρύθμιση ορίων

Από προεπιλογή, το όριο για customSlowCall είναι 2000 ms, για disk_read και disk_write — χωρίς όριο (οποιαδήποτε λειτουργία ενεργοποιείται). Μέσω των setSlowCallDurationThreshold και setSlowIoDurationThreshold μπορείτε να ορίσετε δικές σας τιμές σε χιλιοστά του δευτερολέπτου. Αν η εφαρμογή σας διαβάζει νόμιμα SharedPreferences στο κύριο νήμα (μικρή ρύθμιση), αυξήστε το όριο στα 10–20 ms — αυτό θα αποκόψει τις γρήγορες αναγνώσεις αλλά θα διατηρήσει τις αργές.

Βέλτιστες πρακτικές StrictMode

StrictMode είναι ένα ισχυρό αλλά ιδιότροπο εργαλείο. Η λανθασμένη ρύθμιση οδηγεί σε εκατομμύρια ψευδείς συναγερμούς, με αποτέλεσμα οι προγραμματιστές να σταματούν να τους δίνουν σημασία. Παρακάτω — δοκιμασμένες πρακτικές από την εμπειρία μεγάλων Android ομάδων.

Ενεργοποιείτε μόνο σε debug builds

Αυτός είναι αδιαπραγμάτευτος κανόνας: το StrictMode δεν πρέπει ΠΟΤΕ να είναι ενεργό σε release builds. Χρησιμοποιήστε τη σημαία BuildConfig.DEBUG ή μια προσαρμοσμένη buildConfigField. Στα release builds, πολλές βιβλιοθήκες τρίτων εκτελούν νόμιμα λειτουργίες στο κύριο νήμα (αρχικοποίηση SDK, εγγραφή cache) και το StrictMode θα δημιουργήσει ψευδείς συναγερμούς. Επιπλέον, το penaltyDialog σε release build θα εμφανίσει διάλογο στον τελικό χρήστη — κάτι που είναι απαράδεκτο.

Χρησιμοποιήστε τρία επίπεδα αυστηρότητας

Για μικρά έργα (1–10 οθόνες) ρυθμίστε penaltyLog — αρκούν τα logs για χειροκίνητη ανάλυση. Για μεσαία έργα (10–50 οθόνες) προσθέστε penaltyDeath σε network και customSlowCalls. Για μεγάλα έργα (50+ οθόνες) ενεργοποιήστε πλήρες σύνολο πολιτικών με penaltyDeath στο CI/CD, και για τοπική ανάπτυξη — penaltyLog. Αυτή η διαβάθμιση επιτρέπει να μην υπερφορτώνεται ο προγραμματιστής με ψευδή crashes, αλλά να ελέγχεται αυστηρά η ποιότητα στο pipeline.

Διαχειριστείτε τα false positives

Ορισμένες βιβλιοθήκες (Firebase, Crashlytics, Adjust) εκτελούν νόμιμα λειτουργίες στο παρασκήνιο που το StrictMode μπορεί να ανιχνεύσει λανθασμένα. Λύσεις: προσθέστε τη βιβλιοθήκη στη whitelist μέσω penaltyListener, ενημερώστε τη βιβλιοθήκη σε έκδοση με ρητή εναλλαγή σε background thread, ή χρησιμοποιήστε StrictMode.vmPolicy. Στο Android 11+ εμφανίστηκε το StrictMode.OnVmViolationListener για προγραμματιστικό φιλτράρισμα παραβιάσεων ανά stacktrace.

kotlin
// Φιλτράρισμα false positives μέσω penaltyListener
StrictMode.setThreadPolicy(
    StrictMode.ThreadPolicy.Builder()
        .detectAll()
        .penaltyListener { violation ->
            val stack = violation.stackTraceToString()
            if ("com.google.firebase" !in stack) {
                logViolation(violation)
            }
        }
        .build()
)

StrictMode vs Android Lint vs προφίλερ

Το StrictMode δεν είναι το μοναδικό εργαλείο ποιοτικού ελέγχου στο οικοσύστημα Android. Για να κατανοήσουμε τη θέση του, ας το συγκρίνουμε με τα Android Lint, Android Profiler και Perfetto ως προς βασικά κριτήρια: χρόνος ελέγχου, βάθος ανάλυσης και αυτοματισμός.

ΚριτήριοStrictModeAndroid LintProfiler / Perfetto
Χρόνος ελέγχουRuntime (κατά τη λειτουργία της εφαρμογής)Compile time (πριν την εκτέλεση)Runtime (post-mortem)
Τι ελέγχειΔίσκο, δίκτυο, διαρροέςXML, κώδικα, πόρουςCPU, μνήμη, δίκτυο, ενέργεια
ΑυτοματισμόςCI/CD μέσω penaltyDeathGradle task + lint-baselineΑπαιτεί χειροκίνητη ανάλυση
ΒάθοςΜόνο UI νήμα και διαρροέςΣτατική ανάλυση κώδικαΠλήρης εικόνα απόδοσης
Ψευδείς συναγερμοίΜέτριοι (εξαρτώνται από βιβλιοθήκες)Χαμηλοί (ρυθμισμένοι κανόνες)Κανένας (πραγματικές μετρήσεις)

Η καλύτερη στρατηγική είναι να συνδυάζετε και τις τρεις προσεγγίσεις: το Android Lint πιάνει προφανή σφάλματα στο στάδιο μεταγλώττισης (π.χ. ξεχασμένο IdleHandler), το StrictMode εντοπίζει προβλήματα σε χρόνο εκτέλεσης, και το Android Profiler / Perfetto χρησιμοποιείται για βαθιά ανάλυση όταν τα δύο πρώτα εργαλεία δεν δίνουν απάντηση. Σε πραγματικά έργα (Google Maps, Instagram) το StrictMode εφαρμόζεται τη δεύτερη εβδομάδα ανάπτυξης — αμέσως μετά τη ρύθμιση της βασικής αρχιτεκτονικής.

Παραδείγματα κώδικα με StrictMode

Ας δούμε δύο πραγματικά σενάρια όπου το StrictMode βοηθά στον εντοπισμό και την εξάλειψη προβλημάτων απόδοσης: ανάγνωση SharedPreferences στο κύριο νήμα και διαρροή Activity μέσω μη καταχωρημένου callback.

Ανίχνευση αργής SharedPreferences

Κατά την εκκίνηση της εφαρμογής, το StrictMode με πολιτική detectDiskReads θα ανιχνεύσει την ανάγνωση SharedPreferences στο κύριο νήμα. Λύση: φορτώστε τη ρύθμιση ασύγχρονα μέσω CoroutineScope ή αποθηκεύστε την σε cache στη μνήμη κατά την εκκίνηση. Η SharedPreferences διαβάζει σύγχρονα ένα XML αρχείο από τον δίσκο — ακόμα και για μικρό αρχείο (1–2 KB) η λειτουργία διαρκεί 1–5 ms, και σε φθηνές συσκευές έως 20 ms, κάτι που μπορεί να οδηγήσει σε απώλεια καρέ.

kotlin
// ❌ Προβληματικός κώδικας — ανάγνωση SharedPrefs στο UI νήμα
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // StrictMode detectDiskReads → VIOLATION!
        prefs = getSharedPreferences("config", MODE_PRIVATE)
    }
}

// ✅ Διορθωμένος κώδικας — ανάγνωση μέσω Coroutine
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        loadConfigAsync()
    }
}

Ανίχνευση διαρροής Activity

Το StrictMode με VmPolicy.detectActivityLeaks θα ανιχνεύσει μια Activity που βγήκε από τη στοίβα (κλήθηκε finish) αλλά το αντικείμενο Activity συνεχίζει να υπάρχει στη μνήμη λόγω στατικής αναφοράς ή μη καταχωρημένου callback. Τυπικό σενάριο: καταχώρηση EventBus ή LocationListener στο onResume χωρίς κλήση unregister στο onPause. Η VmPolicy θα εμφανίσει stack trace με ένδειξη της γραμμής όπου δημιουργήθηκε η αναφορά.

kotlin
// ❌ Διαρροή — το callback δεν ακυρώθηκε
private var locationCallback: LocationCallback? = null

override fun onResume() {
    super.onResume()
    locationCallback = LocationCallback(this::onLocationUpdated)
    locationManager.register(locationCallback) // StrictMode → LEAK!
}

override fun onPause() {
    super.onPause()
    // Ξεχάσατε: locationManager.unregister(locationCallback)
}

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

Επιβραδύνει το StrictMode την εφαρμογή;

StrictMode πράγματι προσθέτει ένα μικρό overhead — κάθε κλήση συστήματος ελέγχεται για συμμόρφωση με τις πολιτικές. Η επίδραση στην απόδοση είναι 1–3% σε debug build και απουσιάζει στο release (όπου το StrictMode απενεργοποιείται). Με την ενεργοποίηση detectAll σε παλιές συσκευές (Android 6–8) το overhead μπορεί να φτάσει το 5%, γι' αυτό συνιστάται η ρύθμιση μόνο των απαραίτητων πολιτικών.

Μπορώ να χρησιμοποιήσω StrictMode με Jetpack Compose;

Ναι, το StrictMode είναι πλήρως συμβατό με το Jetpack Compose. Οι πολιτικές disk και network λειτουργούν σε επίπεδο framework, ανεξάρτητα από το UI framework. Επιπλέον, στο Compose η κρισιμότητα των μπλοκαρισμάτων UI είναι μεγαλύτερη — το Compose ανασχεδιάζει καρέ με συχνότητα 120 FPS σε συσκευές υψηλών επιδόσεων, επομένως τα επιπλέον 5 ms για ανάγνωση αρχείου γίνονται πιο αισθητά.

Γιατί το StrictMode δεν ενεργοποιείται στην ανάγνωση SharedPreferences;

Από το Android 8.1 (API 27), η SharedPreferences μπορεί να χρησιμοποιεί προσωρινή αποθήκευση στη μνήμη — αν το αρχείο έχει ήδη διαβαστεί, η επαναλαμβανόμενη ανάγνωση δεν ενεργοποιεί το StrictMode. Ελέγξτε ότι καλείτε getSharedPreferences για πρώτη φορά (ψυχρή ανάγνωση) και ότι η πολιτική detectDiskReads είναι ενεργή. Επίσης, ελέγξτε ότι το StrictMode δεν έχει παρακαμφθεί σε ένα parent-free fragment.

Πώς να απενεργοποιήσω το StrictMode για συγκεκριμένα τεστ;

Σε JUnit τεστ χρησιμοποιήστε StrictMode.allowThreadDiskReads() και StrictMode.allowThreadDiskWrites() στο @Before, και στο @After επαναφέρετε τις ρυθμίσεις μέσω StrictMode.enableDefaults(). Για Instrumentation τεστ χρησιμοποιήστε προσαρμοσμένο TestRunner με προσωρινή αποθήκευση της αρχικής πολιτικής. Σε Espresso τεστ είναι βολικό να τυλίξετε τον ευάλωτο σε StrictMode κώδικα σε IdlingResource.

Χρειάζεται StrictMode σε Kotlin Multiplatform;

Το StrictMode λειτουργεί μόνο στην πλατφόρμα Android μέσω Android SDK. Στο Kotlin Multiplatform (KMP) ο κώδικας commonMain δεν μπορεί να χρησιμοποιήσει StrictMode, αλλά για το androidMain μπορείτε να τον προσθέσετε κανονικά. Για το τμήμα iOS χρησιμοποιήστε ανάλογο — DispatchQueue.main.async assertion για το κύριο νήμα.

Συμπεράσματα

  • StrictMode — ανιχνευτής προβλημάτων απόδοσης σε πραγματικό χρόνο στο κύριο νήμα του Android
  • Οι πολιτικές χωρίζονται σε ThreadPolicy (δίσκος, δίκτυο) και VmPolicy (διαρροές μνήμης)
  • Η ρύθμιση απαιτεί 10 γραμμές κώδικα στο Application.onCreate με έλεγχο BuildConfig.DEBUG
  • Για CI/CD χρησιμοποιήστε penaltyDeath — η παραβίαση πολιτικής οδηγεί σε κατάρρευση της εφαρμογής
  • Το StrictMode δεν αντικαθιστά, αλλά συμπληρώνει τα Android Lint και Perfetto
  • Η σωστή φιλτράρισμα false positives είναι το κλειδί για αποτελεσματική χρήση του εργαλείου
  • Συνιστάται η εφαρμογή StrictMode τη δεύτερη εβδομάδα ανάπτυξης του έργου

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

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

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

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