根据这个: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).
        }
    }
}

其他回答

对每一种许可情况的完整解释

/**
 *    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.
        }

    }
}

我在Android m中写了一个权限请求的简写,这段代码还处理了对旧Android版本的向后兼容性。

所有丑陋的代码都被提取到一个片段中,该片段将自己附加到请求权限的活动上。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

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

假设您需要位置权限

请求权限启动器

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

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

    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
}

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

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

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