Permission Handler مؤلفهای از برنامه اندروید است که مسئول بررسی، درخواست و پردازش نتایج مجوزهای runtime میباشد. به گفته Android Developer Guide, 2024، handler مجوز منطق checkSelfPermission، requestPermissions و shouldShowRequestPermissionRationale را در یک کلاس یا ViewModel متمرکز میکند. این کار نگهداری کد را سادهتر و آزمایش را بهبود میبخشد.
نکات اصلی
Permission Handler — یک الگوی معماری برای مدیریت متمرکز مجوزهای runtime اندروید است. به جای فراخوانیهای پراکنده ContextCompat.checkSelfPermission و ActivityCompat.requestPermissions در سراسر کد برنامه، تمام منطق درخواستها و پردازش نتایج در یک کلاس متمرکز میشود. این کار تکرار را کاهش میدهد، نگهداری را ساده میکند و کد را قابل پیشبینیتر میسازد.
نیاز به Permission Handler با معرفی مجوزهای runtime در اندروید 6.0 به وجود آمد. پیش از آن، همه مجوزها هنگام نصب درخواست میشدند و کد برنامه میتوانست بدون بررسی از هر API استفاده کند. پس از انتقال به مدل runtime، هر استفاده از مجوز خطرناک نیاز به بررسی سه مرحلهای دارد: checkSelfPermission، requestPermissions، onRequestPermissionsResult. پراکنده شدن این منطق در Activity و Fragment منجر به تکرار درونخطی و خطا میشود. طبق دادههای Google I/O 2019، متمرکزسازی پردازش مجوزها تعداد باگهای مربوط به Permission Denial را به طور متوسط 60 درصد کاهش میدهد.
یک Permission Handler خوب رابط کاربری تمیزی برای کد فراخوان فراهم میکند. Activity یا Fragment نیازی به دانستن جزئیات درخواست ندارند — آنها متدی مانند requestCamera(callback) را فراخوانی میکنند و handler خودش بررسی وضعیت، نمایش rationale، فراخوانی دیالوگ سیستمی و ارسال نتیجه به callback را مدیریت میکند. این اصل مسئولیت واحد را پیادهسازی کرده و منطق کسبوکار را از کد پلتفرم مجوزها جدا میکند.
Permission Handler زمانی ضروری میشود که برنامه از 3 مجوز خطرناک یا بیشتر استفاده میکند. برای برنامههای ساده با یک مجوز (مثلاً دوربین برای اسکنر QR) میتوان با فراخوانی مستقیم کار کرد. اما برای یک برنامه موبایل معمولی با دوربین، موقعیتیابی، اعلانها و حافظه — handler متمرکز برای نگهداری اجباری است.
یک Permission Handler معمولی از سه لایه تشکیل شده است: رابط-قرارداد، پیادهسازی با ActivityResultLauncher و لایه برای ViewModel. رابط متدهای درخواست برای هر مجوز را تعریف میکند — requestCamera، requestLocation، requestStorage. پیادهسازی این متدها را با قراردادهای ActivityResultContracts.RequestPermission مرتبط میکند.
مؤلفههای کلیدی معماری:
این معماری امکان جایگزینی آسان پیادهسازی را در آزمایشها فراهم میکند: به جای ActivityResultLauncher واقعی از یک mock استفاده میشود که بدون تعامل با سیستم نتیجه از پیش تعیینشده را برمیگرداند. این برای آزمون واحد منطق UI که در آن راهاندازی Activity برای دیالوگ مجوز غیرممکن است، حیاتی میباشد.
Permission Handler باید چرخه حیات Activity و Fragment را در نظر بگیرد. Launcherها در ActivityResultRegistry ثبت میشوند که به طور خودکار وضعیت را در هنگام چرخش صفحه و بازآفرینی Activity ذخیره و بازیابی میکند. Handler نباید ارجاعات مستقیم به Activity یا Fragment نگه دارد — به جای آن از WeakReference استفاده کنید یا registry را از طریق سازنده ارسال کنید. این کار از نشت حافظه و crash در هنگام تغییرات پیکربندی جلوگیری میکند.
پیادهسازی پایه Permission Handler بر ActivityResultContracts.RequestPermission ساخته میشود. Handler از ComponentActivity یا Fragment ActivityResultRegistry را دریافت کرده و برای هر مجوز launcherهایی ثبت میکند. هر launcher یک لامبدا-کالبک دریافت میکند که پس از پاسخ کاربر فراخوانی میشود.
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 را فراهم میکند. پس از مقداردهی، handler آماده پردازش درخواستها در طول چرخه حیات Activity است. مهم است که initialize قبل از اولین درخواست فراخوانی شود، در غیر این صورت launcher ثبت نخواهد شد.
ادغام 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 در شروع از طریق Handler isPermissionGranted را بررسی میکند و ViewModel فقط وضعیت را مدیریت میکند. اگر مجوز داده نشده باشد — Activity مشترک uiState میشود، requestCamera را از Handler فراخوانی کرده و نتیجه را از طریق onPermissionResult به ViewModel برمیگرداند. جداسازی کد پلتفرم و منطق کسبوکار امکان آزمایش ViewModel را بدون وابستگیهای اندروید فراهم میکند.
آزمون واحد 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 را بهروزرسانی میکند. آزمایشهای یکپارچهسازی Permission Handler واقعی را با ActivityScenario بررسی میکنند، اما معمولاً 2-3 مورد از این آزمایشها برای کل برنامه کافی است — بقیه سناریوها با آزمایشهای واحد با fake پوشش داده میشوند.
خطاهای رایج هنگام کار با Permission Handler شامل: عدم بررسی checkSelfPermission قبل از هر فراخوانی API، نادیده گرفتن shouldShowRequestPermissionRationale، فراخوانی مجدد requestPermissions در حالت Never Ask Again و ذخیره launcherها بدون در نظر گرفتن چرخه حیات Activity است. بیایید هر مشکل و راهحل آن را بررسی کنیم.
رایجترین خطا — فراخوانی API بدون بررسی وضعیت مجوز. توسعهدهندگان فرض میکنند که اگر مجوز یک بار اعطا شده باشد، برای همیشه باقی میماند. اما کاربر میتواند آن را در هر لحظه از طریق تنظیمات لغو کند. Permission Handler باید قبل از انجام عملیات حساس همیشه isPermissionGranted را فراخوانی کند. دومین خطای رایج — نادیده گرفتن shouldShowRequestPermissionRationale و درخواست مجدد که در حالت Never Ask Again منجر به رد فوری بدون دیالوگ میشود.
بهترین شیوهها شامل: ایجاد یک نمونه Handler برای کل چرخه حیات Activity، استفاده از SharedFlow برای ارسال نتایج به ViewModel، ثبت تمام درخواستها و ردها برای تحلیل، و همچنین نمایش دیالوگ rationale سفارشی قبل از دیالوگ سیستمی در اولین رد. پیروی از این قوانین عملکرد پایدار با مجوزها را در تمام نسخههای اندروید تضمین میکند.
سوالات متداول
Permission Handler — مؤلفهای برای مدیریت متمرکز مجوزهای runtime است که checkSelfPermission، requestPermissions و shouldShowRequestPermissionRationale را کپسوله میکند. نگهداری کد را ساده و آزمایش را بهبود میبخشد.
ActivityResultContracts.RequestPermission از کتابخانه androidx.activity توصیه میشود. این API جایگزین onRequestPermissionsResult قدیمی شده و یک API callback تمیز با نتیجه Boolean ارائه میدهد.
برای یک مجوز Handler اجباری نیست — میتوانید از فراخوانی مستقیم RequestPermission launcher در Activity استفاده کنید. Handler با 3 مجوز یا بیشتر برای جلوگیری از تکرار کد ضروری میشود.
یک رابط PermissionHandler و پیادهسازی fake آن برای آزمایشهای واحد ایجاد کنید. Fake نتایج از پیش تعیینشده را بدون فراخوانی سیستمی برمیگرداند. این امکان آزمایش ViewModel و منطق UI را بدون شبیهساز فراهم میکند.
پس از رد، shouldShowRequestPermissionRationale را بررسی کنید. اگر متد false برگرداند — حالت Never Ask Again فعال شده است. Handler باید PermissionResult.DENIED(false) را برگرداند و UI باید دکمه انتقال به تنظیمات را نمایش دهد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید