根据这个: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()作为PERMISSION_DENIED当你再次请求权限时,而落在shouldShowRequestPermissionRationale()的错误条件下

来自Android文档:

When the system asks the user to grant a permission, the user has the option of telling the system not to ask for that permission again. In that case, any time an app uses requestPermissions() to ask for that permission again, the system immediately denies the request. The system calls your onRequestPermissionsResult() callback method and passes PERMISSION_DENIED, the same way it would if the user had explicitly rejected your request again. This means that when you call requestPermissions(), you cannot assume that any direct interaction with the user has taken place.

其他回答

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

    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
}

OnRequestPermissionResult-free和shouldshowrequestpermissionrationalfree方法:

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

如果没有权限弹出,250ms后打开设备设置(如果选择了“Never ask again”,就是这种情况)。

我也遇到过同样的问题,但我解决了。为了简化工作,我编写了一个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上的回调。

快乐编码:)

如果你想检测所有的“状态”(第一次被拒绝,刚刚被拒绝,刚刚被“Never Ask Again”拒绝或永久拒绝),你可以做以下事情:

创建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

您可以通过检查权限原理是否显示在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).
        }
    }
}