Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Разрешения в Android

Обновлено 20.08.2024, 14 октября 2025

Одно разрешение
Несколько разрешений

Начиная с Android 6.0 Marshmallow, радикально изменилась модель разрешений для доступа к важным системным настройкам.

Разрешения представляют собой набор прав, которые приложения должны запрашивать у пользователей, чтобы получить доступ к различным функциям и данным на устройстве. Эти разрешения охватывают широкий спектр возможностей, начиная от доступа к камере и микрофону и заканчивая чтением контактов и получением информации о местоположении. Система разрешений предназначена для защиты конфиденциальной информации пользователей и поддержания целостности операционной системы.

Старая система, когда пользователь скачивал приложение и перед установкой видел диалоговое окно, в котором перечислялись используемые разрешения, ушла в прошлое. Пользователя ставили перед фактом, и он никак не мог повлиять на настройки.

Теперь пользователи могут контролировать доступ приложений к ресурсам устройства. Запросы будут появляться прямо во время работы приложения и пользователи смогут выбрать, давать разрешение или нет. А также смогут управлять правами приложений через системное меню настроек (Settings | Apps | Any AppName). Данное изменение потребует от разработчиков создавать приложения таким образом, чтобы запросы на доступ появлялись, когда это требуется, и учитывать, что некоторые разрешения на доступ не будут получены. Но краха приложений не будет, система попытается подставить пустые значение.

Например, каждый раз, когда приложение обращается с запросом на местоположение пользователя, мы должны проверить, есть ли у приложения разрешение от пользователя на это действие. Если есть — обращаемся к нужным нам системным ресурсам, если нет — запрашиваем. Так же пользователь может навсегда приложению запретить доступ, тогда единственный наш шанс — это попросить его самого зайти в настройки и снять запрет, показав ему объясняющее сообщение, зачем нам нужен доступ.

Разрешения делятся на два типа:

  • Нормальные разрешения (normal), вроде доступа к сети, Bluetooth, звуковым оповещениям, вибрации, установка обоев на главном экране
  • Опасные разрешения (dangerous). В этот список входят разрешения на календарь, камеру, контакты, местоположение, микрофон, телефон, сенсоры, смс и внешнее хранилище

Нормальные разрешения автоматически предоставляются системой во время установки. Когда приложению требуется нормальное разрешение, оно должно быть объявлено в файле AndroidManifest.xml. В процессе установки система Android проверяет разрешения, запрашиваемые приложением, и автоматически предоставляет их без какого-либо вмешательства со стороны пользователя.


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">
    
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SET_WALLPAPER" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <application
        ...>
        <activity android:name=".MainActivity">
            ...
        </activity>
    </application>

</manifest>

В отличие от обычных нормальных разрешений, которые предоставляются автоматически, опасные разрешения требуют явного согласия пользователя. Это означает, что, когда приложение запрашивает опасное разрешение, пользователь должен одобрить его в соответствующем диалоговом окне. Все опасные разрешения мы должны постоянно проверять, так как пользователь может в любой момент их запретить. Да и при первом старте доступа у приложения к ним нет. Последовательность наших шагов:

  • Описать только PROTECTION_NORMAL запросы в манифесте
  • Пользователь их все подтвердит при установке
  • Когда приложению нужен доступ к одному или нескольким разрешениям из группы опасных, проверить, нет ли разрешения
    Если разрешения нет — запросить
    Если разрешения не будет — объяснить, на что это повлияет
    Если разрешение получено — продолжить работу

Чтобы проверить доступность разрешения, вызываем ContextCompat.checkSelfPermission (Context context, String permission). Метод требует контекст и название разрешения. Метод возвращает константу PackageManager.PERMISSION_GRANTED (если разрешение есть) или PackageManager.PERMISSION_DENIED (если разрешения нет). Если разрешение есть, значит мы ранее его уже запрашивали, и пользователь подтвердил его.

Если разрешения нет, то переходим к следующему шагу. Чтобы запросить разрешения, показав системный диалог, вызываем ActivityCompat.requestPermissions(). Мы вызываем метод, передаём ему данные и request code, а ответ потом получаем в методе onResult (см. ниже).

Результат запроса придёт в асинхронный колбэк onRequestPermissionsResult(), в нём мы узнаем решение пользователя по каждому из запрошенных разрешений.

Запрашивать следует лишь те разрешения, которые действительно нужны.

Если есть возможность, вместо запроса воспользуйтесь Intent. Например, для фото или видео часто нет смысла встраивать камеру в приложение, гораздо проще воспользоваться внешним приложением.

Запрашивайте разрешение, только перед тем, когда оно понадобится. Запрашивать при старте приложения все разрешения нелогично (из тех, которые нам нужны), их смысл как раз в том, что мы запрашиваем их в контексте их использования. Например, пользователю становится понятно зачем его банковскому клиенту доступ к контактам — чтобы выбрать одного из них, чтобы поделиться информацией по ФИО.

Поясняйте пользователю, для чего запрашивается разрешение. Если пользователь все же запретил приложению доступ, а без него оно не может, оно должно максимально понятно объяснить, что без этого разрешения оно работать дальше не будет.

Нужно разобраться с алгоритмом обработки требуемого разрешения.

Первое - сначала нужно убедиться, что у приложения есть разрешения для выполнения задачи.

Система проверяет, получило ли приложение необходимое разрешение через метод checkSelfPermission().

Если разрешение имеется, то обрабатываем результат и продолжаем работу. В противном случае система проверяет, спрашивали ли мы у пользователя о предоставлении разрешения и выводит стандартное диалоговое окно, если такого запроса не было. Если пользователь ни разу не был спрошен, то выводится схожее диалоговое окно с дополнительным флажком. Если флажок будет отмечен, то результат будет возвращён в метод onRequestPermissionsResult() и соответствующим образом обработан.

Ничего страшного, если пользователь отклонит разрешение в первый раз. В нужный момент вы всегда можете снова обратиться к пользователю, убеждая его в том, что без запрашиваемого разрешения приложение не сможет предоставить ему нужную функциональность. Здесь нам поможет метод shouldShowRequestPermissionRationale().

Одно разрешение

Допустим, наше приложение должно уметь определять местоположение пользователя. Для этого требуется разрешение android.permission.ACCESS_FINE_LOCATION. Пропишем его как и раньше в манифесте (вдобавок студия попросит дополнительное разрешение).


<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Далее напишем код для запроса на получение разрешения.


// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.permission

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat

class MainActivity : AppCompatActivity() {

    private val REQUEST_LOCATION = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        val button: Button = findViewById(R.id.button)
        button.setOnClickListener {
            requestSinglePermission()
        }
    }

    private fun requestSinglePermission() {
        val locationPermission = Manifest.permission.ACCESS_FINE_LOCATION
        val hasPermission = ActivityCompat.checkSelfPermission(this, locationPermission)
        val permissions = arrayOf(locationPermission) // массив разрешений

        if (hasPermission != PackageManager.PERMISSION_GRANTED) {
            // Выводим диалоговое окно
            ActivityCompat.requestPermissions(this, permissions, REQUEST_LOCATION)
        } else {
            // У нас уже было разрешение
            println("У нас есть есть разрешение на определение местоположения")
        }
    }
}

Могут быть и другие варианты для разрешений, например Manifest.permission.SEND_SMS и т.д.

Метод checkSelfPermission() проверяет разрешение. Если вы забудете прописать в манифесте нужное разрешение, то метод вернёт значение PERMISSION_DENIED. Если вы прописали разрешение в манифесте, то метод вернёт PERMISSION_GRANTED. В таком случае вы можете вывести диалоговое окно при помощи метода requestPermissions(). Обратите внимание, что в методе используется массив разрещений permissions. В нашем случае достаточно одного элемента массива.

У пользователя будет два варианта - принять или отклонить запрашиваемое разрешение. Если пользователь выберет вариант Запретить, то вернётся константа PERMISSION_DENIED. Это означает, что при следующем нажатии на кнопку диалоговое окно больше не появится. Тут вы бессильны. Чтобы вернуться к первоначальным настройкам, нужно зайти в Настройки Приложения - Ваше приложение - раздел Разрешения и вручную включить разрешения.

Теперь рассмотрим другой вариант - пользователь вам доверяет и выбрал вариант Только в этот раз. Приложение получило разрешение на получение определения местоположения. Повторные нажатия на кнопку уже не будут выводить диалоговое окно - разрешение-то уже имеется. И мы можем выполнить какой-то код. Не забываем, что пользователь может опять зайти в настройки и отозвать разрешение.

Прекрасно, мы разобрались, как работает система. Но как определить выбранный вариант пользователя? Ответ от диалогового окна поступает в метод onRequestPermissionsResult().


override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if(requestCode == REQUEST_LOCATION && grantResults.isNotEmpty()){
        for(i in grantResults.indices){
            if(grantResults[i] == PackageManager.PERMISSION_GRANTED){
                println("${permissions[i]} granted")
                // ваш код после получения разрешения
            } else {
                println("Пользователь отклонил запрос")
            }
        }
    }
}

Разрешений может быть несколько, поэтому используется массив grantResults. Проверяем первое и единственное разрешение в этом примере и выясняем выбор пользователя. Если пользователь отклонит разрешение, то мы можем вывести сообщение, что не сможем сделать необходимую работу, например, определить его местоположение.

Несколько разрешений

Рассмотрим вариант, когда запрашиваем сразу несколько разрешений.


private val REQUEST_PERMISSIONS = 101

private fun requestMultiplePermissions() {
    val locationPermission: String = Manifest.permission.ACCESS_FINE_LOCATION
    val calendarPermission: String = Manifest.permission.WRITE_CALENDAR
    val hasLocPermission = ContextCompat.checkSelfPermission(this, locationPermission)
    val hasCalPermission = ContextCompat.checkSelfPermission(this, calendarPermission)
    val permissions: MutableList<String> = ArrayList()

    if (hasLocPermission != PackageManager.PERMISSION_GRANTED) {
        permissions.add(locationPermission)
    }

    if (hasCalPermission != PackageManager.PERMISSION_GRANTED) {
        permissions.add(calendarPermission)
    }

    if (permissions.isNotEmpty()) {
        ActivityCompat.requestPermissions(this, permissions.toTypedArray(),
            REQUEST_PERMISSIONS)
    } else {
        println("У нас уже есть разрешения")
    }
}

Мы запрашиваем два разрешения одновременно - определение местоположения и доступ к календарю. Добавляем их в списочный массив и выводим диалоговое окно. На этот раз в диалоговом окне пользователь увидит счётчик разрешений. Пользователю придётся последовательно ответить на вопросы несколько раз по отдельности.

Ниже представлены старые варианты диалоговых окон. Надеюсь, вы разберётесь в изменениях.

Соответственно, если пользователь даст согласие на одно разрешение и запретит другое, то в следующий раз будет запрашиваться доступ только к отклонённому ранее разрешению.

Манифест для Marshmallow

В манифесте можно указать разрешение, которое будет действительно только для устройств с Marshmallow:


<uses-permission-sdk-m android:name="android.permission.ACCESS_FINE_LOCATION"/>

В начале статьи я показал, как можно посмотреть на разрешения у конкретной программы. Можно также посмотреть наоборот, какие программы используют то или иное разрешение. Для этого идём снова в Настройки | Программы и щёлкаем на значке шестерёнки. Выбираем пункт Разрешения приложений. Вы увидите список разрешений и количество программ, которые используют данное разрешение. Вы можете зайти в нужное разрешение, чтобы увидеть детальную информацию о программах, которые используют разрешение и отключить/включить при желании.

Старые приложения, написанные до Android 6.0, будут работать в прежнем режиме с небольшими изменениями. Пользователь может отозвать ненужные на его взгляд разрешения после установки приложения. В этом случае будет выводится сообщение, что программа была разработана для старых устройств и может работать некорректно. Если упрямый пользователь тем не менее запретит доступ к каким-то разрешениям, то программа будет поставлять какие-то левые данные. Например, это могут быть нулевые значения для сенсоров. При работе с контактами будет выводиться сообщение, что нет контактов. Для определения местоположения будет выводиться сообщение, что определение недоступно и т.д. В этом случае программа продолжает свою работу без аварийного завершения, но доверять показаниям уже нет смысла.

В процессе отладки часто приходится включать/отключать разрешения. Заходить для этого каждый раз в настройки приложения не очень удобно. Можно подавать команды через adb:


adb shell pm grant <app package> <permission name>
adb shell pm revoke <app package> <permission name>
adb shell pm reset-permissions
adb shell pm list permission-groups
adb shell pm list permissions

Позже в Android появился новый способ запрашивать разрешения через RequestPermission.

Дополнительное чтение

Разрешение android.permission.RECORD_AUDIO

Пример с определением местоположения

Позвонить по телефону

Звоним по номерам контактов

Отправить SMS программно

Разрешение ACCESS_FINE_LOCATION для карты

Диктофон - делаем запись с микрофона

Реклама