ChatGPT解决这个技术问题 Extra ChatGPT

Android M - 检查运行时权限 - 如何确定用户是否选中了“不再询问”?

据此:http://developer.android.com/preview/features/runtime-permissions.html#coding 应用程序可以检查运行时权限并在尚未授予权限的情况下请求权限。然后将显示以下对话框:

https://i.stack.imgur.com/JiZCa.png

如果用户拒绝重要权限,则应用程序应显示需要权限的解释以及拒绝的影响。该对话框有两个选项:

重试(再次请求权限)拒绝(应用程序将在没有该权限的情况下工作)。

但是,如果用户选中 Never ask again,则不应显示带有说明的第二个对话框,尤其是在用户之前已经拒绝过一次的情况下。现在的问题是:我的应用程序如何知道用户是否检查了 Never ask again? IMO onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 没有给我这些信息。

第二个问题是:Google 是否有计划在权限对话框中加入一条自定义消息来解释应用为什么需要权限?这样就永远不会有第二个对话,这肯定会带来更好的用户体验。

“谷歌是否有计划在权限对话框中加入一条自定义消息来解释为什么应用程序需要权限?” ——在关于 M 权限系统的 Google I|O 演示中,我似乎记得有人在问答中问过,答案是他们正在考虑这个问题。
我自己没有测试它,但文档说 Activity.shouldShowRequestPermissionRationale(String) :如果应用程序先前已请求此权限并且用户拒绝了该请求,则此方法返回 true。这表明您可能应该向用户解释您需要该权限的原因。如果用户过去拒绝了权限请求并在权限请求系统对话框中选择了不再询问选项,则此方法返回 false。如果设备策略禁止应用程序拥有该权限,该方法也会返回 false。
@Fraid:看起来他们在 Android M 的预览 #2 中添加了这个:developer.android.com/preview/support.html#preview2-notes,这可能就是我想要的。我现在无法测试,但下周会测试。如果它做了我希望它做的事情,您可以将其发布为答案并获得一些声誉。与此同时,这可能对其他人有所帮助:youtube.com/watch?v=f17qe9vZ8RM
危险权限和特殊权限示例:github.com/henrychuangtw/AndroidRuntimePermission
@Alex 对于开发人员来说更难,这是肯定的,但从用户的角度来看,能够授予或拒绝特定权限是有意义的。我看到的主要问题是权限的粒度非常不一致,您最终要求的权限可能与您在应用程序中尝试执行的操作几乎无关(例如,当我想连接时的联系人权限)谷歌驱动器,因为这需要设备帐户列表用于身份验证,并且帐户权限是联系人权限组的一部分)。

E
Emanuel Moecklin

Developer Preview 2 对应用程序请求权限的方式进行了一些更改(另请参阅 http://developer.android.com/preview/support.html#preview2-notes)。

第一个对话框现在看起来像这样:

https://i.stack.imgur.com/T9lCb.png

没有“不再显示”复选框(与开发者预览版 1 不同)。如果用户拒绝该权限并且该权限对应用程序至关重要,则它可以显示另一个对话框来解释应用程序请求该权限的原因,例如:

https://i.stack.imgur.com/299nH.png

如果用户再次拒绝,应用程序要么在绝对需要该权限时关闭,要么继续以有限的功能运行。如果用户重新考虑(并选择重试),则再次请求权限。这次提示看起来像这样:

https://i.stack.imgur.com/RVmdo.png

第二次显示“不再询问”复选框。如果用户再次拒绝并且勾选了复选框,则不会发生任何其他事情。是否勾选复选框可以通过使用Activity.shouldShowRequestPermissionRationale(String) 来确定,例如这样:

if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {...

这就是 Android 文档所说的 (https://developer.android.com/training/permissions/requesting.html):

为了帮助您找到需要额外解释的情况,系统提供了 Activity.shouldShowRequestPermissionRationale(String) 方法。如果应用程序之前已请求此权限并且用户拒绝了该请求,则此方法返回 true。这表明您可能应该向用户解释您需要该权限的原因。如果用户过去拒绝了权限请求并在权限请求系统对话框中选择了不再询问选项,则此方法返回 false。如果设备策略禁止应用程序拥有该权限,该方法也会返回 false。

要知道用户是否以“不再询问”被拒绝,您可以在用户未授予权限时再次检查 onRequestPermissionsResult 中的 shouldShowRequestPermissionRationale 方法。

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == REQUEST_PERMISSION) {
        // for each permission check if the user granted/denied them
        // you may want to group the rationale in a single dialog,
        // this is just an example
        for (int i = 0, len = permissions.length; i < len; i++) {
            String permission = permissions[i];
            if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
            // user rejected the permission
                boolean showRationale = shouldShowRequestPermissionRationale( permission );
                if (! showRationale) {
                    // user also CHECKED "never ask again"
                    // you can either enable some fall back,
                    // disable features of your app
                    // or open another dialog explaining
                    // again the permission and directing to
                    // the app setting
                } else if (Manifest.permission.WRITE_CONTACTS.equals(permission)) {
                    showRationale(permission, R.string.permission_denied_contacts);
                    // user did NOT check "never ask again"
                    // this is a good place to explain the user
                    // why you need the permission and ask if he wants
                    // to accept it (the rationale)
                } else if ( /* possibly check more permissions...*/ ) {
                }
            }
        }
    }
}

您可以使用以下代码打开您的应用设置:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, REQUEST_PERMISSION_SETTING);

无法将用户直接发送到授权页面。


我将 shouldShowRequestPermissionRationale() 方法的返回值验证为 false 以检查用户是否选择了“不再询问”。但是当我请求许可时,我也第一次将它的值设为假。所以我无法区分用户是否选择了“不再询问”复选框。请建议??
根据我的理解 shouldShowRationalePermissionRationale() 方法在三种情况下返回 false: 1. 如果我们在请求许可之前第一次调用此方法。 2. 如果用户选择“不再询问”并拒绝许可。 3. 如果设备策略禁止应用拥有该权限
一切都好......但是我们,开发人员,真的需要知道用户是否说“永远不要再问”。我有一个不错的按钮来访问一项功能。用户第一次点击:应该问理由?不,请求许可。用户否认。用户再次点击按钮:理由?是的!显示理由,用户说好的,然后拒绝,再也不问(好吧,他是个白痴,但用户经常是)。后来用户再次按下按钮,理由是什么?不,请求许可,用户没有任何反应。我真的需要一种方法来告诉用户:嘿,伙计,如果你想要这个功能,现在转到应用程序设置并授予权限。
很棒的@EmanuelMoecklin,现在比 Google 文档更好:D
除非您请求许可,否则不会调用 onRequestPermissionsResult。由于第一次请求权限时没有“不再询问”复选框,shouldShowRequestPermissionRationale 将返回 True(请求权限但不再询问)。因此,基本原理总是在用户第一次拒绝权限时显示,但之后只有在未选中复选框的情况下才会显示。
A
Abhinav Chauhan

您可以在 onRequestPermissionsResult() 中检查 shouldShowRequestPermissionRationale()

https://i.stack.imgur.com/e67lS.jpg

检查是否在 onRequestPermissionsResult() 中授予了权限。如果不是,则检查 shouldShowRequestPermissionRationale()

如果此方法返回 true,则说明为什么需要此特定权限。然后根据用户的选择再次requestPermissions()。如果它返回 false 则显示一条错误消息,指出未授予权限并且应用程序无法继续进行或禁用特定功能。

下面是示例代码。

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case STORAGE_PERMISSION_REQUEST:
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted :)
                downloadFile();
            } else {
                // permission was not granted
                if (getActivity() == null) {
                    return;
                }
                if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    showStoragePermissionRationale();
                } else {
                    Snackbar snackbar = Snackbar.make(getView(), getResources().getString(R.string.message_no_storage_permission_snackbar), Snackbar.LENGTH_LONG);
                    snackbar.setAction(getResources().getString(R.string.settings), new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (getActivity() == null) {
                                return;
                            }
                            Intent intent = new Intent();
                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
                            intent.setData(uri);
                            OrderDetailFragment.this.startActivity(intent);
                        }
                    });
                    snackbar.show();
                }
            }
            break;
    }
}

显然,谷歌地图正是为了获得位置许可而这样做的。


感谢您提供图片和 Youtube 链接。它或多或少符合我自己的答案。必须注意的是,当只有开发人员预览版 1 可用且没有 shouldShowRequestPermissionRationale 方法时,才会提出该问题。
我是 android 新手,我想超越这个 onRequestPermissionsResult() 方法。但我收到错误,它必须实现一个超类型方法。你能告诉我如何使用这个吗
P
Patrick Favre

这是一个检查当前权限状态的好方法:

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

    public static final int GRANTED = 0;
    public static final int DENIED = 1;
    public static final int BLOCKED_OR_NEVER_ASKED = 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_OR_NEVER_ASKED;
            }
            return DENIED;
        }
        return GRANTED;
    }

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

更新:

Android 支持库现在似乎也有一个非常相似的类 android.support.v4.content.PermissionChecker,其中包含一个 checkSelfPermission(),它返回:

public static final int PERMISSION_GRANTED = 0;
public static final int PERMISSION_DENIED = -1;
public static final int PERMISSION_DENIED_APP_OP = -2;

对于第一次启动,我在共享首选项中存储了一个布尔值。
如果尚未请求权限,则始终返回 BLOCKED_OR_NEVER_ASKED
是的,这就是它被称为“BLOCKED_OR_NEVER_ASKED”的原因,另见最后一句话
android.content.pm 已经定义了 PERMISSION_GRANTED = 0PERMISSION_DENIED = -1。也许设置 BLOCKED_OR_NEVER_ASKED = PERMISSION_DENIED - 1 或其他什么?
有关处理警告,请参阅下面的 mVck 答案。
ר
רותם ריכטר

一旦用户标记了“不再询问”,就不能再次显示该问题。但可以向用户解释,他之前拒绝了权限,必须在设置中授予权限。并参考他的设置,使用以下代码:

@Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {

    if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // now, you have permission go ahead
        // TODO: something

    } else {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.READ_CALL_LOG)) {
            // now, user has denied permission (but not permanently!)

        } else {

            // now, user has denied permission permanently!

            Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "You have previously declined this permission.\n" +
                "You must approve this permission in \"Permissions\" in the app settings on your device.", Snackbar.LENGTH_LONG).setAction("Settings", new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + BuildConfig.APPLICATION_ID)));

            }
        });
        View snackbarView = snackbar.getView();
        TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
        textView.setMaxLines(5);  //Or as much as you need
        snackbar.show();

        }

    }
    return;
}

在迁移到 androidX 时,您可以将 android.support.design.R 替换为 com.google.android.material.R
N
Nabin Bhandari

可以通过检查是否在 onRequestPermissionsResult() 回调方法中显示权限 rationale 来确定。如果您发现任何权限设置为不再询问,您可以请求用户从设置中授予权限。

我的完整实现如下所示。它适用于单个多个权限请求。使用以下或 directly use my library.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if(permissions.length == 0){
        return;
    }
    boolean allPermissionsGranted = true;
    if(grantResults.length>0){
        for(int grantResult: grantResults){
            if(grantResult != PackageManager.PERMISSION_GRANTED){
                allPermissionsGranted = false;
                break;
            }
        }
    }
    if(!allPermissionsGranted){
        boolean somePermissionsForeverDenied = false;
        for(String permission: permissions){
            if(ActivityCompat.shouldShowRequestPermissionRationale(this, permission)){
                //denied
                Log.e("denied", permission);
            }else{
                if(ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED){
                    //allowed
                    Log.e("allowed", permission);
                } else{
                    //set to never ask again
                    Log.e("set to never ask again", permission);
                    somePermissionsForeverDenied = true;
                }
            }
        }
        if(somePermissionsForeverDenied){
            final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
            alertDialogBuilder.setTitle("Permissions Required")
                    .setMessage("You have forcefully denied some of the required permissions " +
                            "for this action. Please open settings, go to permissions and allow them.")
                    .setPositiveButton("Settings", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                    Uri.fromParts("package", getPackageName(), null));
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(intent);
                        }
                    })
                    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    })
                    .setCancelable(false)
                    .create()
                    .show();
        }
    } else {
        switch (requestCode) {
            //act according to the request code used while requesting the permission(s).
        }
    }
}

hii @nabin我的要求是当我点击下载按钮(下载pdf文件)时,必须检查写权限是允许还是拒绝,所以如何使用这个代码!你能指导我吗?
你好@RuchaBhatt 看看我的图书馆。 github.com/nabinbhandari/Android-Permissions
C
Community

可能对某人有用:--

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

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

状态 2:-返回 false :- 如果用户选择“不再询问”。

Link of detailed working example


这是检测用户是否选择了不再询问选项的正确方法。
啊,这里的关键是您在 onRequestPermissionsResult 中处理这个,而不是在实际请求权限时。
A
Adrian Mole

如果您想检测所有“状态”(第一次被拒绝、刚刚被拒绝、刚刚被“不再询问”或永久拒绝),您可以执行以下操作:

创建 2 个布尔值:

private boolean beforeClickPermissionRat;
private boolean afterClickPermissionRat;

在请求许可之前设置第一个:

beforeClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);

onRequestPermissionsResult 方法中设置第二个:

afterClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);

使用以下“真值表”在 onRequestPermissionsResult() 中执行您需要的任何操作(在检查您仍然没有权限之后):

// before after
// FALSE  FALSE  =  Was denied permanently, still denied permanently --> App Settings
// FALSE  TRUE   =  First time deny, not denied permanently yet --> Nothing
// TRUE   FALSE  =  Just been permanently denied --> Changing my caption to "Go to app settings to edit permissions"
// TRUE   TRUE   =  Wasn't denied permanently, still not denied permanently --> Nothing

在调用 requestPermissions 之前检查 shouldShowRequestPermissionRationale 是没有意义的,除非您想在请求权限之前显示理由。不过,仅在用户拒绝许可后才显示理由似乎是当今大多数应用程序处理它的方式。
@EmanuelMoecklin,据我所知,这是检查它是否已经被拒绝的唯一方法(通过在我的真值表中解释的前后检查它)或者它是否是第一次拒绝(在我的情况下,我将用户重定向到应用程序设置(如果它被永久拒绝)
// TRUE FALSE 也发生在用户在之前拒绝后允许权限时。
@mVck 这意味着,当 afterClickPermissionRat 为假时,它会被永久拒绝。
此解决方案基于 shouldShowRequestPermissionRationale() 的未记录行为,并且可能(s/may/will)在未来的 Android 版本中被破坏。
m
muthuraj

我有同样的问题,我想通了。为了让生活更简单,我编写了一个 util 类来处理运行时权限。

public class PermissionUtil {
    /*
    * Check if version is marshmallow and above.
    * Used in deciding to ask runtime permission
    * */
    public static boolean shouldAskPermission() {
        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
    }
private static boolean shouldAskPermission(Context context, String permission){
        if (shouldAskPermission()) {
            int permissionResult = ActivityCompat.checkSelfPermission(context, permission);
            if (permissionResult != PackageManager.PERMISSION_GRANTED) {
                return true;
            }
        }
        return false;
    }
public static void checkPermission(Context context, String permission, PermissionAskListener listener){
/*
        * If permission is not granted
        * */
        if (shouldAskPermission(context, permission)){
/*
            * If permission denied previously
            * */
            if (((Activity)context).shouldShowRequestPermissionRationale(permission)) {
                listener.onPermissionPreviouslyDenied();
            } else {
                /*
                * Permission denied or first time requested
                * */
if (PreferencesUtil.isFirstTimeAskingPermission(context, permission)) {
                    PreferencesUtil.firstTimeAskingPermission(context, permission, false);
                    listener.onPermissionAsk();
                } else {
                    /*
                    * Handle the feature without permission or ask user to manually allow permission
                    * */
                    listener.onPermissionDisabled();
                }
            }
        } else {
            listener.onPermissionGranted();
        }
    }
/*
    * Callback on various cases on checking permission
    *
    * 1.  Below M, runtime permission not needed. In that case onPermissionGranted() would be called.
    *     If permission is already granted, onPermissionGranted() would be called.
    *
    * 2.  Above M, if the permission is being asked first time onPermissionAsk() would be called.
    *
    * 3.  Above M, if the permission is previously asked but not granted, onPermissionPreviouslyDenied()
    *     would be called.
    *
    * 4.  Above M, if the permission is disabled by device policy or the user checked "Never ask again"
    *     check box on previous request permission, onPermissionDisabled() would be called.
    * */
    public interface PermissionAskListener {
/*
        * Callback to ask permission
        * */
        void onPermissionAsk();
/*
        * Callback on permission denied
        * */
        void onPermissionPreviouslyDenied();
/*
        * Callback on permission "Never show again" checked and denied
        * */
        void onPermissionDisabled();
/*
        * Callback on permission granted
        * */
        void onPermissionGranted();
    }
}

PreferenceUtil 方法如下。

public static void firstTimeAskingPermission(Context context, String permission, boolean isFirstTime){
SharedPreferences sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE;
 sharedPreference.edit().putBoolean(permission, isFirstTime).apply();
 }
public static boolean isFirstTimeAskingPermission(Context context, String permission){
return context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE).getBoolean(permission, true);
}

现在,您只需要使用带有正确参数的方法 * checkPermission*。

这是一个例子,

PermissionUtil.checkPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    new PermissionUtil.PermissionAskListener() {
                        @Override
                        public void onPermissionAsk() {
                            ActivityCompat.requestPermissions(
                                    thisActivity,
              new String[]{Manifest.permission.READ_CONTACTS},
                            REQUEST_EXTERNAL_STORAGE
                            );
                        }
@Override
                        public void onPermissionPreviouslyDenied() {
                       //show a dialog explaining permission and then request permission
                        }
@Override
                        public void onPermissionDisabled() {
Toast.makeText(context, "Permission Disabled.", Toast.LENGTH_SHORT).show();
                        }
@Override
                        public void onPermissionGranted() {
                            readContacts();
                        }
                    });

我的应用程序如何知道用户是否选中了“不再询问”?

如果用户选中“不再询问”,您将在 onPermissionDisabled 上收到回调。

快乐编码:)


shouldShowRequestPermissionRationale 我在这里遇到错误,你能帮帮我吗?
我找不到这个方法 shouldShowRequestPermissionRationale 可能是我未能获得上下文..但很好我找到了其他替代解决方案..谢谢你的帮助:)
我的错。 shouldShowRequestPermissionRationale 可通过 Activity 获得,而不是上下文。在调用该方法之前,我通过将上下文转换为 Activity 来更新我的答案。一探究竟 :)
这是绕过 shouldShowRequestPermissionRationale 返回的第一个错误值的唯一方法,将发送给用户的请求保存到首选项。我有同样的想法并找到了你的答案。干得好人
如果用户禁用某个权限然后再次尝试访问该功能,这将是一种破坏和破坏
A
Alessio

方法 shouldShowRequestPermissionRationale() 可用于检查用户是否选择了“不再询问”选项并拒绝了权限。有很多代码示例,所以我宁愿解释如何将它用于这样的目的,因为我认为它的名称和它的实现使这比实际上更复杂。

Requesting Permissions at Run Time 中所述,如果“不再询问”选项可见,则该方法返回 true,否则返回 false;因此它在第一次显示对话框时返回 false,然后从第二次开始返回 true,并且只有当用户拒绝选择该选项的权限时,它才会再次返回 false。

要检测这种情况,您可以检测序列 false-true-false,或者(更简单)您可以有一个标志来跟踪显示对话框的初始时间。之后,该方法返回 true 或 false,其中 false 将允许您检测何时选择了该选项。


s
saksham

每个许可案例的完整解释

/**
 *    Case 1: User doesn't have permission
 *    Case 2: User has permission
 *
 *    Case 3: User has never seen the permission Dialog
 *    Case 4: User has denied permission once but he din't clicked on "Never Show again" check box
 *    Case 5: User denied the permission and also clicked on the "Never Show again" check box.
 *    Case 6: User has allowed the permission
 *
 */
public void handlePermission() {
    if (ContextCompat.checkSelfPermission(MainActivity.this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        // This is Case 1. Now we need to check further if permission was shown before or not

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

            // This is Case 4.
        } else {
            // This is Case 3. Request for permission here
        }

    } else {
        // This is Case 2. You have permission now you can do anything related to it
    }
}

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // This is Case 2 (Permission is now granted)
    } else {
        // This is Case 1 again as Permission is not granted by user

        //Now further we check if used denied permanently or not
        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            // case 4 User has denied permission but not permanently

        } else {
            // case 5. Permission denied permanently.
            // You can open Permission setting's page from here now.
        }

    }
}

你能提供案例5的答案吗?如何再次显示相同的权限?
我昨天得到了它,它正在工作。不过感谢您的回复。
S
Soon Santos

您可以使用

shouldShowRequestPermissionRationale()

里面

onRequestPermissionsResult()

请参见下面的示例:

检查用户点击按钮时是否有权限:

@Override
public void onClick(View v) {
    if (v.getId() == R.id.appCompatBtn_changeProfileCoverPhoto) {
        if (Build.VERSION.SDK_INT < 23) { // API < 23 don't need to ask permission
            navigateTo(MainActivity.class); // Navigate to activity to change photos
        } else {
            if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                // Permission is not granted yet. Ask for permission...
                requestWriteExternalPermission();
            } else {
                // Permission is already granted, good to go :)
                navigateTo(MainActivity.class);
            }
        } 
    }
}

当用户回答权限对话框时,我们将转到 onRequestPermissionResult:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    if (requestCode == WRITE_EXTERNAL_PERMISSION_REQUEST_CODE) {
        // Case 1. Permission is granted.  
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
            if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED) {
                // Before navigating, I still check one more time the permission for good practice.
                navigateTo(MainActivity.class);
            }
        } else { // Case 2. Permission was refused
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // Case 2.1. shouldShowRequest... returns true because the
                // permission was denied before. If it is the first time the app is running we will 
                // end up in this part of the code. Because he need to deny at least once to get 
                // to onRequestPermissionsResult. 
                Snackbar snackbar = Snackbar.make(findViewById(R.id.relLayout_container), R.string.you_must_verify_permissions_to_send_media, Snackbar.LENGTH_LONG);
                snackbar.setAction("VERIFY", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        ActivityCompat.requestPermissions(SettingsActivity.this
                                , new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}
                                , WRITE_EXTERNAL_PERMISSION_REQUEST_CODE);
                    }
                });
                snackbar.show();
            } else {
                // Case 2.2. Permission was already denied and the user checked "Never ask again". 
                // Navigate user to settings if he choose to allow this time.
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage(R.string.instructions_to_turn_on_storage_permission)
                        .setPositiveButton(getString(R.string.settings), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                Uri uri = Uri.fromParts("package", getPackageName(), null);
                                settingsIntent.setData(uri);
                                startActivityForResult(settingsIntent, 7);
                            }
                        })
                        .setNegativeButton(getString(R.string.not_now), null);
                Dialog dialog = builder.create();
                dialog.show();
            }
        }
    }

}

J
Jake Lee

确定是否已阻止请求任意权限的有用函数(在 Kotlin 中):

private fun isPermissionBlockedFromAsking(activity: Activity, permission: String): Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        return ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED
            && !activity.shouldShowRequestPermissionRationale(permission)
            && PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false)
    }
    return false
}

使用此功能需要在您首次请求权限时将共享首选项布尔值设置为 true,并将您所需权限的名称(例如 android.Manifest.permission.READ_PHONE_STATE)设置为 true

解释:

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M,因为某些代码只能在 API 级别 23+ 上运行。

ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED 检查我们是否还没有该权限。

!activity.shouldShowRequestPermissionRationale(permission) 检查用户是否拒绝了应用再次询问。由于 quirks of this function,还需要以下行。

PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false) 用于区分“从不询问”和“不再询问”状态(以及在第一次权限请求时将值设置为 true),因为上一行不返回此信息。


A
Antzi

请不要为了这个解决方案向我扔石头。

这可行,但有点“hacky”。

当您调用 requestPermissions 时,注册当前时间。

        mAskedPermissionTime = System.currentTimeMillis();

然后在 onRequestPermissionsResult

如果结果未通过,请再次检查时间。

 if (System.currentTimeMillis() - mAskedPermissionTime < 100)

由于用户不可能这么快点击拒绝按钮,我们知道他选择了“不再询问”,因为回调是即时的。

使用风险自负。


如果我们看到请求的对话 5 分钟然后拒绝怎么办?
那么,如果它不能满足基本要求,这有什么用。如果代码清楚地满足所有情况下的所有要求,则代码可以被接受。
是的,这很糟糕。像这样的自动测试人员可能会设法比这更快地点击:developer.android.com/training/testing/crawler
T
Tanish bansal

我发现了许多冗长而令人困惑的答案,在阅读了一些答案后,我的结论是

if (!ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.READ_EXTERNAL_STORAGE))
                Toast.makeText(this, "permanently denied", Toast.LENGTH_SHORT).show();

shouldShowRequestPermissionRationale 仅在先前被拒绝权限时才会返回 true。那么您不认为它会在工作流程的一开始就显示“永久拒绝”吗?
M
Martin

OnRequestPermissionResult-free 和 shouldShowRequestPermissionRationale-free 方法:

public static void requestDangerousPermission(AppCompatActivity activity, String permission) {
        if (hasPermission(activity, permission)) return;
        requestPermission();

        new Handler().postDelayed(() -> {
            if (activity.getLifecycle().getCurrentState() == Lifecycle.State.RESUMED) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.setData(Uri.parse("package:" + context.getPackageName()));
                context.startActivity(intent);
            }
        }, 250);
    }

如果没有弹出权限,则在 250 毫秒后打开设备设置(如果选择了“不再询问”,则会出现这种情况。


您如何确定用户能够在 250 毫秒内对权限对话框做出反应?
那不一定。如果用户在 250 毫秒内做出反应(这是不可能的),设备设置就会打开。但是如果之前选择了“不再询问”,系统会在 250 毫秒内关闭权限对话框,这就是它起作用的原因。
v
vishnu benny

shouldShowRequestPermissionRationale 根据先前权限请求中的用户偏好返回 true 或 false。

如果用户刚刚拒绝许可(不是永远),shouldShowRequestPermissionRationale 将返回 true。如果永远拒绝许可,则返回 false。诀窍是即使用户允许权限,shouldShowRequestPermissionRationale 也会返回 false

所以我们可以结合这两个条件来选择never ask again or not。

因此,如果用户不允许权限并且 shouldShowRequestPermissionRationale 返回 false,则意味着用户选择不再请求权限。

https://stackoverflow.com/a/58114769/5151336


这并不完全正确。如果用户在系统对话框上执行外部点击或后按,shouldShowRequestPermissionRationale 将返回 false。即使用户没有永久拒绝该权限。
@JacksOnF1re 这是被视为用户允许权限然后执行操作的流程。检查也应根据实施情况进行调整。
c
crysxd

我在 Android M 中编写了权限请求的简写。这段代码还处理了与旧 Android 版本的向后兼容性。

所有丑陋的代码都被提取到一个 Fragment 中,该 Fragment 将自身附加和分离到请求权限的 Activity。您可以使用 PermissionRequestManager,如下所示:

new PermissionRequestManager()
        // We need a AppCompatActivity here, if you are not using support libraries you will have to slightly change 
        // the PermissionReuqestManager class
        .withActivity(this)

        // List all permissions you need
        .withPermissions(android.Manifest.permission.CALL_PHONE, android.Manifest.permission.READ_CALENDAR)

        // This Runnable is called whenever the request was successfull
        .withSuccessHandler(new Runnable() {
            @Override
            public void run() {
                // Do something with your permissions!
                // This is called after the user has granted all 
                // permissions, we are one a older platform where 
                // the user does not need to grant permissions 
                // manually, or all permissions are already granted

            }
        })

        // Optional, called when the user did not grant all permissions
        .withFailureHandler(new Runnable() {
            @Override
            public void run() {
                // This is called if the user has rejected one or all of the requested permissions
                L.e(this.getClass().getSimpleName(), "Unable to request permission");

            }
        })

        // After calling this, the user is prompted to grant the rights
        .request();

看看:https://gist.github.com/crysxd/385b57d74045a8bd67c4110c34ab74aa


M
Muhammad Farhan Habib

相反,当您在 shouldShowRequestPermissionRationale() 的错误条件下再次请求许可时,您将在 onRequestPermissionsResult() 上收到作为 PERMISSION_DENIED 的回调

来自安卓文档:

当系统要求用户授予权限时,用户可以选择告诉系统不要再次请求该权限。在这种情况下,只要应用程序使用 requestPermissions() 再次请求该权限,系统就会立即拒绝该请求。系统调用您的 onRequestPermissionsResult() 回调方法并传递 PERMISSION_DENIED,就像用户再次明确拒绝您的请求时一样。这意味着当您调用 requestPermissions() 时,您不能假设与用户发生了任何直接交互。


A
Axe
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    switch (requestCode) {
        case PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
            if (grantResults.length > 0) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    // Denied
                } else {
                    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        // To what you want
                    } else {
                       // Bob never checked click
                    }
                }
            }
        }
    }
}

V
Vignes

试试这个简单的权限库。它将通过 3 个简单的步骤处理与权限相关的所有操作。它节省了我的时间。您可以在 15 分钟内完成所有与权限相关的工作。

它可以处理拒绝,它可以处理不再询问,它可以调用应用程序设置以获得权限,它可以给出一个 Rational 消息,它可以给出一个拒绝消息,它可以给出一个接受的权限列表,它可以给出一个拒绝列表权限等

https://github.com/ParkSangGwon/TedPermission

第 1 步:添加您的依赖项

dependencies {
     compile 'gun0912.ted:tedpermission:2.1.1'
     //check the above link for latest libraries
}

Step2:询问权限

TedPermission.with(this)
    .setPermissionListener(permissionlistener)
    .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
    .setPermissions(Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION)
    .check();

第三步:处理权限响应

PermissionListener permissionlistener = new PermissionListener() {
    @Override
    public void onPermissionGranted() {
        Toast.makeText(MainActivity.this, "Permission Granted", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onPermissionDenied(ArrayList<String> deniedPermissions) {
        Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
    }
};

伟大的。它节省了我的时间
不错,易于使用
R
Rasoul Miri

你可以听得很漂亮。

听众

interface PermissionListener {
    fun onNeedPermission()
    fun onPermissionPreviouslyDenied(numberDenyPermission: Int)
    fun onPermissionDisabledPermanently(numberDenyPermission: Int)
    fun onPermissionGranted()
}

MainClass 获得许可

class PermissionUtil {

    private val PREFS_FILENAME = "permission"
    private val TAG = "PermissionUtil"

    private fun shouldAskPermission(context: Context, permission: String): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val permissionResult = ActivityCompat.checkSelfPermission(context, permission)
            if (permissionResult != PackageManager.PERMISSION_GRANTED) {
                return true
            }
        }
        return false
    }

    fun checkPermission(context: Context, permission: String, listener: PermissionListener) {

        Log.i(TAG, "CheckPermission for $permission")

        if (shouldAskPermission(context, permission)) {

            // Load history permission
            val sharedPreference = context.getSharedPreferences(PREFS_FILENAME, 0)
            val numberShowPermissionDialog = sharedPreference.getInt(permission, 0)

            if (numberShowPermissionDialog == 0) {

                (context as? Activity)?.let {
                    if (ActivityCompat.shouldShowRequestPermissionRationale(it, permission)) {
                        Log.e(TAG, "User has denied permission but not permanently")
                        listener.onPermissionPreviouslyDenied(numberShowPermissionDialog)
                    } else {
                        Log.e(TAG, "Permission denied permanently.")
                        listener.onPermissionDisabledPermanently(numberShowPermissionDialog)
                    }
                } ?: kotlin.run {
                    listener.onNeedPermission()
                }

            } else {
                // Is FirstTime
                listener.onNeedPermission()
            }


            // Save history permission
            sharedPreference.edit().putInt(permission, numberShowPermissionDialog + 1).apply()


        } else {
            listener.onPermissionGranted()
        }

    }
}

以这种方式使用

      PermissionUtil().checkPermission(this, Manifest.permission.ACCESS_FINE_LOCATION,
                object : PermissionListener {
                    override fun onNeedPermission() {
                        log("---------------------->onNeedPermission")

//                            ActivityCompat.requestPermissions(this@SplashActivity,
//                                    Array(1) { Manifest.permission.ACCESS_FINE_LOCATION },
//                                    118)

                    }

                    override fun onPermissionPreviouslyDenied(numberDenyPermission: Int) {
                        log("---------------------->onPermissionPreviouslyDenied")
                    }

                    override fun onPermissionDisabledPermanently(numberDenyPermission: Int) {
                        log("---------------------->onPermissionDisabled")
                    }

                    override fun onPermissionGranted() {
                        log("---------------------->onPermissionGranted")
                    }

                })

在活动或片段网络中覆盖 onRequestPermissionsResult

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
 if (requestCode == 118) {
        if (permissions[0] == Manifest.permission.ACCESS_FINE_LOCATION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            getLastLocationInMap()
        }
        }
    }

d
devDeejay

为了准确回答这个问题,当用户按下“不再询问”时会发生什么?

被覆盖的方法/函数

onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)

grantResult 数组结果是空的,所以你可以在那里做点什么吗?但不是最佳实践。

如何处理“不再询问”?

我正在使用 Fragment,它需要 READ_EXTERNAL_STORAGE 权限。

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        when {
            isReadPermissionsGranted() -> {

                /**
                 * Permissions has been Granted
                 */

                getDirectories()
            }

            isPermissionDeniedBefore() -> {

                /**
                 * User has denied before, explain why we need the permission and ask again
                 */

                updateUIForDeniedPermissions()
                checkIfPermissionIsGrantedNow()

            }
            else -> {

                /**
                 * Need to ask For Permissions, First Time
                 */

                checkIfPermissionIsGrantedNow()

                /**
                 * If user selects, "Dont Ask Again" it will never ask again! so just update the UI for Denied Permissions
                 */

                updateUIForDeniedPermissions()

            }
        }
    }

其他功能是微不足道的。

// Is Read Write Permissions Granted
fun isReadWritePermissionGranted(context: Context): Boolean {
    return (ContextCompat.checkSelfPermission(
        context as Activity,
        Manifest.permission.READ_EXTERNAL_STORAGE
    ) == PackageManager.PERMISSION_GRANTED) and
            (ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) == PackageManager.PERMISSION_GRANTED)
}

fun isReadPermissionDenied(context: Context) : Boolean {
    return ActivityCompat.shouldShowRequestPermissionRationale(
        context as Activity,
        PermissionsUtils.READ_EXTERNAL_STORAGE_PERMISSIONS)
}

R
Rasool Mohamed

This sample demonstrates how to handle when the user selects "DENY & DON'T ASK AGAIN"

https://i.stack.imgur.com/dsS2B.gif

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    registerStoragePermission()
    registerGalleryLauncher()

    registerCameraPermission()
    registerCameraLauncher()
}

private fun registerCameraPermission() {
    requestCameraPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) {
                Log.d(TAG, "registerCameraPermission - Camera Permission Granted")
                openCamera()
            } else {
                Log.d(TAG, "registerCameraPermission - Camera Permission NOT Granted")
                requestCameraPermission()
            }
        }
}

private fun registerStoragePermission() {
    requestStoragePermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) {
                Log.d(TAG, "registerStoragePermission - Storage Permission Granted")
                viewGallery()
            } else {
                Log.d(TAG, "registerStoragePermission - Storage Permission NOT Granted")
                requestStoragePermission()
            }
        }
}

private fun registerCameraLauncher() {
    cameraLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val data: Intent? = result.data
                if (data == null) {
                    return@registerForActivityResult
                }
                val extras = data.extras
                imageBitmap = extras!!["data"] as Bitmap
                file = FileUtils.createFile(requireContext(),
                    getString(R.string.app_name),
                    "my_profile_image.png"
                )
                //FileUtils.saveBitmap(imageBitmap, file);
                val imageLocalPath = FileUtils.saveImageToInternalStorage(file, imageBitmap)

                SharedPreferencesUtils.setProfilePath(requireActivity(), imageLocalPath)
                profileFragmentBinding.imageViewCircleNoStroke.setImageBitmap(imageBitmap)
                profileFragmentBinding.imageViewCircleNoStroke.setScaleType(ImageView.ScaleType.CENTER_CROP)
            }
        }
}

private fun registerGalleryLauncher() {
    galleryLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val data: Intent? = result.data
                if (data == null) {
                    return@registerForActivityResult
                }
                val uri = data.data
                var imageLocalPath = File(FileUtils.getPathReal(requireActivity(), uri!!))

                file = imageLocalPath.absoluteFile

                SharedPreferencesUtils.setProfilePath(requireActivity(), imageLocalPath.absolutePath)
                Glide.with(requireActivity()).load(uri)
                    .into(profileFragmentBinding.imageViewCircleNoStroke)
                profileFragmentBinding.imageViewCircleNoStroke.setScaleType(ImageView.ScaleType.CENTER_CROP)
            }
        }
}

private fun showImageUploadOptions() {
    val mDialog = activity.let { Dialog(it!!) }
    mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
    mDialog.setContentView(R.layout.dialog_profile_image_option)
    mDialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
    //val mAlertMessageTv = mDialog.findViewById<View>(R.id.id_alert_tv) as TextView
    //mAlertMessageTv.text = message
    galleryLl = mDialog.findViewById<View>(R.id.id_gallery_ll) as LinearLayout
    cameraLl = mDialog.findViewById<View>(R.id.id_camera_ll) as LinearLayout
    removePhotoLl = mDialog.findViewById<View>(R.id.id_remove_photo_ll) as LinearLayout

    galleryLl.setOnClickListener {
        CallStoragePermission()
        mDialog.dismiss()
    }

    cameraLl.setOnClickListener {
        CallCameraPermission()
        mDialog.dismiss()
    }

    removePhotoLl.setOnClickListener {
        CallRemovePhoto()
        mDialog.dismiss()
    }

    mDialog.setCancelable(true)
    mDialog.show()
    val metrics = resources.displayMetrics
    val width = metrics.widthPixels
    val height = metrics.heightPixels
    mDialog.window!!.setLayout(
        width,
        LinearLayout.LayoutParams.WRAP_CONTENT
    )

}

fun CallStoragePermission() {

    if (!Status_checkReadExternalStoragePermission()) {
        requestStoragePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
    } else {
        viewGallery()
    }
}

private fun Status_checkReadExternalStoragePermission(): Boolean {
    val permissionState = ActivityCompat.checkSelfPermission(
        requireActivity(),
        Manifest.permission.READ_EXTERNAL_STORAGE
    )
    return permissionState == PackageManager.PERMISSION_GRANTED
}

private fun requestCameraPermission() {

    when {
        ContextCompat.checkSelfPermission(
            requireContext(),
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED -> {

            Log.d(TAG, "requestCameraPermission - Camera Permission Granted")
            openCamera()

            // The permission is granted
            // you can go with the flow that requires permission here
        }
        shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
            // This case means user previously denied the permission
            // So here we can display an explanation to the user
            // That why exactly we need this permission
            Log.d(TAG, "requestCameraPermission - Camera Permission NOT Granted")
            showPermissionAlert(
                getString(R.string.camera_permission),
                getString(R.string.camera_permission_denied),
                getString(R.string.ok_caps),
                getString(R.string.cancel_caps)
            ) { requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA) }
        }
        else -> {
            // Everything is fine you can simply request the permission

            showPermissionAlert(
                getString(R.string.camera_permission),
                getString(R.string.camera_permission_denied),
                getString(R.string.settings_caps),
                getString(R.string.cancel_caps)
            ) {
                val intent = Intent()
                intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                val uri = Uri.fromParts(
                    "package",
                    BuildConfig.APPLICATION_ID, null
                )
                intent.data = uri
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                startActivity(intent)
            }

        }
    }
}


private fun requestStoragePermission() {

    when {
        ContextCompat.checkSelfPermission(
            requireContext(),
            Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED -> {

            Log.d(TAG, "requestStoragePermission - Storage Permission Granted")
            viewGallery()

            // The permission is granted
            // you can go with the flow that requires permission here
        }
        shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
            // This case means user previously denied the permission
            // So here we can display an explanation to the user
            // That why exactly we need this permission
            Log.d(TAG, "requestStoragePermission - Storage Permission NOT Granted")
            showPermissionAlert(
                getString(R.string.read_storage_permission_required),
                getString(R.string.storage_permission_denied),
                getString(R.string.ok_caps),
                getString(R.string.cancel_caps)
            ) { requestStoragePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) }
        }
        else -> {
            // Everything is fine you can simply request the permission

            showPermissionAlert(
                getString(R.string.read_storage_permission_required),
                getString(R.string.storage_permission_denied),
                getString(R.string.settings_caps),
                getString(R.string.cancel_caps)
            ) {
                val intent = Intent()
                intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                val uri = Uri.fromParts(
                    "package",
                    BuildConfig.APPLICATION_ID, null
                )
                intent.data = uri
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                startActivity(intent)
            }

        }
    }
}

private fun showPermissionAlert(
    title: String,
    message: String,
    ok: String,
    cancel: String,
    function: () -> Unit
) {
    val mDialog = requireActivity().let { Dialog(it) }
    mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
    mDialog.setContentView(R.layout.dialog_permission_alert)
    mDialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))

    val mTitleTv = mDialog.findViewById<View>(R.id.id_title_tv) as AppCompatTextView
    mTitleTv.text = title

    val mMessageTv = mDialog.findViewById<View>(R.id.id_message_tv) as AppCompatTextView
    mMessageTv.text = message

    val mNoBtn = mDialog.findViewById<View>(R.id.no_btn) as AppCompatTextView
    mNoBtn.text = cancel

    val mYesBtn = mDialog.findViewById<View>(R.id.yes_btn) as AppCompatTextView
    mYesBtn.text = ok

    mYesBtn.setOnClickListener {
        function.invoke()
        mDialog.dismiss()
    }

    mNoBtn.setOnClickListener { mDialog.dismiss() }

    mDialog.setCancelable(true)
    mDialog.show()
    val metrics = resources.displayMetrics
    val width = metrics.widthPixels
    val height = metrics.heightPixels
    mDialog.window!!.setLayout(
        width,
        LinearLayout.LayoutParams.WRAP_CONTENT
    )
}

fun viewGallery() {
    val intentDocument = Intent(Intent.ACTION_GET_CONTENT)
    intentDocument.type = "image/*"
    intentDocument.putExtra(
        Constants.REQUEST_CODE,
        Constants.REQUEST_PHOTO_FROM_GALLERY
    )
    galleryLauncher.launch(intentDocument)
}

fun openCamera() {
    val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    takePictureIntent.putExtra(
        Constants.REQUEST_CODE,
        Constants.REQUEST_PERMISSIONS_REQUEST_CODE_CAMERA
    )
    cameraLauncher.launch(takePictureIntent)
}

fun CallCameraPermission() {
    if (!Status_checkCameraPermission()) {
        requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
    } else {
        openCamera()
    }
}

private fun Status_checkCameraPermission(): Boolean {
    val camera = ActivityCompat.checkSelfPermission(
        requireActivity(),
        Manifest.permission.CAMERA
    )

    return camera == PackageManager.PERMISSION_GRANTED
}

J
Juan Mendez

我想出这一点的方式对我来说有点新。如果用户曾经选择过一个决定,我必须保留参考。这样,如果没有授予权限,我可以告诉用户第一次在那里并且应该提示看到权限弹出,或者用户暂时或永久拒绝它。

伪代码:

if( granted ) {
 // you are set
} else if( requiresRationale() ) {
 // in the ui let the user know he has to tap and launch permission
 button.onSetClickListener { requestPermission() }
} else if( sharedPreferences.getBoolean("permission", false) ) {
  // so user has already decided to deny permission, then it is permanent
  launchAppSettings()
} else {
  // user's first encounter, request permission
  requestPermission()
}

demo 以 gif 格式附在自述文件中。 https://github.com/juanmendez/android-sdk-updates/tree/api/android-permissions/single


C
ChristianKoelle

我还想获取用户是否选择“不再询问”的信息。我已经用一个丑陋的旗帜实现了“几乎解决”,但在我告诉你如何之前,我会告诉你我的动机:

我想最初提供权限引用功能。如果用户使用它并且没有权限,他/她将获得上面的第 1 个对话或第 2 个和第 3 个对话。当用户选择“不再询问”时,我想禁用该功能并以不同的方式显示它。 - 我的动作是由微调文本条目触发的,我还想在显示的标签文本中添加“(权限撤销)”。这向用户显示:“有功能,但由于我的权限设置,我无法使用它。”但是,这似乎是不可能的,因为我无法检查是否选择了“不再询问”。

我找到了一个我可以接受的解决方案,方法是始终通过主动权限检查启用我的功能。如果出现否定响应,我会在 onRequestPermissionsResult() 中显示 Toast 消息,但前提是我没有显示我的自定义理由弹出窗口。因此,如果用户选择了“不再询问”,他只会收到一条祝酒消息。如果用户不愿意选择“不再询问”,他只会得到操作系统弹出的自定义理由和权限请求,而不是 toast,因为连续三个通知会太痛苦。


F
Federico Navarrete

我必须为相机实现动态权限。出现 3 种可能的情况:1. 允许,2. 拒绝,3. 不再询问。

 @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    for (String permission : permissions) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
            //denied
            Log.e("denied", permission);
        } else {
            if (ActivityCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED) {
                //allowed
                Log.e("allowed", permission);
            } else {
                //set to never ask again
                Log.e("set to never ask again", permission);
                //do something here.
            }
        }
    }
    if (requestCode != MaterialBarcodeScanner.RC_HANDLE_CAMERA_PERM) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        return;
    }
    if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        mScannerView.setResultHandler(this);
        mScannerView.startCamera(mCameraId);
        mScannerView.setFlash(mFlash);
        mScannerView.setAutoFocus(mAutoFocus);
        return;
    } else {
        //set to never ask again
        Log.e("set to never ask again", permissions[0]);
    }
    DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            dialog.cancel();
        }
    };
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle("Error")
            .setMessage(R.string.no_camera_permission)
            .setPositiveButton(android.R.string.ok, listener)
            .show();


}

private void insertDummyContactWrapper() {
        int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.CAMERA);
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.CAMERA},
                    REQUEST_CODE_ASK_PERMISSIONS);
            return;
        }
        mScannerView.setResultHandler(this);
        mScannerView.startCamera(mCameraId);
        mScannerView.setFlash(mFlash);
        mScannerView.setAutoFocus(mAutoFocus);
    }

private int checkSelfPermission(String camera) {
    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        return REQUEST_CODE_ASK_PERMISSIONS;
    } else {
        return REQUEST_NOT_CODE_ASK_PERMISSIONS;
    }
}

u
user6435056

您可以使用 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) 方法来检测是否选中了 never ask。

更多参考:Check this

要检查多个权限,请使用:

  if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
                            showDialogOK("Service Permissions are 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.
                                                    finish();
                                                    break;
                                            }
                                        }
                                    });
                        }
                        //permission is denied (and never ask again is  checked)
                        //shouldShowRequestPermissionRationale will return false
                        else {
                            explain("You need to give some mandatory permissions to continue. Do you want to go to app settings?");
                            //                            //proceed with logic by disabling the related features or quit the app.
                        }

解释()方法

private void explain(String msg){
        final android.support.v7.app.AlertDialog.Builder dialog = new android.support.v7.app.AlertDialog.Builder(this);
        dialog.setMessage(msg)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        //  permissionsclass.requestPermission(type,code);
                        startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:com.exampledemo.parsaniahardik.marshmallowpermission")));
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        finish();
                    }
                });
        dialog.show();
    }

上面的代码还将显示对话框,该对话框会将用户重定向到应用程序设置屏幕,如果已选中不再询问按钮,他可以从该屏幕授予权限。


s
samus

扩展上面 mVck 的答案,以下逻辑确定是否已针对给定的权限请求检查“不再询问”:

bool bStorage = grantResults[0] == Permission.Granted;
bool bNeverAskForStorage =
    !bStorage && (
        _bStorageRationaleBefore == true  && _bStorageRationaleAfter == false ||
        _bStorageRationaleBefore == false && _bStorageRationaleAfter == false
    );

摘自下文(完整示例参见此 answer

private bool _bStorageRationaleBefore;
private bool _bStorageRationaleAfter;        
private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 2;
//private const int ANDROID_PERMISSION_REQUEST_CODE__CAMERA = 1;
private const int ANDROID_PERMISSION_REQUEST_CODE__NONE = 0;

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

    switch (requestCode)
    {
        case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:               
            _bStorageRationaleAfter = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
            bool bStorage = grantResults[0] == Permission.Granted;
            bool bNeverAskForStorage =
                !bStorage && (
                    _bStorageRationaleBefore == true  && _bStorageRationaleAfter == false ||
                    _bStorageRationaleBefore == false && _bStorageRationaleAfter == false
                );      
            break;                
    }
}

private List<string> GetRequiredPermissions(out int requestCode)
{
    // Android v6 requires explicit permission granting from user at runtime for security reasons            
    requestCode = ANDROID_PERMISSION_REQUEST_CODE__NONE; // 0
    List<string> requiredPermissions = new List<string>();

    _bStorageRationaleBefore = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
    Permission writeExternalStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
    //if(extStoragePerm == Permission.Denied)
    if (writeExternalStoragePerm != Permission.Granted)
    {
        requestCode |= ANDROID_PERMISSION_REQUEST_CODE__SDCARD;
        requiredPermissions.Add(Android.Manifest.Permission.WriteExternalStorage);
    }

    return requiredPermissions;
}

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

        // Android v6 requires explicit permission granting from user at runtime for security reasons
        int requestCode;
        List<string> requiredPermissions = GetRequiredPermissions(out requestCode);
        if (requiredPermissions != null && requiredPermissions.Count > 0)
        {
            if (requestCode >= ANDROID_PERMISSION_REQUEST_CODE__SDCARD)                    
            {
                _savedInstanceState = savedInstanceState;
                RequestPermissions(requiredPermissions.ToArray(), requestCode);
                return;
            }
        }
    }            

    OnCreate2(savedInstanceState);
}

N
Narendra

我有点晚了,我也遇到过类似的问题。解决了这个问题如下

假设您想要位置权限

请求权限启动器

private final ActivityResultLauncher<String> requestPermissionLauncher =
      registerForActivityResult(
          new RequestPermission(),
          isGranted -> {
            if (isGranted) {
              // Permission is granted go ahead
            } else {
              shouldShowRequestPermissionRationale();
            }
          });

权限检查

  private boolean hasPermissions() {
        if (checkSelfPermission(requireActivity(), ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
          // Permission is granted go ahead
        } else {
          requestPermissionLauncher.launch(ACCESS_FINE_LOCATION);
        }
      }

检查是否需要显示权限理性/自定义对话框来教育用户

private void shouldShowRequestPermissionRationale() {
        if (!shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)) {
          // need to show permission rational custom dialog.
        } 
    }