我正在阅读关于Android M中新权限模型的官方文档。它讨论了shouldShowRequestPermissionRationale()
函数,如果应用程序先前已请求此权限且用户拒绝请求,则返回true
。如果用户过去拒绝了权限请求并选择了“不再询问”选项,则此方法将返回false
。
但我们如何区分以下两种情况呢?
案例1 :该应用没有权限,之前未向用户询问过该权限。在这种情况下,shouldShowRequestPermissionRationale()将返回false,因为这是我们第一次询问用户。
案例2 :用户拒绝了该权限并选择了“不要再问”,在这种情况下,shouldInShowRequestPermissionRationale()也会返回false。
我想将用户发送到案例2中的应用程序设置页面。我如何区分这两种情况?
答案 0 :(得分:148)
在M预览1之后,如果对话框第一次显示为 ,则表示没有从不再询问复选框。
如果用户拒绝了权限请求,则会在权限对话框第二次权限请求中显示从不再询问复选框。
所以逻辑应该是这样的:
请求权限:
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
} else {
//Do the stuff that requires permission...
}
检查onRequestPermissionsResult
中是否拒绝或授予了权限。
如果以前拒绝了该权限,则此时将在权限对话框中显示从不再询问复选框。
致电shouldShowRequestPermissionRationale
,查看用户是否已选中从不再询问。仅当用户选择从不再询问或设备政策禁止该应用获得该权限时,shouldShowRequestPermissionRationale
方法才会返回false:
if (grantResults.length > 0){
if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//Do the stuff that requires permission...
}else if (grantResults[0] == PackageManager.PERMISSION_DENIED){
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//Show permission explanation dialog...
}else{
//Never ask again selected, or device policy prohibits the app from having that permission.
//So, disable that feature, or fall back to another situation...
}
}
}
因此,您无需跟踪用户是否选中从不再询问。
答案 1 :(得分:22)
我有同样的问题,我想出来了。为了简化生活,我编写了一个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();
}
});
案例1:应用程序没有权限,用户还没有 之前要求获得许可。在这种情况下, shouldShowRequestPermissionRationale()将返回false,因为这样 这是我们第一次询问用户。
案例2:用户拒绝了许可,并选择了“不要问” 再次",在这种情况下也应该是ShowRequestPermissionRationale() 返回false。
我想将用户发送到案例2中的应用程序设置页面。 我如何区分这两种情况?
对于案例1,您将在 onPermissionAsk 上获得回调,对于案例2,您将获得 onPermissionDisabled 的回调。
快乐编码:)
答案 2 :(得分:8)
更新
我相信下面的CanC' answer是应该遵循的正确选择。唯一可以确定的方法是使用shouldShowPermissionRationale在onRequestPermissionResult回调中验证这一点。
==
我原来的回答:
我找到的唯一方法是自行跟踪这是否是第一次(例如使用共享偏好)。如果不是第一次,请使用UnicodeEncodeError
进行区分。
另见:Android M - check runtime permission - how to determine if the user checked "Never ask again"?
答案 3 :(得分:6)
我理解它的方式,shouldShowRequestPermissionRationale()在底层运行了许多用例,并通知应用程序是否显示对所请求权限的解释。
运行时权限背后的想法是,大多数情况下,用户会对权限请求说“是”。这样用户只需点击一下即可。当然,请求应该在正确的上下文中使用 - 即在"相机"按下按钮。
如果用户拒绝了请求,但在一段时间后,按下"相机"再次按钮,shouldShowRequestPermissionRationale()将返回true,因此应用程序可以显示一些有意义的解释,为什么请求权限,以及为什么应用程序在没有它的情况下无法正常工作。通常,您会在该对话框窗口中显示一个按钮,以便稍后拒绝/决定,以及一个授予权限的按钮。基本原理对话框中的授予权限按钮应该再次启动权限请求。这次用户还将有一个" Never再次显示"复选框。如果他决定选择它并再次拒绝许可,它会通知Android系统用户和应用程序不在同一页面上。该操作会产生两种结果 - shouldShowRequestPermissionRationale()将始终返回false,而requestPermissions()方法将不会显示任何对话框,但会直接返回拒绝onRequestPermissionsResult回调。
但是还有另一种可能使用onRequestPermissionsResult的情况。例如,某些设备可能具有禁用摄像头的设备策略(适用于CIA,DARPA等)。在这些设备上,onRequestPermissionsResult将始终返回false,并且requestPermissions()方法将以静默方式拒绝该请求。
通过与Ben Poiesz(Android框架上的产品经理)一起收听播客,我收集了这些内容。
http://androidbackstage.blogspot.jp/2015/08/episode-33-permission-mission.html
答案 4 :(得分:6)
如果有人愿意,可以发布另一个选项。您可以使用Google自己提供的 EasyPermissions 来表示“简化Android M系统权限”。
然后您不必直接处理shouldShowRequestPermissionRationale
。
答案 5 :(得分:3)
检查此实施。对我来说工作很好。基本上你检查checkPermissions()方法中传递权限列表的权限。您在onRequestPermissionsResult()上检查权限请求的结果。该实现允许您解决用户选择“永不再问”的情况。在此实现中,如果se选择“never again again”,则该对话框可以选择将他带到App Settings Activity。
所有这些代码都在我的片段中。我认为创建一个专门的类来做这件事会更好,比如PermissionManager,但我不确定。
/**
* responsible for checking if permissions are granted. In case permissions are not granted, the user will be requested and the method returns false. In case we have all permissions, the method return true.
* The response of the request for the permissions is going to be handled in the onRequestPermissionsResult() method
* @param permissions list of permissions to be checked if are granted onRequestPermissionsResult().
* @param requestCode request code to identify this request in
* @return true case we already have all permissions. false in case we had to prompt the user for it.
*/
private boolean checkPermissions(List<String> permissions, int requestCode) {
List<String> permissionsNotGranted = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED)
permissionsNotGranted.add(permission);
}
//If there is any permission we don't have (it's going to be in permissionsNotGranted List) , we need to request.
if (!permissionsNotGranted.isEmpty()) {
requestPermissions(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]), requestCode);
return false;
}
return true;
}
/**
* called after permissions are requested to the user. This is called always, either
* has granted or not the permissions.
* @param requestCode int code used to identify the request made. Was passed as parameter in the
* requestPermissions() call.
* @param permissions Array containing the permissions asked to the user.
* @param grantResults Array containing the results of the permissions requested to the user.
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case YOUR_REQUEST_CODE: {
boolean anyPermissionDenied = false;
boolean neverAskAgainSelected = false;
// Check if any permission asked has been denied
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
anyPermissionDenied = true;
//check if user select "never ask again" when denying any permission
if (!shouldShowRequestPermissionRationale(permissions[i])) {
neverAskAgainSelected = true;
}
}
}
if (!anyPermissionDenied) {
// All Permissions asked were granted! Yey!
// DO YOUR STUFF
} else {
// the user has just denied one or all of the permissions
// use this message to explain why he needs to grant these permissions in order to proceed
String message = "";
DialogInterface.OnClickListener listener = null;
if (neverAskAgainSelected) {
//This message is displayed after the user has checked never ask again checkbox.
message = getString(R.string.permission_denied_never_ask_again_dialog_message);
listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//this will be executed if User clicks OK button. This is gonna take the user to the App Settings
startAppSettingsConfigActivity();
}
};
} else {
//This message is displayed while the user hasn't checked never ask again checkbox.
message = getString(R.string.permission_denied_dialog_message);
}
new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme)
.setMessage(message)
.setPositiveButton(getString(R.string.label_Ok), listener)
.setNegativeButton(getString(R.string.label_cancel), null)
.create()
.show();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* start the App Settings Activity so that the user can change
* settings related to the application such as permissions.
*/
private void startAppSettingsConfigActivity() {
final Intent i = new Intent();
i.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
i.addCategory(Intent.CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + getActivity().getPackageName()));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
getActivity().startActivity(i);
}
答案 6 :(得分:2)
可能对某人有用: -
我注意到,如果我们在onRequestPermissionsResult()回调方法中检查shouldShowRequestPermissionRationale()标志,它只显示两种状态。
状态1: - 返回true: - 任何时候用户点击拒绝权限(包括第一次)。
状态2:-Returns false: - 如果用户选择“never again again”。
答案 7 :(得分:1)
我们可以这样做吗?
@Retention(RetentionPolicy.SOURCE)
@IntDef({GRANTED, DENIED, NEVER})
public @interface PermissionStatus {
}
public static final int GRANTED = 0;
public static final int DENIED = 1;
public static final int NEVER = 2;
@PermissionStatus
public static int getPermissionStatus(Activity activity, String permission) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
return DENIED;
} else {
if (ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
return GRANTED;
} else {
return NEVER;
}
}
}
答案 8 :(得分:1)
如果有人对Kotlin解决方案感兴趣,我将@muthuraj答案重构为Kotlin。还对它进行了现代化,使其具有完成块而不是侦听器。
PermissionUtil
CoreModule
PermissionHandler
AppModule
实施
object PermissionUtil {
private val PREFS_FILE_NAME = "preference"
fun firstTimeAskingPermission(context: Context, permission: String, isFirstTime: Boolean) {
val sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE)
sharedPreference.preferences.edit().putBoolean(permission,
isFirstTime).apply()
}
fun isFirstTimeAskingPermission(context: Context, permission: String): Boolean {
val sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE)
return sharedPreference.preferences.getBoolean(permission,
true)
}
}
答案 9 :(得分:0)
shouldShowRequestPermissionRationale
特殊权限始终返回 没有复选框
我们对 FALSE 值
感兴趣因 false 值导致 3 个案丢失:
1。之前没有此类操作,现在用户决定同意或拒绝。
只需定义一个现在不存在的偏好ASKED_PERMISSION_*
,并且onRequestPermissionsResult
中的 true 就任何情况下的同意或者shouldShowRequestPermissionRationale
开始拒绝
因此,虽然此偏好不存在,但无理由检查checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
2。用户点击同意。
简单地说:
shouldShowRequestPermissionRationale
将返回 true 并且无理由来检查shouldShowRequestPermissionRationale
3。用户点击拒绝复选框(第二次或更多次询问)
时间与Enable 32-bit applications
合作将返回 FALSE
(偏好存在,我们没有权限)
答案 10 :(得分:0)
此代码要求用户在运行时询问权限, 如果用户允许,则执行结果方法, 如果用户拒绝,则再次询问用户拒绝的描述(它再次询问说明), 但如果用户选择从不再问。 它处理永不再问,显示打开设置选项与说明。
public String storagePermissions = Manifest.permission.READ_EXTERNAL_STORAGE;
private static final int REQUEST_ACCESS =101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if(checkSelfPermission(storagePermissions)== PackageManager.PERMISSION_GRANTED){
result(); // result is your block of code
}else {
requestPermissions(new String[]{storagePermissions},REQUEST_ACCESS);
}
}
else{
result(); //so if user is lower than api verison M, no permission is requested
}
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setTitle("Hi User..")
.setPositiveButton("Ok", okListener)
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { //idea calling showMessage funtion again
Snackbar mySnackbar = Snackbar.make( findViewById(R.id.coordinatorlayout),"You Press Cancel.. ", Snackbar.LENGTH_INDEFINITE);
mySnackbar.setAction("Exit", new cancelButton());
mySnackbar.show();
}
})
.create()
.show();
}
private void result(){
//your code
}
@RequiresApi(api = Build.VERSION_CODES.M)
public class NeverAskAgain implements View.OnClickListener{
@Override
public void onClick(View view)
{
goToSettings();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void goToSettings() {
Intent myAppSettings = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
finish();
myAppSettings.addCategory(Intent.CATEGORY_DEFAULT);
myAppSettings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(myAppSettings, REQUEST_APP_SETTINGS);
}
public class cancelButton implements View.OnClickListener{
@Override
public void onClick(View view){
Toast.makeText(MainActivity.this,"To use this app , you must grant storage permission",Toast.LENGTH_SHORT);
finish();
}
}
@Override
@RequiresApi(api = Build.VERSION_CODES.M)
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode,permissions,grantResults);
switch(requestCode) {
case REQUEST_ACCESS:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission is granted
result();
break;
}
else if (!shouldShowRequestPermissionRationale(permissions[0])){
showMessageOKCancel("You choose Never Ask Again,option",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Snackbar mySnackbar = Snackbar.make(findViewById(R.id.coordinatorlayout), "Permission=>Storage=>On", Snackbar.LENGTH_INDEFINITE);
mySnackbar.setAction("Settings", new NeverAskAgain());
mySnackbar.show();
}
});
break;
}
else {
showMessageOKCancel("You Denid permission Request..",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(new String[]{storagePermissions}, REQUEST_ACCESS);
}
});
break;
}
}
}
答案 11 :(得分:0)
public void requestPermission(View view){
if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
if(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.ACCESS_FINE_LOCATION)){
//The Alert Dialog before asking for the second time to help the user understand why he needs to give permission.
AlertDialog alert = new AlertDialog.Builder(this).setMessage("Without the permissions you cannot run the application")
.setCancelable(false)
.setPositiveButton("Okay, I understand", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
}
}).setNegativeButton("No, Exit the App", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.exit(2);
}
}).create();
alert.setTitle("ALERTY");
alert.show();
//End of the alert Dialog
}
else{
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
}
}
else {
textView.setText("Permission Is Already Granted");
}
}
/*
The shouldShowRequestPermissionRationale() function returns true if the app has requested this permission
previously and the user denied the request.If the user turned down the permission request in the past and chose
the Don't ask again option, this method returns false.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(requestCode == REQUEST_FINE_LOCATION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
textView.setText("Hooray! on Request Permissions Grantred");
}
else{
//Since the user has choosen the don't ask again option,
if(!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)){
//Alert Dialog that will take user to settings where he can manually give the permissions
AlertDialog alert = new AlertDialog.Builder(this).setMessage("You have permanently disabled the permission ")
.setPositiveButton("Go to Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
openSettings();
}
}).setNegativeButton("Don't Go",null).setCancelable(false).create();
alert.setTitle("Give permission manually");
alert.show();
// End of the Alert Dialog
}
else{
textView.setText("Permission has been denied the 1st time");
}
}
}
}
这是openSettings方法。
public void openSettings(){
Intent intent = new Intent();
Uri uri = Uri.fromParts("package",this.getPackageName(),null);
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
startActivity(intent);
}