Каким образом мы отличаем никогда не просил у стоп-спрашивать разрешения во время выполнения Андроид м?


когда дело доходит до разрешения M Developer Preview runtime, согласно Google:

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

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

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

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

для разрешения запрашивает при первом запуске приложения, это не большая проблема. Есть много рецептов для определения того, что это, вероятно, первый запуск вашего приложения (например,boolean значение SharedPreferences), и поэтому вы предполагаете, что если это первый запуск вашего приложения, вы находитесь в состоянии never-asked.

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

в этих случаях мы должны отслеживать, просили ли мы разрешения сами или нет? Или есть что-то в Android M API, что мне не хватает, что говорит нам, спрашивали ли мы раньше или нет?

9 60

9 ответов:

согласно текущему примеру: https://github.com/googlesamples/android-RuntimePermissions/blob/master/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java#L195

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
        int[] grantResults) {
    if (requestCode == REQUEST_CAMERA) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            doThing();
            //STORE FALSE IN SHAREDPREFERENCES
        } else {
            //STORE TRUE IN SHAREDPREFERENCES
        }
    }

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

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

Я знаю, что публикую очень поздно, но подробный пример может быть полезен для кого-то.

Я заметил, что если мы проверим флаг shouldShowRequestPermissionRationale () в методе обратного вызова onRequestPermissionsResult (), он показывает только два состояния.

состояние 1: - Return true: - каждый раз, когда пользователь нажимает кнопку запретить разрешения (в том числе в самый первый раз.

состояние 2 :- возвращает false: - если пользователь выбирает s "никогда не спрашивает снова.

здесь это пример с несколькими запросами разрешений: -

приложение требует 2 разрешения при запуске . SEND_SMS и ACCESS_FINE_LOCATION (оба упомянуты в манифесте.XML.)

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

enter image description here

public static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if(checkAndRequestPermissions()) {
        // carry on the normal flow, as the case of  permissions  granted.
    }
}

private  boolean checkAndRequestPermissions() {
    int permissionSendMessage = ContextCompat.checkSelfPermission(this,
            Manifest.permission.SEND_SMS);
    int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
    List<String> listPermissionsNeeded = new ArrayList<>();
    if (locationPermission != PackageManager.PERMISSION_GRANTED) {
        listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
    }
    if (permissionSendMessage != PackageManager.PERMISSION_GRANTED) {
        listPermissionsNeeded.add(Manifest.permission.SEND_SMS);
    }
    if (!listPermissionsNeeded.isEmpty()) {
        ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),REQUEST_ID_MULTIPLE_PERMISSIONS);
        return false;
    }
    return true;
}

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

вы должны проверить значение флага shouldShowRequestPermissionRationale () в методе обратного вызова onRequestPermissionsResult ().

есть только два случая:--

Пример 1: - каждый раз, когда пользователь нажимает кнопку запретить разрешения (в том числе в самый первый раз), он вернет true. Поэтому, когда пользователь отрицает, мы можем показать больше объяснений и продолжать спрашивать снова.

Пример 2: - только если пользователь выбирает "никогда не спрашивает снова" он вернет false. В этом случае мы можем продолжить с ограниченной функциональностью и направлять пользователя для активации разрешений из настроек для дополнительных функций, или мы можем завершить настройку, если разрешения тривиальны для приложения.

CASE-1

Case - 1

случае- 2

Case - 2

@Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        Log.d(TAG, "Permission callback called-------");
        switch (requestCode) {
            case REQUEST_ID_MULTIPLE_PERMISSIONS: {

                Map<String, Integer> perms = new HashMap<>();
                // Initialize the map with both permissions
                perms.put(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
                // Fill with actual results from user
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++)
                        perms.put(permissions[i], grantResults[i]);
                    // Check for both permissions
                    if (perms.get(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED
                            && perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                        Log.d(TAG, "sms & location services permission granted");
                        // process the normal flow
                        //else any one or both the permissions are not granted
                    } else {
                            Log.d(TAG, "Some permissions are not granted ask again ");
                            //permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
//                        // shouldShowRequestPermissionRationale will return true
                            //show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.SEND_SMS) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
                                showDialogOK("SMS and Location Services Permission required for this app",
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                switch (which) {
                                                    case DialogInterface.BUTTON_POSITIVE:
                                                        checkAndRequestPermissions();
                                                        break;
                                                    case DialogInterface.BUTTON_NEGATIVE:
                                                        // proceed with logic by disabling the related features or quit the app.
                                                        break;
                                                }
                                            }
                                        });
                            }
                            //permission is denied (and never ask again is  checked)
                            //shouldShowRequestPermissionRationale will return false
                            else {
                                Toast.makeText(this, "Go to settings and enable permissions", Toast.LENGTH_LONG)
                                        .show();
    //                            //proceed with logic by disabling the related features or quit the app.
                            }
                    }
                }
            }
        }

    }

    private void showDialogOK(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", okListener)
                .create()
                .show();
    }

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

состояние 1 и 3 одинаковы для разработчика приложений: вам нужно разрешение и ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED, то вы просто попросите разрешения через ActivityCompat.requestPermissions(), всякий раз, когда пользователь нажал на функцию, которая требует разрешения, независимо от того, сколько раз вы просили. Пользователь в конечном итоге" предоставит "его или" откажет "в нем с проверкой" никогда не спрашивайте снова". Этот дизайн не препятствует вам всплывать диалоговое окно запроса разрешения несколько раз.

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

еще пара пояснений относительно состояния 3:

  1. Да, мы должны остановить беспокоить пользователя, прекратив показывать объяснение, а не останавливать запрос. Вот почему они предоставили shouldShowRequestPermissionRationale().
  2. это не беспокоит, чтобы сохранить запрос на разрешение. После того, как пользователь выбрал "никогда не спрашивайте снова",ActivityCompat.requestPermissions() больше не будет всплывающего диалогового окна.
  3. лучше отключить соответствующий пользовательский интерфейс каждый раз, когда мы узнаем, что у нас нет разрешения, во время сеанса одного пользователя. Вместо того, чтобы отключить пользовательский интерфейс после shouldShowRequestPermissionRationale() возвращает false.

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

я отличаю Never-Asked от Stop-Asking с помощью SharedPreferences, я приведу вам пример того, как я это использую.

private void requestAccountPermission() {

        SharedPreferences mPreferences = getSharedPreferences("configuration", MODE_PRIVATE);
        boolean firstTimeAccount = mPreferences.getBoolean("firstTimeAccount", true);

        if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.GET_ACCOUNTS)) {
            // 2. Asked before, and the user said "no"
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, REQUEST_CODE_ACCOUNTS);
        }else {
            if(firstTimeAccount) { 
                // 1. first time, never asked 
                SharedPreferences.Editor editor = mPreferences.edit();
                editor.putBoolean("firstTimeAccount", false);
                editor.commit();

                // Account permission has not been granted, request it directly.
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS},REQUEST_CODE_ACCOUNTS);
            }else{
                // 3. If you asked a couple of times before, and the user has said "no, and stop asking"

                // Your code
            }
        }
    }

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

public boolean mPermissionRationaleDialogShown = false;

public void checkPermission() {
    if (ContextCompat.checkSelfPermission(this, "PermissionName")
            != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")) {
            showPermissionRequiredDialog();
        } else {
            askPermission();
        }
    } else {
       // Permission Granted
    }
}

public void askPermission() {
    ActivityCompat.requestPermissions(this,
            new String[]{"PermissionName"}, permissionRequestCode);
}

public void showPermissionRequiredDialog() {
    mPermissionRationaleDialogShown = true;
    // Dialog to show why permission is required
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission Granted
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
                    && !mPermissionRationaleDialogShown) {
                // Permission dialog was shown for first time
            } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
                    && mPermissionRationaleDialogShown){
                // User deny permission without Never ask again checked
            } else if (!ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_READ_EXTERNAL)
                    && mPermissionRationaleDialogShown) {
                // User has checked Never ask again during this permission request
            } else {
                // No permission dialog shown to user has user has previously checked Never ask again. Here we can show dialog to open setting screen to change permission
            }
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

попробовав все ответы здесь и некоторые другие сообщения через интернет. Я узнал, что я должен использовать sharedPreference isLocationPermissionDialogShown (по умолчанию false) и все работает как ожидается.

  1. если первый раз спросил разрешения. В данном случае shouldShowRequestPermissionRationale возвращает false и isLocationPermissionDialogShown и false
  2. второй раз shouldShowRequestPermissionRationale return true и при показе диалога мы устанавливаем isLocationPermissionDialogShown to true. и когда мы проверим условие оба будут true
  3. каждый раз пока никогда не спрашивал ставил галочку shouldShowRequestPermissionRationale return true и isLocationPermissionDialogShown возвращает true
  4. если никогда не спрашивайте снова галочкой shouldShowRequestPermissionRationale return false и isLocationPermissionDialogShown возвращает true. Именно это нам и нужно.

пожалуйста, проверьте рабочий пример.

public class MainActivity extends AppCompatActivity {
    SharedPreferences sharedPreferences;
    String locationPermission;
    String prefLocationPermissionKey = "isLocationPermissionDialogShown";
    private final int PERMISSION_REQUEST_CODE_LOCATION = 1001;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        locationPermission = Manifest.permission.ACCESS_FINE_LOCATION;
        sharedPreferences = getSharedPreferences("configuration", MODE_PRIVATE);

        //check for android version
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //Check for permission
            if (checkSelfPermission(locationPermission) != PackageManager.PERMISSION_GRANTED) {
                //check if clarification dialog should be shown.
                if (shouldShowRequestPermissionRationale(locationPermission)) {
                    showClarificationDialog(locationPermission, PERMISSION_REQUEST_CODE_LOCATION);
                } else  {
                    requestPermissions(new String[] { locationPermission}, PERMISSION_REQUEST_CODE_LOCATION);
                }
            } else {
                Log.d("nets-debug", "permission already grranted");
            }
        }

    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            //for location permission
            if (requestCode == PERMISSION_REQUEST_CODE_LOCATION) {
                boolean isLocationPermissionDialogShown = sharedPreferences.getBoolean(prefLocationPermissionKey, false);

                if (!shouldShowRequestPermissionRationale(locationPermission) && isLocationPermissionDialogShown) {
                    // user selected Never Ask Again. do something
                    Log.d("nets-debug", "never ask again");
                } else {
                    // all other conditions like first time asked, previously denied etc are captured here and can be extended if required.
                    Log.d("nets-debug", "all other cases");
                }
            }

        }

    }

    @TargetApi(Build.VERSION_CODES.M)
    public void showClarificationDialog(final String permission, final int requestCode) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Permission Required");
        builder.setMessage("Please grant Location permission to use all features of this app");
        builder.setPositiveButton("Grant", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putBoolean(prefLocationPermissionKey, true);
                editor.apply();
                requestPermissions(new String[] {permission}, requestCode);
            }
        });
        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getApplicationContext(), "This permission required", Toast.LENGTH_LONG).show();
            }
        });
        builder.create().show();
    }

}

надеюсь, что это поможет.

вы можете ознакомиться здесь - есть блок-схема, которая объясняет процесс довольно хорошо. Он также объясняет, Когда вы должны позвонить shouldShowRequestPermissionRationale() и когда он возвращает true.

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


НАКОНЕЦ-ТО ПРИШЛО МОЕ ВРЕМЯ ОТВЕТИТЬ НА ВОПРОС ОТ COMMONSWARE


подача дела:-

1. когда пользователь нажимает на кнопку "запретить разрешение" в первый раз, я покажу диалог обоснования, чтобы объяснить необходимость разрешения. Затем, если пользователь нажимает на кнопку "Отмена" в диалоговом окне "обоснование", я покажу тост, показывающий сообщение "Пожалуйста, дайте разрешение на получение расположение."

2. после этого, когда пользователь нажимает на запретить разрешение(не спрашивайте снова) в диалоговом окне разрешения, я покажу сообщение "Пожалуйста, дайте разрешение на местоположение из настроек приложения". Обратите внимание, что я добавил слова "из настроек приложения", потому что пользователь установил флажок "не спрашивать снова".

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

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

код:-

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_FINE_LOCATION)){
                AlertDialogHelper.showDialogWithYesNoCallback(mContext, getString(R.string.confirm), getString(R.string.please_give_permission_to_get_location), new onItemClickReturnBoolean() {
                    @Override
                    public void onItemClick(Boolean status) {
                        if(status){
                            ActivityCompat.requestPermissions(SplashScreenActivity.this,permissions,AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE);
                        }
                        else{
                            ShowToast.showShortToast(SplashScreenActivity.this,getString(R.string.please_give_permission_to_get_location));
                            finish();
                        }
                    }
                });
            }
            else{
                ActivityCompat.requestPermissions(this,permissions,AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE);
            }
        }
        else{
            gettingLocationAfterPermissionGranted();
        }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                gettingLocationAfterPermissionGranted();
            }
            else{
                if(ActivityCompat.shouldShowRequestPermissionRationale(SplashScreenActivity.this,Manifest.permission.ACCESS_FINE_LOCATION)){
                    ShowToast.showShortToast(this,getString(R.string.please_give_permission_to_get_location));
                }
                else{
                    ShowToast.showShortToast(this,getString(R.string.please_give_location_permission_from_app_settings));
                }
                finish();
            }
        }
    }

проверьте этот репозиторий: https://github.com/debChowdhury/PermissionHelperEasy


Easy peasy


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

@Retention(RetentionPolicy.SOURCE)
    @IntDef({GRANTED, DENIED, BLOCKED})
    public @interface PermissionStatus {}

    public static final int GRANTED = 0;
    public static final int DENIED = 1;
    public static final int BLOCKED = 2;

    @PermissionStatus 
    public static int getPermissionStatus(Activity activity, String androidPermissionName) {
        if(ContextCompat.checkSelfPermission(activity, androidPermissionName) != PackageManager.PERMISSION_GRANTED) {
            if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName)){
                return BLOCKED;
            }
            return DENIED;
        }
        return GRANTED;
    }

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

Я также использовал этот ответ здесь.