65

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

  1. 如果您以前从未请求过某个许可,请直接请求

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

  3. 如果您之前问过几次,而用户说“不,并停止询问”(通过运行时权限对话框上的复选框),您应该停止打扰(例如,禁用需要权限的 UI)

但是,我们只有一个方法,shouldShowRequestPermissionRationale()返回 a boolean,并且我们有三种状态。我们需要一种方法来区分从不询问状态和停止询问状态,因为我们falseshouldShowRequestPermissionRationale()两者中得到。

对于第一次运行应用程序时请求的权限,这不是一个大问题。有很多方法可以确定这可能是您的应用程序的第一次运行(例如,booleanvalue in SharedPreferences),因此您假设如果这是您的应用程序的第一次运行,您就处于从未询问过的状态。

但是,运行时权限的部分愿景是您可能不会预先要求所有这些权限。当用户点击需要该权限的东西时,您可能只会在以后请求与边缘功能相关的权限。在这里,应用程序可能已经运行了好几个月,然后我们突然需要请求另一个权限。

在这些情况下,我们是否应该跟踪我们是否自己请求了许可?还是我缺少的 Android M API 中的某些内容可以告诉我们之前是否询问过?

4

10 回答 10

68

我知道我发布得很晚,但是详细的示例可能对某人有所帮助。

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

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

状态 2:- 返回 false :- 如果用户选择 s,则不再询问。

这是一个具有多个权限请求的示例:-

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

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

在此处输入图像描述

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

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

只有两种情况:--

案例1: -任何时候用户点击拒绝权限(包括第一次),它都会返回true。所以当用户拒绝的时候,我们可以给出更多的解释并继续询问。

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

情况1

情况1

案例- 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();
    }
于 2016-02-19T00:49:44.777 回答
15

根据当前示例: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 中存储一个布尔值,其中键作为您的权限代码和值,如上所示,以指示该首选项之前是否已被拒绝。

遗憾的是,您可能无法检查在您的应用程序运行时已被接受但后来被拒绝的首选项。最终规范不可用,但您的应用可能会重新启动或获取模拟值,直到下一次启动。

于 2015-08-10T21:02:36.677 回答
6

不,您不需要跟踪您是否请求了许可,也不需要区分 Never-Asked 和 Stop-Asking。

状态 1 和 3 对于应用程序开发人员来说是相同的:您需要权限ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED,然后您只需通过 请求权限ActivityCompat.requestPermissions(),每当用户点击需要权限的功能时,无论您请求了多少次。用户最终将“授予”它,或“拒绝”它并选中“不再询问”。该设计不会阻止您多次弹出权限请求对话框。

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

关于状态 3 的更多解释:

  1. 是的,我们应该通过停止显示解释来停止打扰用户,而不是停止请求。这就是他们提供shouldShowRequestPermissionRationale().
  2. 保留许可请求并不麻烦。用户选择“不再询问”后,ActivityCompat.requestPermissions()将不再弹出对话框。
  3. 每次我们发现我们没有权限时,最好在单用户会话期间禁用相关的 UI。shouldShowRequestPermissionRationale()而不是在返回 false后禁用 UI 。
于 2015-09-16T10:03:24.673 回答
2

我有一种方法可以解决您的问题,它似乎对我来说效果很好。

我使用 SharedPreferences 区分 Never-Asked 和 Stop-Asking,我会给你一个我如何使用它的例子。

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
            }
        }
    }
于 2015-11-10T16:22:02.830 回答
1

这是跟踪第一次显示权限对话框时的方法,当用户检查时不再询问,当权限在用户检查后直接被拒绝时不再询问,我们需要保留一个标志,以便在获取之前是否已显示权限理由对话框导致 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);
    }
}
于 2016-04-01T10:58:16.770 回答
1

在尝试了这里的所有答案和互联网上的其他一些帖子之后。我开始知道我必须使用 sharedPreference isLocationPermissionDialogShown(默认为 false)并且每件事都按预期工作。

  1. 如果第一次请求许可。在这种情况下shouldShowRequestPermissionRationale返回false并且isLocationPermissionDialogShownfalse
  2. 第二次shouldShowRequestPermissionRationale返回true并显示我们设置isLocationPermissionDialogShown为的对话框true。当我们检查条件时,两者都将是true
  3. 每次,直到不再询问,都勾选了 shouldShowRequestPermissionRationale返回trueisLocationPermissionDialogShown返回true
  4. If Never Ask Again 勾选 shouldShowRequestPermissionRationalereturnfalseisLocationPermissionDialogShown返回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();
    }

}

希望这会有所帮助。

于 2017-05-21T16:31:29.623 回答
1

关于 MLProgrammer-CiM 的回答,我知道如何解决存储在 SharedPrefrences 中的布尔值已经为真后用户撤销权限的场景,

只需创建另一个常量布尔值,例如,如果第一个被调用:(Constant.FIRST_TIME_REQUEST其默认状态将为真)将调用第二个Constant.PERMISSION_ALREADY_GRANTED(默认为假)

当然,如果onRequestPermissionsResult授予权限,您将其值更改为 true。

现在,在你想通过预先解释请求许可的部分,写下这样的内容:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
   SharedPreferences sp = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
   boolean isPermissionGranted = sp.getBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
   if (isPermissionGranted) {
      sp.putBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
      sp.putBoolean(Constant.FIRST_TIME_REQUEST, true);
   }

   if (ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName) || sp.getBoolean(Constant.FIRST_TIME_REQUEST, true) ) {
   showDialogExplanation();
}
}

这样,即使用户将删除权限,布尔值也会再次设置为 false。

祝你好运,我希望它会有所帮助。

什洛

于 2020-07-20T21:14:24.833 回答
0

所以我终于有时间回答来自 COMMONSWARE 的问题了


业务流程:-

1.当用户第一次点击“拒绝权限”时,我会显示理由对话框来解释权限的必要性。然后,如果用户单击基本原理对话框上的“取消”按钮,我将显示一个 toast 显示消息“请授予获取位置的权限”。

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


于 2018-09-11T06:30:44.770 回答
-1

你可以看这里- 有一个流程图很好地解释了这个过程。它还解释了何时应该调用shouldShowRequestPermissionRationale()以及何时返回 true。

基本上根据Android的文档,如果你没有权限,你应该总是请求权限(如果用户说永远不要再询问,Android会在回调中自动返回DENIED),如果用户已经拒绝,你应该显示一条短消息您过去曾有过一次,但未标记“不再询问”选项。

于 2015-08-31T08:44:36.783 回答
-3

不需要为权限状态创建一个并行的持久化状态,你可以使用这个随时返回当前权限状态的方法:

@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;
    }

警告:在用户通过用户提示接受/拒绝权限之前返回 BLOCKED 第一个应用程序启动(在 sdk 23+ 设备上)

我也用这个回答here。

于 2016-01-27T14:58:06.917 回答