根据 Google,谈到 M Developer Preview 运行时权限时:
如果您之前从未请求过某个权限,只需请求它需要权限,然后再继续请求权限(例如,禁用需要权限的 UI)
然而,我们只有一个方法,shouldShowRequestPermissionRationale()
,返回一个 boolean
,我们有三个状态。我们需要一种方法来区分从未询问状态和停止询问状态,因为我们从 shouldShowRequestPermissionRationale()
中得到 false
。
对于第一次运行应用程序时请求的权限,这不是一个大问题。有很多方法可以确定这可能是您的应用程序的第一次运行(例如,SharedPreferences
中的 boolean
值),因此您假设如果这是您的应用程序的第一次运行,那么您永远不会-问状态。
但是,运行时权限的部分愿景是您可能不会预先要求所有这些权限。当用户点击需要该权限的东西时,您可能只会在以后请求与边缘功能相关的权限。在这里,应用程序可能已经运行了好几个月,然后我们突然需要请求另一个权限。
在这些情况下,我们是否应该跟踪我们是否自己请求了许可?还是我缺少的 Android M API 中的某些内容可以告诉我们之前是否询问过?
SharedPreferences
可能与 Android 自己存储的权限信息不同步。 Android 是关于运行时权限的“记录系统”。它显然有信息,否则它永远无法从 shouldShowRequestPermissionRationale()
返回 true
。我只是看看是否有一些我缺少的方法被添加了,仅此而已。
shouldShowRequestPermissionRationale()
并添加一个返回 int
的新方法。
我知道我发布得很晚,但是详细的示例可能对某人有所帮助。
我注意到的是,如果我们将 shouldShowRequestPermissionRationale() 标志检查到 onRequestPermissionsResult() 回调方法中,它只显示两种状态。
状态1:-返回真:--任何时候用户点击拒绝权限(包括第一次。
状态 2:- 返回 false :- 如果用户选择 s,则不再询问。
这是一个具有多个权限请求的示例:-
该应用程序在启动时需要 2 个权限。 SEND_SMS 和 ACCESS_FINE_LOCATION(都在 manifest.xml 中提到)。
应用程序启动后,它会一起请求多个权限。如果两个权限都被授予,则正常流程进行。
https://i.stack.imgur.com/TmOCS.png
public static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(checkAndRequestPermissions()) {
// carry on the normal flow, as the case of permissions granted.
}
}
private boolean checkAndRequestPermissions() {
int permissionSendMessage = ContextCompat.checkSelfPermission(this,
Manifest.permission.SEND_SMS);
int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
List<String> listPermissionsNeeded = new ArrayList<>();
if (locationPermission != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (permissionSendMessage != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.SEND_SMS);
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),REQUEST_ID_MULTIPLE_PERMISSIONS);
return false;
}
return true;
}
如果一个或多个权限没有被授予,activityCompat.requestPermissions() 将请求权限并且控制转到 onRequestPermissionsResult() 回调方法。
您应该检查 onRequestPermissionsResult() 回调方法中 shouldShowRequestPermissionRationale() 标志的值。
只有两种情况:--
案例1:-任何时候用户点击拒绝权限(包括第一次),它都会返回true。所以当用户拒绝的时候,我们可以给出更多的解释并继续询问。
案例 2:- 只有当用户选择“不再询问”时,它才会返回 false。在这种情况下,我们可以继续使用有限的功能,并引导用户从设置中激活权限以获得更多功能,或者如果权限对应用程序来说是微不足道的,我们可以完成设置。
情况1
https://i.stack.imgur.com/nbTo6.png
案例- 2
https://i.stack.imgur.com/rQqYp.png
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
Log.d(TAG, "Permission callback called-------");
switch (requestCode) {
case REQUEST_ID_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<>();
// Initialize the map with both permissions
perms.put(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
// Fill with actual results from user
if (grantResults.length > 0) {
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for both permissions
if (perms.get(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "sms & location services permission granted");
// process the normal flow
//else any one or both the permissions are not granted
} else {
Log.d(TAG, "Some permissions are not granted ask again ");
//permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
// // shouldShowRequestPermissionRationale will return true
//show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.SEND_SMS) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
showDialogOK("SMS and Location Services Permission 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.
break;
}
}
});
}
//permission is denied (and never ask again is checked)
//shouldShowRequestPermissionRationale will return false
else {
Toast.makeText(this, "Go to settings and enable permissions", Toast.LENGTH_LONG)
.show();
// //proceed with logic by disabling the related features or quit the app.
}
}
}
}
}
}
private void showDialogOK(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode == REQUEST_CAMERA) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
doThing();
//STORE FALSE IN SHAREDPREFERENCES
} else {
//STORE TRUE IN SHAREDPREFERENCES
}
}
在 SharedPreferences 中存储一个布尔值,其中键作为您的权限代码和值,如上所示,以指示该首选项之前是否已被拒绝。
遗憾的是,您可能无法检查在您的应用程序运行时已被接受但后来被拒绝的首选项。最终规范不可用,但您的应用有可能会重新启动或获取模拟值,直到下一次启动。
不,您不需要跟踪您是否请求了许可,也不需要区分 Never-Asked 和 Stop-Asking。
状态 1 和 3 对于应用开发者来说是一样的:你需要权限和 ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED
,然后你只需通过 ActivityCompat.requestPermissions()
请求权限,每当用户点击需要权限的功能时,无论你有多少次请求。用户最终将“授予”它,或“拒绝”它并选中“不再询问”。该设计不会阻止您多次弹出权限请求对话框。
但是,该设计确实鼓励您在某些时候解释许可的目的 - 您的状态 2。shouldShowRequestPermissionRationale()
不用于确定您是否应该请求许可,它用于确定您是否应该在请求之前显示解释获得许可。
关于状态 3 的更多解释:
是的,我们应该通过停止显示解释来停止打扰用户,而不是停止请求。这就是他们提供 shouldShowRequestPermissionRationale() 的原因。保留许可请求并不麻烦。用户选择“不再询问”后,ActivityCompat.requestPermissions() 将不再弹出对话框。每次我们发现我们没有权限时,最好在单用户会话期间禁用相关的 UI。而不是在 shouldShowRequestPermissionRationale() 返回 false 之后禁用 UI。
shouldShowRequestPermissionRationale()
似乎已经在 onRequestPermissionsResult()
中返回了预期值,因此如果用户以 don't-ask-again 拒绝,shouldShowRequestPermissionRationale()
确实返回 false
。因此,如果您想要获得相同的响应(例如,显示一个小吃店),无论用户是再次拒绝还是之前这样做,您都不需要状态 1。如果您想要不同的响应(例如,仅当用户在一段时间前(而不是现在)再次拒绝时才显示小吃栏),您仍然需要状态 1。
shouldShowRequestPermissionRationale()
将返回 false
,即使用户以前从未拒绝过该权限。所以第一次你应该强制权限对话框,然后你可以依赖它。考虑到这种行为将来可能会改变。
我有一种方法可以解决您的问题,它似乎对我来说效果很好。
我使用 SharedPreferences 区分 Never-Asked 和 Stop-Asking,我会给你一个我如何使用它的例子。
private void requestAccountPermission() {
SharedPreferences mPreferences = getSharedPreferences("configuration", MODE_PRIVATE);
boolean firstTimeAccount = mPreferences.getBoolean("firstTimeAccount", true);
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.GET_ACCOUNTS)) {
// 2. Asked before, and the user said "no"
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, REQUEST_CODE_ACCOUNTS);
}else {
if(firstTimeAccount) {
// 1. first time, never asked
SharedPreferences.Editor editor = mPreferences.edit();
editor.putBoolean("firstTimeAccount", false);
editor.commit();
// Account permission has not been granted, request it directly.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS},REQUEST_CODE_ACCOUNTS);
}else{
// 3. If you asked a couple of times before, and the user has said "no, and stop asking"
// Your code
}
}
}
这是跟踪第一次显示权限对话框时的方法,当用户检查时不再询问,当权限在用户检查后直接被拒绝时不再询问,我们需要保留一个标志,以便在获取之前是否已显示权限理由对话框导致 onRequestPermissionsResult。需要时调用方法 checkPermission()。
public boolean mPermissionRationaleDialogShown = false;
public void checkPermission() {
if (ContextCompat.checkSelfPermission(this, "PermissionName")
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")) {
showPermissionRequiredDialog();
} else {
askPermission();
}
} else {
// Permission Granted
}
}
public void askPermission() {
ActivityCompat.requestPermissions(this,
new String[]{"PermissionName"}, permissionRequestCode);
}
public void showPermissionRequiredDialog() {
mPermissionRationaleDialogShown = true;
// Dialog to show why permission is required
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
&& !mPermissionRationaleDialogShown) {
// Permission dialog was shown for first time
} else if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
&& mPermissionRationaleDialogShown){
// User deny permission without Never ask again checked
} else if (!ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_READ_EXTERNAL)
&& mPermissionRationaleDialogShown) {
// User has checked Never ask again during this permission request
} else {
// No permission dialog shown to user has user has previously checked Never ask again. Here we can show dialog to open setting screen to change permission
}
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
在尝试了这里的所有答案和互联网上的其他一些帖子之后。我开始知道我必须使用 sharedPreference isLocationPermissionDialogShown
(默认为 false),并且每件事都按预期工作。
如果第一次请求许可。在这种情况下 shouldShowRequestPermissionRationale 返回 false 并且 isLocationPermissionDialogShown 也返回 false 第二次 shouldShowRequestPermissionRationale 返回 true 并且在显示对话框时我们将 isLocationPermissionDialogShown 设置为 true。当我们检查条件时,每次都为真,直到不再询问再次勾选 shouldShowRequestPermissionRationale 返回 true 并且 isLocationPermissionDialogShown 返回 true 如果不再询问再次勾选 shouldShowRequestPermissionRationale 返回 false 并且 isLocationPermissionDialogShown 返回 true。这就是我们需要的。
请检查工作示例。
public class MainActivity extends AppCompatActivity {
SharedPreferences sharedPreferences;
String locationPermission;
String prefLocationPermissionKey = "isLocationPermissionDialogShown";
private final int PERMISSION_REQUEST_CODE_LOCATION = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
locationPermission = Manifest.permission.ACCESS_FINE_LOCATION;
sharedPreferences = getSharedPreferences("configuration", MODE_PRIVATE);
//check for android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//Check for permission
if (checkSelfPermission(locationPermission) != PackageManager.PERMISSION_GRANTED) {
//check if clarification dialog should be shown.
if (shouldShowRequestPermissionRationale(locationPermission)) {
showClarificationDialog(locationPermission, PERMISSION_REQUEST_CODE_LOCATION);
} else {
requestPermissions(new String[] { locationPermission}, PERMISSION_REQUEST_CODE_LOCATION);
}
} else {
Log.d("nets-debug", "permission already grranted");
}
}
}
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
//for location permission
if (requestCode == PERMISSION_REQUEST_CODE_LOCATION) {
boolean isLocationPermissionDialogShown = sharedPreferences.getBoolean(prefLocationPermissionKey, false);
if (!shouldShowRequestPermissionRationale(locationPermission) && isLocationPermissionDialogShown) {
// user selected Never Ask Again. do something
Log.d("nets-debug", "never ask again");
} else {
// all other conditions like first time asked, previously denied etc are captured here and can be extended if required.
Log.d("nets-debug", "all other cases");
}
}
}
}
@TargetApi(Build.VERSION_CODES.M)
public void showClarificationDialog(final String permission, final int requestCode) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Permission Required");
builder.setMessage("Please grant Location permission to use all features of this app");
builder.setPositiveButton("Grant", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(prefLocationPermissionKey, true);
editor.apply();
requestPermissions(new String[] {permission}, requestCode);
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(), "This permission required", Toast.LENGTH_LONG).show();
}
});
builder.create().show();
}
}
希望这会有所帮助。
关于 MLProgrammer-CiM 的回答,我知道如何解决存储在 SharedPrefrences 中的布尔值已经为真后用户撤销权限的场景,
只需创建另一个常量布尔值,如果第一个调用例如:Constant.FIRST_TIME_REQUEST
(其默认状态将为 true),第二个将调用 Constant.PERMISSION_ALREADY_GRANTED
(默认为 false)
如果在 onRequestPermissionsResult
上授予权限,您当然可以将其值更改为 true。
现在,在你想通过预先解释请求许可的部分,写下这样的内容:
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
SharedPreferences sp = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
boolean isPermissionGranted = sp.getBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
if (isPermissionGranted) {
sp.putBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
sp.putBoolean(Constant.FIRST_TIME_REQUEST, true);
}
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName) || sp.getBoolean(Constant.FIRST_TIME_REQUEST, true) ) {
showDialogExplanation();
}
}
这样,即使用户将删除权限,布尔值也会再次设置为 false。
祝你好运,我希望它会有所帮助。
什洛
所以我终于有时间回答来自 COMMONSWARE 的问题了
业务流程:-
1. 当用户第一次点击“拒绝权限”时,我会显示理由对话框来解释权限的必要性。然后,如果用户单击基本原理对话框上的“取消”按钮,我将显示一个 toast 显示消息“请授予获取位置的权限”。
2. 之后,当用户在权限对话框中点击拒绝权限(不要再询问)时,我会显示一条消息“请从应用设置中授予位置权限”。请注意,我添加了“来自应用程序设置”的字样,因为用户已选中“不再询问”框。
3. 所以从现在开始,权限对话框将不再显示。基本原理对话框也不会显示。
所以这里的关键是,如果权限对话框和理由对话框都没有显示,那么这意味着用户已经选中了“不再询问”复选框。
编码:-
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_FINE_LOCATION)){
AlertDialogHelper.showDialogWithYesNoCallback(mContext, getString(R.string.confirm), getString(R.string.please_give_permission_to_get_location), new onItemClickReturnBoolean() {
@Override
public void onItemClick(Boolean status) {
if(status){
ActivityCompat.requestPermissions(SplashScreenActivity.this,permissions,AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE);
}
else{
ShowToast.showShortToast(SplashScreenActivity.this,getString(R.string.please_give_permission_to_get_location));
finish();
}
}
});
}
else{
ActivityCompat.requestPermissions(this,permissions,AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE);
}
}
else{
gettingLocationAfterPermissionGranted();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE){
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
gettingLocationAfterPermissionGranted();
}
else{
if(ActivityCompat.shouldShowRequestPermissionRationale(SplashScreenActivity.this,Manifest.permission.ACCESS_FINE_LOCATION)){
ShowToast.showShortToast(this,getString(R.string.please_give_permission_to_get_location));
}
else{
ShowToast.showShortToast(this,getString(R.string.please_give_location_permission_from_app_settings));
}
finish();
}
}
}
检查此存储库:https://github.com/debChowdhury/PermissionHelperEasy
十分简单
你可以看一下here——有一个流程图很好地解释了这个过程。它还解释了何时应调用 shouldShowRequestPermissionRationale()
以及何时返回 true。
基本上根据Android的文档,如果你没有权限,你应该总是请求权限(如果用户说永远不要再询问,Android会在回调中自动返回DENIED),如果用户已经拒绝,你应该显示一条短消息您过去曾有过一次,但未标记“不再询问”选项。
无需为权限状态创建并行持久状态,您可以使用此方法随时返回当前权限状态:
@Retention(RetentionPolicy.SOURCE)
@IntDef({GRANTED, DENIED, BLOCKED})
public @interface PermissionStatus {}
public static final int GRANTED = 0;
public static final int DENIED = 1;
public static final int BLOCKED = 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;
}
return DENIED;
}
return GRANTED;
}
警告:在用户通过用户提示接受/拒绝权限之前返回 BLOCKED 第一个应用程序启动(在 sdk 23+ 设备上)
I also used this answered here.
getPermissionStatus
将错误地返回 BLOCKED
,这实际上是不正确的。在这个设计中需要第 4 个状态,可能称为 HAVENT_ASKED
,检测它的唯一方法是使用共享首选项或类似的东西。
不定期副业成功案例分享
for (int i = 0; i < permissions.length; i++)
有什么用