根据这个:http://developer.android.com/preview/features/runtime-permissions.html#coding一个应用程序可以检查运行时权限和请求权限,如果它还没有被授予。弹出如下对话框:

如果用户拒绝一个重要的权限,在我看来,应用程序应该显示一个解释为什么需要权限和什么影响拒绝。该对话框有两个选项:

重试(再次请求许可) 拒绝(应用程序将工作没有该许可)。

但是,如果用户选中“Never ask again”,则不应该显示带有解释的第二个对话框,特别是如果用户之前已经拒绝了一次。 现在的问题是:我的应用程序如何知道用户是否选中了Never ask again?IMO onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)没有给我这个信息。

第二个问题是:谷歌是否计划在权限对话框中包含一个自定义消息,以解释为什么应用程序需要权限?这样就不会出现第二个对话框,这肯定会带来更好的用户体验。


当前回答

您可以通过检查权限原理是否显示在onRequestPermissionsResult()回调方法中来确定它。如果您发现任何权限集永远不会再次请求,您可以请求用户从设置中授予权限。

我的完整实现如下所示。它适用于单个或多个权限请求。使用以下或直接使用我的库。

@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).
        }
    }
}

其他回答

扩展上面mVck的答案,下面的逻辑确定是否“Never ask again”已经为给定的权限请求进行了检查:

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

摘自下面(完整的例子见这个答案)

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

我还想知道用户是否选择了“never ask again”。我已经实现了一个“几乎解决方案”与一个丑陋的旗帜,但在我告诉你如何,我将告诉你我的动机:

I would like to offer the permission referring functionality initially. If the user uses it and has no rights, he/she gets the either the 1th dialog from above or both the 2nd and 3rd. When the user has chosen 'Never ask again' I would like to disable the functionality and to display it differently. - My action is triggered by a spinner text entry, I would also like to add '(Permission revoked)' to the label text displayed. This shows to the user: 'There is functionality but I cannot use it, due to my permission settings.' However, this does not seem to be possible, as I cannot check whether or not 'Never ask again' has been chosen.

I came to a solution I can live with by having my functionality always enabled with an active permission check. I am showing a Toast message in onRequestPermissionsResult() in case of a negative response but only if I have not shown my custom rationale popup. So if the user has chosen 'Never ask again' he gets a toast message only. If the user is reluctant to chose 'never ask again' he gets only the custom rationale and the permission request popup by the operation system but not toast, as three notifications in a row would be too much pain.

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

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

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

正如在运行时请求权限中解释的那样,如果选项“never ask again”可见,该方法返回true,否则返回false;因此,它在第一次显示对话框时返回false,然后从第二次开始返回true,只有当用户拒绝选择该选项的权限时,它才会再次返回false。

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

这个示例演示了当用户选择“拒绝&不要再次询问”时如何处理

    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
}