Разрешения Среды Выполнения Android. Почему, Почему И Как

Часто при установке приложения на Android мы видели, что оно запрашивает какое-то невообразимое количество разрешений.

Например:

Разрешения среды выполнения Android. Почему, почему и как

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

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

Или, тем более, фонарик, требующий доступа к СМС и звонкам.

Некоторые разработчики, чтобы уменьшить недоверие, добавляют в описание приложения в Google Play информацию о том, зачем нужно то или иное разрешение.

К шестой версии Android ситуация изменилась.

Теперь разрешения необходимо запрашивать в процессе работы.

Как использовать эту новую возможность и некоторые ее подводные камни, мы обсудим ниже.



Общая информация

Подобно тому, что происходит в iOS, при появлении запроса появится системное диалоговое окно с запросом разрешения.



Разрешения среды выполнения Android. Почему, почему и как



Разрешения среды выполнения Android. Почему, почему и как

Разница в том, что после нажатия кнопки «Запретить» приложению не будет полностью отказано в разрешении, как в случае с Apple. Его можно запросить еще раз, но в этом случае появится опция «Никогда больше не спрашивать», после выбора которой «Запретить» работает как «Не разрешать» в iOS. Разрешения делятся на два типа (есть и другие, но они нас не интересуют):
  • нормальный;
  • опасный (опасный).

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

В дальнейшем отозвать их из приложения будет невозможно.

Опасные необходимо запрашивать во время работы приложения и можно отозвать в любой момент. Вы можете увидеть список опасных и не очень опасных разрешений.

здесь .

Видно, что доступ в Интернет не считается опасным.

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

Чтобы отозвать ранее предоставленное разрешение (или предоставить его, если вы выбрали «Больше никогда не спрашивать»), вам необходимо зайти в настройки приложения (Настройки-> Приложения-> *ИмяПриложения*) в разделе Разрешения и нажать на соответствующие переключатели.

В этом меню вы также можете просмотреть все разрешения данной программы, выбрав «Все разрешения» в контекстном меню.

Существует также возможность просмотреть, каким приложениям было предоставлено определенное разрешение (и соответственно предоставить или отозвать его).

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

Далее, выбрав нужное разрешение, вы сможете увидеть все приложения, которым оно необходимо.



Разрешения среды выполнения Android. Почему, почему и как



Разрешения среды выполнения Android. Почему, почему и как



Взаимодействие с пользователем

Давайте посмотрим, как взаимодействовать с пользователем.

Начнем непосредственно с запроса разрешения.

С обычными разрешениями всё понятно, это дело установщика приложения, нас это не интересует, а вот то, как мы запрашиваем опасные разрешения, зависит от двух вещей.

Первым из них является важность этого разрешения для вашего приложения, которое определяет, когда делать запрос.

Если функция критическая и без нее программа не имела бы смысла, то смело спрашивайте разрешение прямо при запуске приложения.

Например, если вы разрабатываете приложение для обмена СМС, то без соответствующих разрешений вы не сможете ничего с ним сделать; оно теряет всякий смысл.

А если пользователь отказывается, то мы не пропускаем его дальше, а даем ему возможность еще раз вызвать диалог запроса и дать инструкции, что делать.

Если разрешение требуется для какой-то второстепенной функции, то не нужно сразу его спрашивать.

Делайте это только тогда, когда пользователь хочет использовать эту функцию.



Разрешения среды выполнения Android. Почему, почему и как



Разрешения среды выполнения Android. Почему, почему и как

Второй момент – насколько человеку будет понятно, зачем нужно это разрешение.

Зачем приложению SMS нужен доступ к вашему календарю? Наверное за какую-то классную функцию, которая облегчит перенос дат из сообщений в календарь и тому подобное.

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

Это касается как первичных, так и вторичных разрешений.



Разрешения среды выполнения Android. Почему, почему и как



Разрешения среды выполнения Android. Почему, почему и как

Коротко еще раз:
  • Важные разрешения мы запрашиваем при запуске, вторичные — при первом использовании соответствующей функции;
  • Если сложно понять, зачем нужно разрешение, мы предоставим объяснение.

В случае, если вам все же откажут, объяснение причины в следующий раз обязательно.

А если помимо отказа пользователь попросил вас никогда не запрашивать это разрешение с помощью опции «Никогда больше не спрашивать», но пытается использовать соответствующую функцию приложения, предложите ему зайти в настройки вашей программы и вручную включить необходимые разрешения.

Это особенно важно, если разрешение критично для работы программы.



Разрешения среды выполнения Android. Почему, почему и как



Разрешения среды выполнения Android. Почему, почему и как



Код

Логичным будет вопрос: что произойдет, если вы запустите свое приложение, не адаптированное для разрешения во время выполнения, на Android Marshmallow? Ответ зависит от того, осмелились ли вы поменять targetSdk на 23 версию (compileSdk и buildTools нас в данном случае не интересуют).

Если да, то у меня для вас плохие новости: весьма вероятно, что вы получите SecurityException. Это не обязательно будет так, возможно где-то вместо запрошенной информации вы получите null, но вероятность далеко не нулевая.

Если вы используете targetSdk версии 22 и ниже, то при установке приложению, как и раньше, будут предоставлены все разрешения, включая опасные.

Это не меняет того факта, что пользователь может отозвать любой из них после установки.

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

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

Но даже если с вашими проверками все в порядке и у вас нет возможности реализовать новые возможности, стоит перепроверить, все ли работает корректно, ведь иногда вы можете получить null не там, где ожидаете.

Так, например, при использовании Environment.getExternalStorageDirectory() без разрешения группы Storage мы получим File, но list() вернет нам заветный ноль.

В документации описан этот результат, но для ситуации, когда File не является каталогом.

Так что проверка в любом случае лишней не будет. Добавить разрешение можно только для Android M и выше.

Для этого вам нужно использовать новый тег (ранее назывался ) в вашем манифесте.

Его синтаксис аналогичен обычному .

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

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

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

В этом случае это разрешение будет полностью отсутствовать.

Во время отладки вам часто приходится включать/отключать разрешения.

Для этого каждый раз заходить в настройки приложения не очень удобно.

К счастью, это можно сделать с помощью 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

Перейдем к непосредственной реализации (не забудьте сначала обновить compileSdkVersion и targetSdkVersion до версии 23).

Момент, когда Marshmallow станет минимальной версией Android для ваших приложений, еще далек, поэтому нужно позаботиться об обратной совместимости.

Конечно, можно проверить версию sdk, но зачем, если у нас все реализовано в библиотеке поддержки v4 (ActivityCompat) и v13 (FragmentCompat).

Если вам все же нужны оригинальные методы, то найти их не составит труда.

Во всех примерах используется ActivityCompat, поскольку они созданы для активности.

Для фрагмента вам нужно использовать FragmentCompat. Если по какой-то причине вы не используете активность и фрагмент из библиотек поддержки, то вам необходимо реализовать интерфейс ActivityCompat.OnRequestPermissionsResultCallback или FragmentCompat.OnRequestPermissionsResultCallback соответственно.

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

Для этого мы используем метод ContextCompat.checkSelfPermission(Context context, String Permission), который возвращает нам одно из значений int: PackageManager.PERMISSION_GRANTED, если разрешение есть, или PackageManager.PERMISSION_DENIED, если его нет. Имя разрешения — это одна из констант класса Manifest.permission.

@Nullable public File[] getExternalStorageFiles() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { final File externalStorage = Environment.getExternalStorageDirectory(); if (externalStorage != null) { return externalStorage.listFiles(); } } return null; }

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

При необходимости вы можете запросить несколько разрешений одновременно (пользователю будет показан запрос по каждому из них по очереди).

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

Но вам не нужно этого делать.

Поскольку состав групп в будущем может измениться, при запросе разрешений нет необходимости делать предположения о том, входят они в одну группу или нет. УПД Как бы в подтверждение предыдущего абзаца, начиная с Android 8.0 разрешения из одной группы разрешений не выдаются сразу - каждое разрешение нужно запрашивать отдельно, а все разрешения из одной группы будут выдаваться автоматически, без вмешательства пользователя, при первом их использовании.

просил.

УПД2 такое же поведение было замечено и на Android 7.0 - если часть разрешений из группы была предоставлена (не могу с уверенностью сказать, какие имеют значение), то остальные будут выданы по запросу сразу без показа диалога.

Это может вызвать проблемы, если ваше приложение объясняет пользователю, почему ему нужно то или иное разрешение, прежде чем его запросить.

В реальной жизни такое случается редко (только при использовании команд adb), но стоит это учитывать при отладке приложения.

В запросе используется метод ActivityCompat.requestPermissions(Activity Activity, разрешения String[], int requestCode).

Массив разрешений соответственно содержит имена разрешений, которые вы хотите запросить.

Это показывает, что вы можете запросить несколько разрешений одновременно.

requestCode — это значение, по которому вы позже сможете определить, на какой запрос разрешения вы получили ответ, так же, как мы получаем результат от активности с помощью startActivityForResult. Кстати, если вы посмотрите на код requestPermission, то обнаружите, что это всего лишь специальная версия startActivityForResult.

public void requestMultiplePermissions() { ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_SMS }, PERMISSION_REQUEST_CODE); }

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

Если сервису требуется разрешение, вам придется запустить Activity, из которого уже можно сделать запрос.

Лучше всего перед этим показать уведомление, содержащее информацию об отсутствующем разрешении, с кнопкой запуска этого самого Activity. Результат запроса разрешения должен быть обработан в onRequestPermissionsResult(int requestCode, @NonNull String[] Permissions, @NonNull int[]grantResults).

Параметры requestCode и Permissions содержат данные, которые вы передали при запросе разрешений.

Основные данные здесь несет массив GrantResults, содержащий информацию о том, получены разрешения или нет. Каждый i-й элемент разрешений соответствует i-му элементу GrantResults. Их возможные значения аналогичны результату checkSelfPermission.

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE && grantResults.length == 2) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { showExtDirFilesCount(); } if (grantResults[1] == PackageManager.PERMISSION_GRANTED) { showUnreadSmsCount(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); }

Размер массива GrantResults проверяется, чтобы убедиться, что запрос разрешения не был прерван (в этом случае разрешения и GrantResults не будут содержать элементов).

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

Если вы ранее запросили разрешение, а пользователь отказывается его предоставить, вам необходимо объяснить ему причину запроса.

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

Если есть вероятность, что вопрос «Зачем приложению это нужноЭ» возникает, крайне желательно это объяснить.

Чтобы узнать, нужно ли показывать объяснение, существует метод mustShowRequestPermissionRationale(@NonNull Activity Activity, @NonNull String Permission), который возвращает логическое значение.

Само объяснение можно реализовать, например, с помощью Snackbar с кнопкой действия, при нажатии которой запрашивается разрешение, или диалогового окна, если разрешение критически необходимо.



public void requestPermissionWithRationale() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { final String message = "Storage permission is needed to show files count"; Snackbar.make(view_, message, Snackbar.LENGTH_LONG) .

setAction("GRANT", new View.OnClickListener() { @Override public void onClick(View v) { requestReadExtStorage(); } }) .

show(); } else { requestReadExtStorage(); } }



Никогда больше не спрашивай
Одной из проблем может быть опция «Никогда больше не спрашивать», которая появляется, когда разрешение запрашивается снова после того, как пользователь ранее уже отказался.

Как следует из названия, при выборе диалоговое окно запроса больше не появляется.

mustShowRequestPermissionRationale вернет false, а onRequestPermissionsResult вернет результат PackageManager.PERMISSION_DENIED. И разрешение мы получим только в том случае, если включим его напрямую через настройки приложения в разделе Разрешения.

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

Далее возможным действием может быть предложение перейти к настройкам и предоставить это разрешение вручную.

Не лучший вариант, но лучше, чем ничего.

Вы можете реализовать это снова, используя Snackbar с кнопкой действия.

Вы не сможете перейти непосредственно на страницу разрешений, поэтому лучшее, что вы можете сделать, — это открыть настройки вашего приложения.

После этого можно, например, показать тост с информацией о том, что нужно сделать.



public void showNoStoragePermissionSnackbar() { Snackbar.make(view_, "Storage permission isn't granted" , Snackbar.LENGTH_LONG) .

setAction("SETTINGS", new View.OnClickListener() { @Override public void onClick(View v) { openApplicationSettings(); Toast.makeText(getApplicationContext(), "Open Permissions and grant the Storage permission", Toast.LENGTH_SHORT) .

show(); } }) .

show(); } public void openApplicationSettings() { Intent appSettingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName())); startActivityForResult(appSettingsIntent, PERMISSION_REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PERMISSION_REQUEST_CODE) { showExtDirFilesCount(); return; } super.onActivityResult(requestCode, resultCode, data); }

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

В методе showExtDirFilesCount нужно еще раз проверить, есть ли разрешение, чтобы быть уверенным, что пользователь его предоставил.

Это может быть ситуация, которая не является особенно разрушительной, если вы используете Snackbar для отображения обоснования, но вредна для UX, если вы решите использовать диалоги (мы не будем вдаваться в причины этого решения).

А именно, двойное появление обоснования, до и после запроса на разрешение.

Как это может произойти? У нас есть только два метода, по которым мы можем судить о статусе разрешения.

Проблема в том, что перед запросом разрешения ситуация, когда мы никогда раньше не запрашивали это разрешение, и ситуация, когда пользователь ранее выбрал «Никогда не спрашивать снова», имеют совершенно одинаковый смысл.

А именно, checkSeflPermission возвращает нам PERMISSION_DENIED, а mustShowRequestPermissionRationale возвращает false. Это значит, что мы покажем диалог открытия настроек в onRequestPermissionsResult, где значение mustShowRequestPermissionRationale точно будет разным для этих двух ситуаций.

Всё хорошо? Не совсем.

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

Хорошие программы так себя не ведут. Что делать в такой ситуации? В Интернете есть парочка не очень приятных решений: одно из них — хранить в SharedPreferences информацию о том, доступно или нет разрешение, другое — хранить внутри класса флаг о том, было ли показано обоснование или нет. Первое решение не является хорошим, поскольку пока приложение не запущено, пользователь может изменить настройки разрешений, и информация в настройках будет неактуальной.

Второй способ не особо красив.

Хорошим вариантом (на мой взгляд) было бы иметь два кода запроса для каждого запроса: один для использования в обосновании, а другой в других случаях.

Этот метод тоже не идеален и не особо красив, но помогает придерживаться существующих методов, не вводя ничего нового.



Намерение
Есть еще одна важная рекомендация при использовании разрешений времени выполнения.

Не используйте их.

Точнее, используйте их, но только тогда, когда тот функционал, который вы собираетесь реализовать с их помощью, еще кем-то не был сделан до вас.

Самый показательный пример — камера.

Используйте стандартное приложение камеры (или другие приложения, которые умеют это делать), если вам просто нужно сделать снимок без какой-либо особой логики.

В этом вам помогут намерения ( подробнее ).

Таким образом, вы сможете избавиться от некоторых опасных разрешений и упростить использование приложения.



Заключение

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

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

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

Конечно, разработчикам придется писать больше кода для реализации новых возможностей, а методы, которые придется для этого использовать, далеки от идеала (это то, что пытаются решить разработчики многочисленных библиотек, о чем хотелось бы поговорить об этом в следующей статье).

Но, несмотря на эти трудности, Google сделал правильный шаг, внедрив эту функцию, поскольку в ней нуждалась удобная система.

Теги: #Android #marshmallow #android 6 #android 8 #разрешения времени выполнения #java #Разработка мобильных приложений #Разработка Android

Вместе с данным постом часто просматривают: