我们如何区分Android M的运行时权限从未停止询问?

根据Google的说法,当涉及到M Developer Preview运行时权限时:

  1. 如果你以前从来没有要求过一定的许可,那就问问吧

  2. 如果您之前询问过,并且用户说“不”,然后用户尝试做一些需要拒绝的权限,您应该提示用户解释为什么您需要权限,然后再继续请求权限

  3. 如果你之前问了几次,并且用户说“不,并停止询问”(通过运行时权限对话框中的checkbox),你应该停止打扰(例如,禁用需要权限的用户界面)

但是,我们只有一个方法, shouldShowRequestPermissionRationale() ,返回一个boolean ,我们有三个状态。 我们需要一种方法来区别从问的状态和停止问的状态,因为我们从shouldShowRequestPermissionRationale()获取false

对于第一次运行应用程序时要求的权限,这不是一个大问题。 有很多的食谱来确定这可能是你的应用程序的第一次运行(例如, SharedPreferences boolean值),所以你假设如果这是你的应用程序的第一次运行,你是在从未问过的状态。

然而,运行时权限的一部分是你可能不会要求所有这些。 权限绑定到附加function,你可能只会问,当用户点击需要权限的东西。 在这里,应用程序可能已经运行了很多次,几个月之后,我们突然之间需要再次申请许可。

在这些情况下,我们是否应该跟踪自己是否要求获得许可? 或者在Android M API中有什么东西可以告诉我们我们之前是否问过?

按照当前的例子: 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中的布尔值作为权限代码和值存储,如上所示,以指示该偏好是否被拒绝。

可悲的是,您可能无法检查已被接受的偏好,并在您的应用运行时被拒绝。 最终的规范是不可用的,但有可能你的应用程序要么重新启动,要么获取模拟值,直到下一次启动。

我知道我发帖很晚,但详细的例子可能对某人有帮助。

我注意到的是,如果我们检查onRequestPermissionsResult()callback方法中的shouldShowRequestPermissionRationale()标志,它只显示两种状态。

状态1:返回true: – 任何时候用户点击拒绝权限(包括第一次。

状态2:返回false: – 如果用户selects“再也不会问。

这是一个多重权限请求的例子:

该应用程序在启动时需要2个权限。 SEND_SMS和ACCESS_FINE_LOCATION(都在manifest.xml中提到)。

一旦应用程序启动,它会一起请求多个权限。 如果这两个权限都被授予,则正常stream程进行。

在这里输入图像说明

 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()callback方法。

您应该在onRequestPermissionsResult()callback方法中检查shouldShowRequestPermissionRationale()标志的值。

只有两种情况:

案例1: – 任何时候用户点击拒绝权限(包括第一次),它将返回true。 所以当用户否认的时候,我们可以展示更多的解释,不断的询问。

情况2: – 只有当用户select“不再询问”时,才会返回false。 在这种情况下,我们可以继续使用有限的function,并指导用户从更多function的设置中激活权限,或者如果权限对于应用程序来说是微不足道的,我们可以完成设置。

情况1

情况1

CASE- 2

案例-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()请求权限,每当用户点击需要权限的function时,不pipe你多less次要求 用户将最终“授予”,或者“拒绝再次询问”选中“拒绝”。 devise不会阻止您多次popup权限请求对话框。

但是,devise确实鼓励你在某些时候解释权限的目的 – 你的状态2. shouldShowRequestPermissionRationale()不用于确定你是否应该请求权限,它是用来确定你是否应该显示解释,在你请求之前许可。

关于状态3的更多解释:

  1. 是的,我们应该停止打扰用户,停止显示说明,而不是停止请求。 这就是为什么他们提供了shouldShowRequestPermissionRationale()
  2. 不需要保留许可请求。 用户select“再也不要问了”之后, ActivityCompat.requestPermissions()将不再popup对话框。
  3. 在单用户会话期间,每次发现我们没有权限时最好closures相关的用户界面。 shouldShowRequestPermissionRationale()返回false之后,而不是禁用UI。

我有一个办法解决你的问题,似乎对我来说工作得很好。

我区分永不停止的问 – 请问使用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返回falseisLocationPermissionDialogShown也是false
  2. 第二次shouldShowRequestPermissionRationale返回true ,当显示对话框时,我们将isLocationPermissionDialogShown设置为true 。 当我们检查条件时,这两个将是true
  3. 每次直到永不再问问题shouldShowRequestPermissionRationale返回trueisLocationPermissionDialogShown返回true
  4. 如果永不再问问题, shouldShowRequestPermissionRationale返回falseisLocationPermissionDialogShown返回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(); } } 

希望这会有所帮助。

你可以看看这里 – 有一个stream程图,解释过程相当不错。 它还解释了何时应该调用shouldShowRequestPermissionRationale()以及何时返回true。

基本上根据Android的文档,你应该总是要求权限,如果你没有它(Android会自动返回拒绝在callback,如果用户说,永远不会再问),你应该显示一条短消息,如果用户已经拒绝你曾经在过去却没有标记过再也不问的select。

无需为权限状态创build并行持久状态,您可以使用此方法随时返回当前权限状态:

 @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+设备上)接受/拒绝权限之前,返回BLOCKED第一个应用程序开始,

我也用这个在这里回答。