Подтвердить что ты не робот

Можете ли вы запрашивать разрешения синхронно в модели разрешений времени запуска Marshmallow (API 23)?

Скажем, у вас есть такой метод:

public boolean saveFile (Url url, String content) {

   // save the file, this can be done a lot of different ways, but
   // basically the point is...

   return save_was_successful;
}

В вашем приложении, если вы хотите сохранить файл во внешнем хранилище, вы делаете что-то вроде...

if (saveFile(external_storage_url, "this is a test")) {
   // yay, success!
} else { // notify the user something was wrong or handle the error }

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

В Android <= Lollipop, если пользователь установил и согласился предоставить android.permission.WRITE_EXTERNAL_STORAGE или что-то еще, все будет хорошо.

Но в новой модели разрешения Marshmallow (API 23) во время исполнения, прежде чем вы сможете сохранить файл во внешнем хранилище, вы должны (1) проверить, разрешение было предоставлено. Если нет, возможно (2) показать обоснование для запроса (если думает, что это хорошая идея) с тостом или что угодно и (3) попросить пользователя предоставить разрешение через диалог, а затем в основном сидеть сложа руки и ждать обратного вызова...

(так что ваше приложение сидит, ждет...)

(4) Когда пользователь, наконец, отвечает на диалог, срабатывает метод onRequestPermissionsResult(), и теперь ваш код (5) должен просеять через WHICH permission, они фактически отвечают на, сказал ли пользователь "да" или нет (насколько мне известно, нет способ обработки "нет" по сравнению с "нет и не спрашивать снова" ), (6) выяснить, что они пытались выполнить, в первую очередь, что вызвало весь процесс запроса-разрешения, чтобы программа могла наконец, (7) идти вперед и делать это.

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

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

Сегодня Подкаст для разработчиков Android намекнул, что за углом может быть синхронное решение, и даже говорили о волшебном одноэтапном альт-вводе типа "добавить запрос разрешения" в Android Studio. Тем не менее, как процесс разрешения времени выполнения может быть перенесен в saveFile() или что-то еще - я что-то думаю по следующим направлениям:

public boolean saveFile(Url url, String content) {
   //   this next line will check for the permission, ask the user
   //   for permission if required, maybe even handle the rationale
   //   situation
   if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        R.string.permission_storage_rationale))
       {
           return false; // or throw an exception or whatever
       } else {

   // try to save the file

   return save_was_successful;

   }
}

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

Возможно ли такое? Желаемое? Будет ли такое решение блокировать поток пользовательского интерфейса? Из подкаста это звучало так, что у Google может быть такое решение, как это происходит "за углом", но я хотел получить мысли о том, есть ли что-то - класс удобства, образец, что-то, что не включает всех, кто должен рефакторинг всех разрешающих действия операций, которые, как я должен предположить, могут стать довольно грязными.

Извините за длинный вопрос, но я хотел быть максимально полным. Я отведу свой ответ с воздуха. Спасибо!


Обновление. Ниже приведена транскрипция из приведенного выше подкаста.

Слушайте около 41:20. В этом обсуждении:

Грубая транскрипция:

Tor Norbye (команда Tools): "Так кажется, что для разработчиков не должно быть много работы. Но я понимаю, что часть проблемы заключается в том, что это не синхронные вызовы, так?" Итак, что вы имеете на самом деле изменить способ написания вашей деятельности, чтобы иметь обратный вызов - так что это действительно любопытно как конечный автомат, где... для этого состояния вы - "

Poiesz (менеджер продукта):" Ах, я думал, что есть возможность синхронного ответа -"

Norbye: "О, это сделает вещи..."

Poiesz: "Я могу поговорить с людьми внутри. Я вспоминаю дискуссию об синхронном, но мы можем это выяснить".

Norbye: "Да, на самом деле мы должны, вероятно, сделать это в инструментах. Где у вас есть простой рефакторинг..."

Затем он говорит об использовании аннотаций в инструментах, чтобы определить, какие API требуют разрешений.. (что на данный момент не работает с этой большой ИМО) и как он хочет, чтобы инструменты когда-либо фактически генерировали требуемый код, если он находит непроверенный "опасный" вызов метода:

Norbye: "... тогда, если вы тоже на M, он скажет:" Эй, вы действительно проверяете это разрешение или вы ловите исключения безопасности? ", а если нет, мы" Скажем, вам, вероятно, нужно что-то сделать для запроса разрешения здесь ". Я бы хотел, чтобы это было быстрым решением, где вы можете пойти" CHING! ". и он вставляет все нужные вещи для запроса, но, как все было обратно, когда я смотрел, это требовало реструктуризации многих вещей: добавления интерфейсов и обратных вызовов, а также изменения потока и чего мы не могли сделать. простой синхронный режим как временная вещь или постоянная вещь, которая была бы [велика]".

4b9b3361

Ответ 1

По сравнению с Marshmallow, я понимаю, что вы не можете.

Мне пришлось решить ту же проблему в моем приложении. Вот как я это сделал:

  • Рефакторинг. Переместите каждый фрагмент кода, который зависит от каких-либо разрешений в свой собственный метод.
  • Больше рефакторинга. Определите триггер для каждого метода (например, запуск активности, нажатие на элемент управления и т.д.) и групповые методы вместе, если они имеют один и тот же триггер. (Если два метода заканчиваются тем же самым триггером и требуют того же набора разрешений, рассмотрите их слияние.)
  • Еще больше рефакторинга. Узнайте, что зависит от новых методов, которые были вызваны ранее, и убедитесь, что они гибки в том, когда вызывается эти методы: либо переместите его в сам метод, либо в по крайней мере, убедитесь, что все, что вы делаете, не генерирует исключений, если ваш метод еще не был вызван, и что он начинает вести себя так, как ожидалось, как только вызывается метод.
  • Коды запроса. Для каждого из этих методов (или их групп) определите целочисленную константу, которая будет использоваться в качестве кода запроса.
  • Проверка и запрос разрешений. Оберните каждый из этих методов/групп методов в следующий код:

.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED)
    doStuffThatRequiresPermission();
else
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION);
  1. Обработка ответов. Запишите реализацию для onRequestPermissionsResult() в каждом Activity, который запрашивает разрешения. Проверьте, были ли предоставлены требуемые разрешения, и используйте код запроса, чтобы определить, какой метод нужно вызывать.

Помните, что для API требуется запрос Activity для запросов времени исполнения. Если у вас есть неинтерактивные компоненты (например, Service), посмотрите Как запросить разрешения от службы в Android Marshmallow за советом о том, как справиться это. В основном, самый простой способ - отобразить уведомление, которое затем приведет к Activity, который ничего не делает, кроме как отображает диалоговое окно разрешений времени выполнения. Здесь, как я решил это в своем приложении.

Ответ 2

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

Ответ 3

Поэтому я ненавижу отвечать на свой вопрос конкретно в отношении разрешения android.permission.WRITE_EXTERNAL_STORAGE, используемого в моем примере, но что это за черт.

Для чтения и/или записи файла на самом деле существует способ полностью избежать необходимости запрашивать и затем проверять разрешение и, таким образом, обходить весь поток, описанный выше. Таким образом, метод saveFile (Url url, String content), который я дал в качестве примера, может продолжать работать синхронно.

Решение, которое, как я полагаю, работает в API 19+, устраняет необходимость в разрешении WRITE_EXTERNAL_STORAGE, если DocumentsProvider действует как "посредник", чтобы в основном спросить пользователя от имени вашего приложения ", пожалуйста, выберите конкретный файл для записи на" (т.е. "сборщик файлов" ), а затем, как только пользователь выбирает файл (или вводит новое имя файла), приложение теперь получает магическое разрешение на это для этого Uri, поскольку пользователь имеет специально предоставил его.

Не требуется официальное разрешение WRITE_EXTERNAL_STORAGE.

Этот способ получения прав заимствования является частью Storage Access Framework, и обсуждение этого было сделано на Big BBQ BBQ by Йен-Лейк. Здесь видео, называемое Забудьте о разрешении на хранение: альтернативы для совместного использования и совместной работы, которая охватывает основные принципы и то, как вы конкретно используете он полностью обходит требование разрешения WRITE_EXTERNAL_STORAGE.

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

Ответ 4

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

update: Более полный пример можно найти здесь.


note: Поскольку API requestPermissions() вызывает startActivityForResult()

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

основная логика создания представления была перенесена с OnCreate() на OnCreate2(), а OnCreate() теперь обрабатывает проверку разрешений. Если нужно вызвать requestPermissions(), то связанный с ним OnRequestPermissionsResult() перезапустит это действие (пересылка копии исходного пакета).


[Activity(Label = "MarshmellowActivated",
    MainLauncher = true,      
    Theme = "@style/Theme.Transparent",
    Icon = "@drawable/icon"        
    ////////////////////////////////////////////////////////////////
    // THIS PREVENTS OnRequestPermissionsResult() from being called
    //NoHistory = true
)]
public class MarshmellowActivated : Activity
{
    private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 112;
    private Bundle _savedInstanceState;

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode)
        {
            case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:
                if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
                {       
                    Intent restartThisActivityIntent = new Intent(this, this.GetType());
                    if (_savedInstanceState != null)
                    {
                        // *ref1: Forward bundle from one intent to another
                        restartThisActivityIntent.PutExtras(_savedInstanceState);
                    }
                    StartActivity(restartThisActivityIntent);
                }
                else
                {                   
                    throw new Exception("SD Card Write Access: Denied.");
                }
                break;
        }
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness
        Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);

        //if(extStoragePerm == Permission.Denied)
        if (extStoragePerm != Permission.Granted)
        {
            _savedInstanceState = savedInstanceState;
            // **calls startActivityForResult()**
            RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, ANDROID_PERMISSION_REQUEST_CODE__SDCARD);
        }
        else
        {
            OnCreate2(savedInstanceState);
        }
    }

    private void OnCreate2(Bundle savedInstanceState)
    {
        //...
    }
}

ref1: Переслать пакет из одного намерения в другое

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

Ответ 5

Вы можете добавить вспомогательный метод блокировки следующим образом:

@TargetApi(23) 
public static void checkForPermissionsMAndAboveBlocking(Activity act) {
    Log.i(Prefs.TAG, "checkForPermissions() called");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Here, thisActivity is the current activity
        if (act.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {


            // No explanation needed, we can request the permission.
            act.requestPermissions(
                    new String[]{
                          Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    0);

            while (true) {
                if (act.checkSelfPermission(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {

                    Log.i(Prefs.TAG, "Got permissions, exiting block loop");
                    break;
                }
                Log.i(Prefs.TAG, "Sleeping, waiting for permissions");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            }

        }
        // permission already granted
        else {
            Log.i(Prefs.TAG, "permission already granted");
        }
    }
    else {
        Log.i(Prefs.TAG, "Below M, permissions not via code");
    }

}