前言:
Android 6.0(api23)引入了运行时权限,它允许应用程序在运行的时候请求权限而不像5.1及更低版本一样在安装的时候请求权限。这样一来,需要请求危险权限(涉及到用户隐私…)的应用在安装的时候就不需要请求权限了,而5.1以前如果安装的时候请求的权限没有通过的话应用就安装不了。同时,这种运行时权限可以由用户随时在设置->应用信息中开启或关闭某项权限,即使某个应用刚才还拥有读取联系人的权限,下一秒可能就没了(用户关闭了)。
1. 权限的分类
权限分为:
- 普通权限:没有涉及到用户隐私的权限,在清单文件中声明的这种权限会被系统自动授予。
- 危险权限:涉及到用户隐私的权限,如读取联系人,定位,拨打电话…,这种权限必须经过用户授予才能得到。
Android共有上百种权限,但危险权限不多,共9组24个,剩下的都是普通权限:
危险权限表:
权限组 | 权限 |
---|---|
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 |
注意:不管是哪种权限,不管在哪个系统版本中权限都需要在清单文件中声明。
2. 运行时权限的申请
运行时权限的申请是在程序中通过代码控制的,首先需要检查应用是否具有相应的权限:
int permissionCheck = ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_CONTACTS);
使用support-v4包的ContextCompat的checkSelfPermission方法检查权限可以兼容低版本,这样在低版本上也不会出错,这样就没必要在使用前判断sdk版本了,这个方法的返回值有PackageManager.PERMISSION_GRANTED
和PERMISSION_DENIED
两种,前者权限被授予,后者表示权限被拒绝。
检查完是否具有相应的权限就可以进行相应的逻辑操作了,如果已经具有该权限则直接执行相应的逻辑,否则就需要先申请权限。
private void getReadContactsAuthority() {
// 使用ContextCompat可以兼容低版本
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 如果没有该权限,判断是否需要解释
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS)) {
// 显示解释为什么需要该权限的对话框
showExpanationDialog();
} else {
// 请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
}else{
// 已经具有该权限了
// ...
}
}
/**
* 显示解释权限的对话框
*/
private void showExpanationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("请求权限");
builder.setMessage("应用需要访问联系人权限以便得到手机通讯录");
builder.setNegativeButton("取消", null);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// 请求读取通讯录权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
});
builder.create().show();
}
请求运行时权限可能存在的一种情况是应用第一次请求权限的时候被用户拒绝了,下次再需要使用该权限下的功能的时候可以给用户解释一下为什么需要该权限然后再继续请求权限,这样会比较人性化,这时就需要使用shouldShowRequestPermissionRationale
方法判断是否需要解释权限的使用,如图:
注:
- 如果应用之前请求过此权限但用户拒绝了请求(没有勾选Don’t ask agai),此方法将返回 true;
- 如果用户之前拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,
shouldShowRequestPermissionRationale
方法将返回 false; - 如果设备规范禁止应用具有该权限,此方法也会返回 false。
点击确定后会弹出请求权限对话框,如图:
这个窗口是由requestPermissions
方法弹出来的。
3. 权限申请的结果
调用requestPermissions方法后程序就会向系统申请权限,申请权限的结果会通过onRequestPermissionsResult
方法反馈给我们。
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,"获得了权限", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this,"权限被拒绝", Toast.LENGTH_SHORT).show();
}
return;
}
}
}
MY_PERMISSIONS_REQUEST_READ_CONTACTS
类似于startActivityForResults
的请求码,当grantResults(结果)长度大于0并且grantResults中申请的第一个权限结果是通过时(PERMISSION_GRANTED
)表示我们的应用获得了这项权限,否则被用户拒绝了。