关于写这个的原因
动态权限已经是老生常谈的东西了,也做过很多次了,但是由于出现的频率比较低,大概的流程是清楚了,但是有些 API 就是记不住,现在有空便整理一下,加深印象。
为什么需要动态权限
Android 从 6.0 开始,部分高危权限的申请需要使用动态权限,否则程序会直接报错,这个是在 targetVersion > 23 或者运行设备安卓版本大于 6.0 的情况下可能会发生。
其实动态权限这个东西,早已经就不是什么新鲜事情了。在之前 MIUI 5 吧,就已经有这个东西了,用户在需要使用到这个权限的时候,才会弹出选择框,向用户申请权限。后面 Google 就在 Android 6.0 的版本做了强制要求,高危权限必须要使用 动态权限。
哪些是高危权限
要说哪些是高危权限,其实我们大概想想就知道。
比如,打电话?读取联系人?拍照? 是啊,要是程序不需要授权就可以使用前面的功能那就太恐怖了。悄无声息的打电话,发短信,拍照,那用户就相当于在程序面前赤身裸体,他们都被看光了。在这个时代这种事情是不会允许发生的。然而动态权限这个东西还是在 6.0 才被 Google 加进来的。。。。。。
好了,说正事。其实这些权限都是分组的,只要这组中的一个权限被授予了,那这一组中其他权限也会被授予。好了,我们来看一下到底有哪些权限需要动态申请呢?
-
CALENDAR(日历)
- READ_CALENDAR
- WRITE_CALENDAR
-
CAMERA(相机)
- CAMERA
-
CONTACTS(联系人)
- READ_CONTACTS
- WRITE_CONTACTS
- GET_ACCOUNTS
-
LOCATION(位置)
- ACCESS_FINE_LOCATION
- ACCESS_COARSE_LOCATION
-
MICROPHONE(麦克风)
- RECORD_AUDIO
-
PHONE(手机)
- READ_PHONE_STATE
- CALL_PHONE
- READ_CALL_LOG
- WRITE_CALL_LOG
- ADD_VOICEMAIL
- USE_SIP
- PROCESS_OUTGOING_CALLS
-
SENSORS(传感器)
- BODY_SENSORS
-
SMS(短信)
- SEND_SMS
- RECEIVE_SMS
- READ_SMS
- RECEIVE_WAP_PUSH
- RECEIVE_MMS
-
STORAGE(存储卡)
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
其实一般用的比较多的就是 存储卡呀,电话呀,手机呀,短信呀,位置呀,联系人呀,拍照呀,这些而已。好像除了 麦克风,传感器,日历之外,其他的都挺常用的。。。。。
补充一点,如果在 6.0 以下的手机使用动态权限,会直接返回拒绝,连个弹窗都没有。所以在使用动态权限的时候一定要先做版本判断。
动态申请权限的步骤
- 在 Androidmanifest.xml 文件中注册权限。
- 检查有没有被授予权限。
- 该权限是否被拒绝过,如果被拒绝过,最好提示一下用户再进行申请。
- 申请权限。
- 权限是否被授予。
- 如果没被授予,提示用户,如果被勾上“不再提示”,该如何处理。
检查是否被授予权限
ContextCompat.checkSelfPermission(context, permission);
就是这个逼,我一直记得后面的方法名,但是就是忘记了是哪一个类。 ContextCompat,ContextCompat,ContextCompat 默念三遍。其实这个类是 Suport v4
包里面的,用这个也是为了兼容处理,在高版本的 SDK 中,是有可以直接检查权限的方法,但是为了兼容,我们还是用这个吧。
这个检查权限的方法会返回一个 int 类型的数据,我们可以根据这个返回值判断是否被授予了 权限。
在 PackManager 中有两个常量,就是用来做这个判断的。分别是:
PackManager.PERMISSION_DENIED : 权限被拒绝
PackManager.PERMISSION_GRANTED : 权限被授权
我们只要跟这两个常量作比较就可以了。
申请权限
ActivityCompat.requestPermissions(activity,permissions[],requestCode)
这个也是Support v4
兼容包里面的。调用这个就会发送一个权限申请请求。前提是没有被用户勾选为“不再提示”。我们申请权限的时候可以几个权限一起申请。
权限是否被授予
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
这个方法有点像onActivityResult
。我们申请权限的时候,弹出权限申请窗口,用户操作后,结果会在这个方法中回调。因为申请时候可以是几个权限一起申请,所以回调的时候也是一起回调的。grantResults
中保存着所有的结果,同样的,我们只要跟 PackManager
中的两个权限相关的常量进行比较就可以了。
权限是否被拒绝过
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
同样的,这个类也是 Support v4
包中的类,高版本中也是有可以直接用的,但是我们为了兼容还是使用兼容包。我们调用这个方法的时候,如果返回是 true
,则说明,上次用户拒绝过我们的权限申请。
处理 “不再提示” 的情况
我们还可以使用这个方法来判断用户是不是勾选了“不再提示”的选项。处理过程是这样子的。
我们先判断有没有权限,如果没有这个权限就去申请,如果申请的结果是 失败 的话,我们再调用这个方法,如果此时返回的是 false
那就有蹊跷了,用户不是拒绝了我们的权限申请了吗?怎么返回了 false
? 这个时候就是用户勾选了 “不再提示” , 因为用户直接就不给你机会了,这个时候我们再去申请权限都是没有用了,浪子不会回头的,我们只能提示用户爸爸,让爸爸去设置里面把权限授权给我们了。
代码
下面附上一个写好的代码,及供参考
private String[] requestPermission = new String[]{Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
private final int permissionRequestCode = 10010;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Android 版本的大于 6.0 才使用动态权限
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.M){
final List<String> needRequestPermission = checkPermissions(this, requestPermission);
if (isShouldShowReason(this,needRequestPermission)){
// 之前被拒绝过,现在要提示用户
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("申请权限")
.setMessage("申请这个权限是为了 XXXX ,请授予这个权限")
.setPositiveButton("好的", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(needRequestPermission,MainActivity.this, permissionRequestCode);
}
})
.setNegativeButton("滚",null)
.show();
} else if (needRequestPermission.size()>0){
requestPermissions(needRequestPermission,this, permissionRequestCode);
}
}
}
//检查权限
private List<String> checkPermissions(Context context , String[] requestPermission){
List<String> needRequest = new ArrayList<>();
for (String permission : requestPermission){
int result = ContextCompat.checkSelfPermission(context, permission);
if (result!=PackageManager.PERMISSION_GRANTED){
needRequest.add(permission);
}
}
return needRequest;
}
//是否需要展示申请权限的原因
private boolean isShouldShowReason(Activity activity,List<String> permissions){
boolean shouldShowReason=false;
for (String permission: permissions){
shouldShowReason = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
if (shouldShowReason) {
break;
}
}
return shouldShowReason;
}
// 申请权限
private void requestPermissions(List<String> permissions, Activity activity, int requestCode){
ActivityCompat.requestPermissions(activity,permissions.toArray(new String[]{}),requestCode);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(requestCode== permissionRequestCode){
boolean requsetPermissionResult=true;
boolean needSetting = false;
for (int i=0;i<grantResults.length;i++){
int result = grantResults[i];
if (result!=PackageManager.PERMISSION_GRANTED){
requsetPermissionResult=false;
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[i])){
//用户选择了不再提示。
needSetting=true;
}
}
}
if (requsetPermissionResult){
Toast.makeText(this, "获取权限成功!", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "获取权限失败!", Toast.LENGTH_SHORT).show();
}
if (needSetting){
// 用户勾选了 “不再提示”
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("申请权限")
.setMessage("申请这个权限是为了 XXXX ,请去设置中添加权限")
.setPositiveButton("好的", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setNegativeButton("滚",null)
.show();
}
}
}