项目地址
github:PermissionsDemo
相关系统方法说明
ActivityCompat.checkSelfPermission() :检查用户是否已经授权; ActivityCompat.requestPermissions() :申请权限; onRequestPermissionsResult():Activity/Fragment中的回调,用于判断申请结果; shouldShowRequestPermissionRationale() :判断是否能弹出“允许、禁止且不再询问”提示框。
首先需要了解动态权限申请总共有哪些现象
vivo IQOO3 请求权限为例
调用申请权限方法:ActivityCompat.requestPermissions()
第一次申请弹出:“允许、禁止”,点击“禁止”;点击“禁止且不再询问”,调用方法则不再弹窗;
再次调用申请方法,不再弹窗。Activity/Fragment的onRequestPermissionsResult方法中回调:grantResults[i] == PackageManager.PERMISSION_DENIED
我们需要判断以上三种场景状态
相关方法:ActivityCompat.shouldShowRequestPermissionRationale
此方法仅能在申请权限,用户选择“禁止”之后,判断是否能弹出“允许、禁止且不再询问”提示框; 在1、第一次申请权限;2、点击“禁止且不再询问”。这两种情形都是返回false。 此方法唯一作用是,判断返回true时,是可以弹出“允许、禁止且不再询问”提示框。返回false则多种情形都会出现,无区别判断意义。
其他方法
AppOpsManager相关权限API只能判断是否有权限 MODE无效 反射也是返回值只有0、1,因此此API在此处无意义;
总结
并无完美方法判断三种权限申请场景。
根据能判断的情况分为3种场景类型:
1、允许权限 2、禁止 禁止,但没有选择“以后不再询问”,以后申请权限,会继续弹出提示 3、其他 场景一:选择“禁止并不再询问”; 场景二:用户点击系统申请权限弹出框外部,使对话框消失; 场景三:再此之前已经点击过"禁止并不再询问",调用申请权限则直接回调到此处。
我们来看看处理方案
既无完美,始终要有方案处理。
1、网上常用方案
申请权限,在Activity/Fragment的onRequestPermissionsResult方法中回调,判断grantResults[i] == PackageManager.PERMISSION_GRANTED,则弹出跳往设置的提示框
在此会有两种情形: 1、申请权限对话框弹出时,用户点击“禁止”,弹出跳往设置提示框; 2、用户已经禁止询问时,调用系统申请权限方法,直接弹出跳往设置提示框;
2、简易方案
相关工具方法,Demo示例,github:PermissionsDemo
申请权限,在Activity/Fragment的onRequestPermissionsResult方法中回调,通过定义requestCode来判断哪次申请,再判断。
可以自行根据细分场景处理。
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
//处理允许权限后的操作
retrun;
}
String permission = permissions[i];
boolean shouldShow = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
if (shouldShow) {
//禁止,但没有选择“以后不再询问”,以后申请权限,会继续弹出提示
//处理用户点击禁止后的操作
} else {
//场景一:选择“禁止并不再询问”;场景二:用户点击系统申请权限弹出框外部,使对话框消失;场景三:再此之前已经点击过"禁止并不再询问",调用申请权限则直接回调到此处。
//Toast提示用户前往设置允许权限
}
3、使用比较流行的框架 RxPermission
优点:可以直接拿到回调 缺点:需要在FragmentActivity 、Fragment中使用 注意:使用时传入的FragmentActivity 或Fragment中,onRequestPermissionsResult方法的父类方法不能删除,否则影响rxPermission的回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//父类方法不能删除,否则影响rxPermission的回调
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
使用步骤:
1)引用依赖
project中gradle添加
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
app中gradle添加
dependencies {
/* 需要多加对应版本的rxjava,0.12对应rxjava3 */
// RxJava
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
//rxpermissions
implementation 'com.github.tbruyelle:rxpermissions:0.12'
//如果想要尝试使用 RxView 时
implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
}
如果项目中使用的是rxjava2,则使用
// RxJava
implementation 'io.reactivex.rxjava2:rxjava:2.0.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
//如果想要尝试使用 RxView 时
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
//rxpermissions
implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
2)gradle报错处理
Android Studio错误提示Duplicate class android.support.v4.app.INotificationSideChannel found
需要在gradle.properties中添加下面两行代码 这是因为混合支持库。通过添加这些行选择androidX作为支持库
android.useAndroidX=true
android.enableJetifier=true
3)以下我们梳理下使用的场景,完整封装请在demo(PermissionsDemo)中查看:
A、简单回调处理,只回调允许或者拒绝
permissions.request(permissionstr)方法参数为可变参数,如果是多个权限,则是所有权限都通过才回调true
String[] permissionstr = {
Manifest.permission.RECORD_AUDIO};
RxPermissions permissions = new RxPermissions(activity);
permissions.setLogging(true);
permissions.request(permissionstr).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) {
}
});
B、区分三种场景回调处理
RxPermissions.requestEach 方法参数为可变参数,可传单个或者多个String,或者String[]。 多个权限则通过方法 permission.name.equalsIgnoreCase() 来区分
String[] permissionstr = {
Manifest.permission.RECORD_AUDIO}
RxPermissions permissions = new RxPermissions(activity);
permissions.setLogging(true);
permissions.requestEach(permissionStr)
.subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Exception {
if (permission.name.equalsIgnoreCase(Manifest.permission.RECORD_AUDIO)) {
if (permission.granted) {
//处理允许权限后的操作
return;
}
if (permission.shouldShowRequestPermissionRationale) {
//禁止,但没有选择“以后不再询问”,以后申请权限,会继续弹出提示
//处理用户点击禁止后的操作
return;
}
//场景一:选择“禁止并不再询问”;
//场景二:用户点击系统申请权限弹出框外部,使对话框消失;
//场景三:再此之前已经点击过"禁止并不再询问",调用申请权限则直接回调到此处。
//Toast提示用户前往设置允许权限
}
}
});
C、区分三种场景回调,多个权限申请,合并结果处理。
RxPermissions permissions = new RxPermissions(activity);
permissions.setLogging(true);
permissions.requestEachCombined(permissionStr)
.subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Exception {
if (permission.granted) {
//处理允许权限后的操作
return;
}
if (permission.shouldShowRequestPermissionRationale) {
//禁止,但没有选择“以后不再询问”,以后申请权限,会继续弹出提示
//处理用户点击禁止后的操作
return;
}
//场景一:选择“禁止并不再询问”;
//场景二:用户点击系统申请权限弹出框外部,使对话框消失;
//场景三:再此之前已经点击过"禁止并不再询问",调用申请权限则直接回调到此处。
//Toast提示用户前往设置允许权限
}
});
其他问题
获取权限名称
网上方法PermissionInfo .loadLabel(pm).toString(),无法获得权限名称。 需自己做个定义来获取权限名称,在PermissionsDemo中已经写好工具类com.hero.simplepermissionsdemo.PermissionNameEnum,直接使用即可。
给小白的提示
系统权限分为两类:正常权限和危险权限。
正常权限:不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
危险权限:会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
记得在在AndroidManifest.xml中添加所需权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hero.simplepermissionsdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SimplePermissionsDemo">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
</manifest>