Как запросить runtime‑разрешения в Android (Java) и корректно обработать отказ
Чтобы запросить runtime‑разрешение в Android на Java, проверяйте ContextCompat.checkSelfPermission, запускайте запрос через ActivityResultContracts.RequestPermission (ActivityResultLauncher) и при отказе используйте shouldShowRequestPermissionRationale(), чтобы либо показать объяснение и повторить запрос, либо отправить пользователя в настройки.
Оглавление
Проверка и запрос одного разрешения
- Сначала проверьте состояние разрешения:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// нужно запросить
} else {
startCamera();
}
- Инициализируйте ActivityResultLauncher в onCreate (регистрировать нужно до первого использования):
private ActivityResultLauncher<String> requestPermissionLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
isGranted -> {
if (isGranted) {
startCamera();
} else {
handlePermissionDenied();
}
});
}
- Запуск запроса:
private void requestCameraPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.CAMERA);
} else {
startCamera();
}
}
Всегда проверяйте разрешение перед запросом — лишние запросы раздражают пользователя и снижают конверсию.
Обработка отказа и "Don't ask again"
После отказа важно понять, был ли выбран "Не спрашивать снова". Для этого используйте shouldShowRequestPermissionRationale():
private void handlePermissionDenied() {
if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
// Пользователь выбрал "Не спрашивать снова" или система не будет показывать диалог
showAppSettingsDialog();
} else {
// Показываем объяснение и предлагаем повторить запрос
showPermissionRationaleDialog();
}
}
private void showPermissionRationaleDialog() {
new AlertDialog.Builder(this)
.setMessage("Камера нужна для сканирования QR-кодов. Разрешите доступ?")
.setPositiveButton("OK", (d, w) -> requestCameraPermission())
.setNegativeButton("Отмена", null)
.show();
}
private void showAppSettingsDialog() {
new AlertDialog.Builder(this)
.setMessage("Разрешение отключено. Включите доступ в настройках приложения.")
.setPositiveButton("Настройки", (d, w) -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
})
.setNegativeButton("Отмена", null)
.show();
}
Практический совет: в объяснении указывайте конкретную пользу (что произойдет, если дать разрешение), а не общие фразы.
На Android 11+ доступны одноразовые (one-time) разрешения — доступ может быть сброшен после закрытия приложения. Учтите это в UX и проверяйте разрешения каждый запуск.
Множественные разрешения
Для групповых запросов используйте RequestMultiplePermissions:
private ActivityResultLauncher<String[]> multiplePermissionsLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
multiplePermissionsLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
results -> {
Boolean cam = results.get(Manifest.permission.CAMERA);
Boolean mic = results.get(Manifest.permission.RECORD_AUDIO);
if (Boolean.TRUE.equals(cam) && Boolean.TRUE.equals(mic)) {
startVideoRecording();
} else {
handleMultiplePermissionDenied(results);
}
});
}
private void requestCameraAndMic() {
multiplePermissionsLauncher.launch(new String[]{
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
});
}
Обрабатывайте по отдельности: возможно, нужно объяснить необходимость именно того разрешения, которое отсутствует.
Рекомендации по UX и тестированию
- Запрашивайте разрешение в контексте действия (перед камерой — при попытке открыть камеру), не при старте приложения.
- Первое объяснение: короткое и конкретное (что делает функция, зачем нужно разрешение).
- После отказа — не надо бесконечно предлагать. Если пользователь отказал несколько раз, предложите альтернативу или перевод в настройки.
- Тестируйте сценарии: первый запрос, отказ + повторный запрос, отказ с "Don't ask again", одноразовое разрешение, ревокирование через adb.
Тестирование через adb:
- revoke: adb shell pm revoke com.yourapp android.permission.CAMERA
- grant: adb shell pm grant com.yourapp android.permission.CAMERA
Частые ошибки
- Не указали permission в AndroidManifest.xml:
- Запрос до инициализации UI/лаунчера — регистрируйте лаунчеры в onCreate, но просите разрешение когда UI готов.
- Игнорирование shouldShowRequestPermissionRationale — без объяснения пользователи чаще отказывают.
- Непроверка состояния после возврата из настроек — пользователь мог не дать разрешение.
FAQ
-
Нужно ли запрашивать обычные (normal) разрешения?
Нет, обычные разрешения выдаются автоматически при установке. -
Чем заменить deprecated onRequestPermissionsResult?
Рекомендуется использовать ActivityResultContracts.RequestPermission / RequestMultiplePermissions и ActivityResultLauncher. -
Как понять, что пользователь выбрал "Не спрашивать снова"?
shouldShowRequestPermissionRationale(...) вернёт false после окончательного отказа (плюс при первом показе тоже false — учитывайте логику вызовов). -
Если пользователь дал одноразовое разрешение, нужно ли повторять запрос?
Да — одноразовое разрешение может быть отменено после закрытия приложения, поэтому проверяйте и запрашивайте заново при следующем запуске/действии.
Если нужно, могу прислать пример для Jetpack Compose или переводить логику на Kotlin.