Το Permission Handler είναι ένα στοιχείο της εφαρμογής Android που είναι υπεύθυνο για τον έλεγχο, την αίτηση και την επεξεργασία αποτελεσμάτων αδειών runtime. Σύμφωνα με τον Android Developer Guide, 2024, ο χειριστής αδειών συγκεντρώνει τη λογική των checkSelfPermission, requestPermissions και shouldShowRequestPermissionRationale σε μια ενιαία κλάση ή ViewModel. Αυτό απλοποιεί τη συντήρηση κώδικα και βελτιώνει τη δοκιμή.
Κύρια Σημεία
Permission Handler — είναι ένα αρχιτεκτονικό μοτίβο για κεντρική διαχείριση αδειών runtime στο Android. Αντί για διάσπαρτες κλήσεις ContextCompat.checkSelfPermission και ActivityCompat.requestPermissions σε όλο τον κώδικα της εφαρμογής, όλη η λογική αιτημάτων και επεξεργασίας αποτελεσμάτων συγκεντρώνεται σε μία κλάση. Αυτό μειώνει την επανάληψη, απλοποιεί τη συντήρηση και κάνει τον κώδικα πιο προβλέψιμο.
Η ανάγκη για Permission Handler προέκυψε με την εισαγωγή αδειών runtime στο Android 6.0. Πριν από αυτό, όλες οι άδειες ζητούνταν κατά την εγκατάσταση και ο κώδικας της εφαρμογής μπορούσε να χρησιμοποιεί οποιοδήποτε API χωρίς ελέγχους. Μετά τη μετάβαση στο μοντέλο runtime, κάθε χρήση επικίνδυνης άδειας απαιτεί τριπλό έλεγχο: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Η διασπορά αυτής της λογικής μεταξύ Activity και Fragment οδηγεί σε ενσωματωμένες επαναλήψεις και σφάλματα. Σύμφωνα με τα δεδομένα του Google I/O 2019, η συγκέντρωση της επεξεργασίας αδειών μειώνει τον αριθμό σφαλμάτων που σχετίζονται με Permission Denial κατά μέσο όρο 60 τοις εκατό.
Ένα καλό Permission Handler παρέχει μια καθαρή διεπαφή για τον καλούντα κώδικα. Το Activity ή το Fragment δεν χρειάζεται να γνωρίζει τις λεπτομέρειες του αιτήματος — καλεί μια μέθοδο όπως requestCamera(callback) και ο χειριστής διαχειρίζεται μόνος του τον έλεγχο κατάστασης, την εμφάνιση rationale, την κλήση του συστήματος διαλόγου και τη μετάδοση του αποτελέσματος στο callback. Αυτό υλοποιεί την αρχή της ενιαίας ευθύνης και διαχωρίζει την επιχειρηματική λογική από τον κώδικα πλατφόρμας αδειών.
Το Permission Handler καθίσταται απαραίτητο όταν η εφαρμογή χρησιμοποιεί 3 ή περισσότερες επικίνδυνες άδειες. Για απλές εφαρμογές με μία άδεια (π.χ. κάμερα για σαρωτή QR κωδικών) μπορείτε να χρησιμοποιήσετε απευθείας κλήση. Αλλά για μια τυπική εφαρμογή κινητού με κάμερα, γεωτοποθεσία, ειδοποιήσεις και αποθήκευση — ένας συγκεντρωτικός χειριστής είναι υποχρεωτικός για τη συντήρηση.
Ένας τυπικός Permission Handler αποτελείται από τρία επίπεδα: διεπαφή-σύμβαση, υλοποίηση με ActivityResultLauncher και επίπεδο για ViewModel. Η διεπαφή ορίζει μεθόδους αιτήματος για κάθε άδεια — requestCamera, requestLocation, requestStorage. Η υλοποίηση συνδέει αυτές τις μεθόδους με τις αντίστοιχες συμβάσεις ActivityResultContracts.RequestPermission.
Βασικά στοιχεία της αρχιτεκτονικής:
Αυτή η αρχιτεκτονική επιτρέπει την εύκολη αντικατάσταση της υλοποίησης σε δοκιμές: αντί για πραγματικό ActivityResultLauncher χρησιμοποιείται ένα mock που επιστρέφει προκαθορισμένο αποτέλεσμα χωρίς αλληλεπίδραση με το σύστημα. Αυτό είναι κρίσιμο για δοκιμές μονάδας λογικής UI, όπου η εκκίνηση Activity για διάλογο άδειας είναι αδύνατη.
Το Permission Handler πρέπει να λαμβάνει υπόψη τον κύκλο ζωής του Activity και του Fragment. Οι εκκινητές καταχωρούνται στο ActivityResultRegistry, το οποίο αποθηκεύει και επαναφέρει αυτόματα την κατάσταση κατά την περιστροφή οθόνης και την αναδημιουργία του Activity. Ο Handler δεν πρέπει να διατηρεί άμεσες αναφορές σε Activity ή Fragment — αντ' αυτού χρησιμοποιήστε WeakReference ή μεταβιβάστε το registry μέσω κατασκευαστή. Αυτό αποτρέπει διαρροές μνήμης και καταρρεύσεις σε αλλαγές διαμόρφωσης.
Η βασική υλοποίηση του Permission Handler βασίζεται στο ActivityResultContracts.RequestPermission. Ο Handler λαμβάνει το ActivityResultRegistry από το ComponentActivity ή το Fragment και καταχωρεί εκκινητές για κάθε άδεια. Κάθε εκκινητής δέχεται μια lambda-callback που καλείται μετά την απάντηση του χρήστη.
sealed class PermissionResult {
object GRANTED : PermissionResult()
data class DENIED(
val shouldShowRationale: Boolean
) : PermissionResult()
}
interface PermissionHandler {
fun requestCamera(
callback: (PermissionResult) -> Unit
)
fun requestLocation(
callback: (PermissionResult) -> Unit
)
fun isPermissionGranted(
permission: String
): Boolean
}
class AndroidPermissionHandler(
private val registry: ActivityResultRegistry,
private val context: Context
) : PermissionHandler {
private var cameraLauncher: ActivityResultLauncher<String>? = null
fun initialize() {
cameraLauncher = registry.register(
"camera_permission",
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
pendingCameraCallback?.invoke(
PermissionResult.GRANTED
)
} else {
val rationale = ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
Manifest.permission.CAMERA
)
pendingCameraCallback?.invoke(
PermissionResult.DENIED(rationale)
)
}
}
}
private var pendingCameraCallback:
((PermissionResult) -> Unit)? = null
override fun requestCamera(
callback: (PermissionResult) -> Unit
) {
if (isPermissionGranted(
Manifest.permission.CAMERA
)) {
callback.invoke(PermissionResult.GRANTED)
return
}
pendingCameraCallback = callback
cameraLauncher?.launch(
Manifest.permission.CAMERA
)
}
override fun isPermissionGranted(
permission: String
): Boolean {
return ContextCompat.checkSelfPermission(
context, permission
) == PackageManager.PERMISSION_GRANTED
}
}
Ο Handler αρχικοποιείται στο onCreate του Activity μέσω registerForActivityResult, το οποίο παρέχει πρόσβαση στο ActivityResultRegistry. Μετά την αρχικοποίηση, ο χειριστής είναι έτοιμος να επεξεργαστεί αιτήματα καθ' όλη τη διάρκεια ζωής του Activity. Είναι σημαντικό να καλέσετε το initialize πριν από το πρώτο αίτημα, διαφορετικά ο εκκινητής δεν θα καταχωρηθεί.
Ενσωμάτωση Permission Handler με ViewModel είναι η πιο προηγμένη προσέγγιση. Το ViewModel διαχειρίζεται την κατάσταση αιτημάτων και ο Handler εκτελεί μόνο κλήσεις πλατφόρμας. Το ViewModel περιέχει StateFlow<PermissionUiState>, όπου το UiState περιγράφει ποια άδεια ζητείται και ποιο αποτέλεσμα ελήφθη. Το Activity εγγράφεται σε αυτό το StateFlow και αναθέτει το αίτημα στον Handler.
class PermissionsViewModel : ViewModel() {
private val _uiState =
MutableStateFlow<PermissionUiState>(
PermissionUiState.Idle
)
val uiState: StateFlow<PermissionUiState> = _uiState.asStateFlow()
fun onCameraRequested() {
_uiState.value = PermissionUiState.RequestingCamera
}
fun onPermissionResult(
permission: String,
result: PermissionResult
) {
when (result) {
PermissionResult.GRANTED -> {
_uiState.value = PermissionUiState.Granted(permission)
}
is PermissionResult.DENIED -> {
_uiState.value = PermissionUiState.Denied(
permission,
result.shouldShowRationale
)
}
}
}
}
sealed class PermissionUiState {
object Idle : PermissionUiState()
object RequestingCamera : PermissionUiState()
data class Granted(val permission: String) : PermissionUiState()
data class Denied(
val permission: String,
val shouldShowRationale: Boolean
) : PermissionUiState()
}
Σε αυτό το μοντέλο, το Activity ελέγχει το isPermissionGranted μέσω Handler κατά την εκκίνηση και το ViewModel διαχειρίζεται μόνο την κατάσταση. Εάν η άδεια δεν έχει χορηγηθεί — το Activity εγγράφεται στο uiState, καλεί το requestCamera από τον Handler και επιστρέφει το αποτέλεσμα στο ViewModel μέσω onPermissionResult. Ο διαχωρισμός κώδικα πλατφόρμας και επιχειρηματικής λογικής επιτρέπει τη δοκιμή του ViewModel χωρίς εξαρτήσεις Android.
Δοκιμές μονάδας του Permission Handler είναι εφικτές χάρη στη διεπαφή PermissionHandler. Στις δοκιμές δημιουργείται ένα FakePermissionHandler που προσομοιώνει διάφορα σενάρια: άδεια χορηγήθηκε, απορρίφθηκε, Never Ask Again. Κάθε σενάριο δοκιμάζεται ανεξάρτητα. Αυτό είναι ιδιαίτερα σημαντικό για τη δοκιμή λογικής UI που πρέπει να αντιδρά σωστά και στα τρία αποτελέσματα.
class FakePermissionHandler : PermissionHandler {
var cameraResult: PermissionResult =
PermissionResult.GRANTED
var grantedPermissions: Set<String> =
setOf(Manifest.permission.CAMERA)
override fun requestCamera(
callback: (PermissionResult) -> Unit
) {
callback.invoke(cameraResult)
}
override fun isPermissionGranted(
permission: String
): Boolean {
return permission in grantedPermissions
}
}
Η υλοποίηση Fake επιτρέπει τη δοκιμή ViewModel χωρίς εξομοιωτή. Αρκεί να ορίσετε το cameraResult στην επιθυμητή τιμή και να ελέγξετε ότι το ViewModel ενημερώνει σωστά το UiState. Οι δοκιμές ολοκλήρωσης ελέγχουν τον πραγματικό PermissionHandler με ActivityScenario, αλλά συνήθως υπάρχουν 2-3 τέτοιες δοκιμές για ολόκληρη την εφαρμογή — τα υπόλοιπα σενάρια καλύπτονται από δοκιμές μονάδας με fake.
Συνηθισμένα λάθη κατά την εργασία με Permission Handler περιλαμβάνουν: απουσία ελέγχου checkSelfPermission πριν από κάθε κλήση API, αγνόηση του shouldShowRequestPermissionRationale, επαναλαμβανόμενη κλήση requestPermissions σε Never Ask Again και αποθήκευση εκκινητών χωρίς να λαμβάνεται υπόψη ο κύκλος ζωής του Activity. Ας εξετάσουμε κάθε πρόβλημα και τη λύση του.
Το συνηθέστερο λάθος — κλήση API χωρίς έλεγχο της κατάστασης άδειας. Οι προγραμματιστές υποθέτουν ότι αν μια άδεια χορηγήθηκε μία φορά, θα παραμείνει για πάντα. Ωστόσο, ο χρήστης μπορεί να την ανακαλέσει ανά πάσα στιγμή μέσω των ρυθμίσεων. Το Permission Handler πρέπει πάντα να καλεί το isPermissionGranted πριν εκτελέσει μια ευαίσθητη λειτουργία. Το δεύτερο συνηθισμένο λάθος — αγνόηση του shouldShowRequestPermissionRationale και επαναλαμβανόμενο αίτημα που οδηγεί σε άμεση απόρριψη χωρίς διάλογο σε Never Ask Again.
Οι βέλτιστες πρακτικές περιλαμβάνουν: δημιουργία ενός στιγμιότυπου Handler για ολόκληρο τον κύκλο ζωής του Activity, χρήση SharedFlow για μεταφορά αποτελεσμάτων στο ViewModel, καταγραφή όλων των αιτημάτων και απορρίψεων για ανάλυση, καθώς και εμφάνιση προσαρμοσμένου διαλόγου rationale πριν από τον σύστημα διάλογο στην πρώτη απόρριψη. Η τήρηση αυτών των κανόνων εγγυάται σταθερή λειτουργία με άδειες σε όλες τις εκδόσεις Android.
Συχνές Ερωτήσεις
Permission Handler — ένα στοιχείο για κεντρική διαχείριση αδειών runtime, που ενσωματώνει τα checkSelfPermission, requestPermissions και shouldShowRequestPermissionRationale. Απλοποιεί τη συντήρηση κώδικα και βελτιώνει τη δοκιμή.
Συνιστάται το ActivityResultContracts.RequestPermission από τη βιβλιοθήκη androidx.activity. Αντικαθιστά το παρωχημένο onRequestPermissionsResult και παρέχει καθαρό callback API με αποτέλεσμα Boolean.
Για μία άδεια ο Handler δεν είναι υποχρεωτικός — μπορείτε να χρησιμοποιήσετε απευθείας κλήση του εκκινητή RequestPermission στο Activity. Ο Handler καθίσταται απαραίτητος με 3 ή περισσότερες άδειες για να αποφευχθεί η επανάληψη κώδικα.
Δημιουργήστε μια διεπαφή PermissionHandler και την fake υλοποίησή της για δοκιμές μονάδας. Το Fake επιστρέφει προκαθορισμένα αποτελέσματα χωρίς κλήσεις συστήματος. Αυτό επιτρέπει τη δοκιμή ViewModel και λογικής UI χωρίς εξομοιωτή.
Μετά την απόρριψη, ελέγξτε το shouldShowRequestPermissionRationale. Εάν η μέθοδος επέστρεψε false — η λειτουργία Never Ask Again είναι ενεργή. Ο Handler πρέπει να επιστρέψει PermissionResult.DENIED(false) και το UI να εμφανίσει ένα κουμπί μετάβασης στις Ρυθμίσεις.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης