Android在 6.0中摒弃了之前的install time permissions model
取而代之的是runtime permissions model
,也就是动态权限管理。
这种改变让用户更加容易的控制自己的隐私,好处不言而喻。但是对于程序员来说,还是有点小负担的,增加了一些学习和开发的成本。
权限分类
Android 将系统权限分成了四个保护等级:
-
normal
:普通级别 -
dangerous
:危险级别 -
signature
:签名级别 -
signatureOrSystem
:系统签名级别
而对于开发而言,关心的只有 普通权限 和 危险权限 两类
其他两级权限,为高级权限,应用拥有platform级别的认证才能申请。
当应用试图在没有权限的情况下做受限操作,应用将被系统杀掉以警示。
所以权限的控制很重要,一个不留神,程序就会系统干掉,后果很严重~~
普通权限 (normal permission)
普通权限 会在App安装期间被默认赋予。这类权限不需要开发人员进行额外操作,这类权限包括:
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
FLASHLIGHT
GET_PACKAGE_SIZE
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT
危险权限
这些权限是在开发6.0程序时,必须要注意的。
这些权限处理不好,程序可能会直接被系统干掉。
权限如下:
权限组 | 权限 |
---|---|
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 |
我们会发现这些权限被分成了组。每个组里面包含了一些相近的权限。
分组的作用:
这些分组实际上是有一些特殊含义的。
系统在动态赋予权利的时候,是按照组去赋予的。即:
如果允许了某一个权限,那么同组中的其他权限也会被直接赋予
对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是对单个权限的说明。
注意:
不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。
代码的处理
-
权限检查
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "权限拒绝了", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "权限同意了", Toast.LENGTH_SHORT).show();
}
-
权限申请
权限的申请方法。可以一次申请多个方法。
// 类似 startActivityForResult()中的REQUEST_CODE
int REQUEST_CODE = 99;
// 权限列表,将要申请的权限以数组的形式提交。
// 系统会依次进行弹窗提示。
// 注意:如果AndroidManifest.xml中没有进行权限声明,这里配置了也是无效的,不会有弹窗提示。
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this,
permissions,
REQUEST_CODE);
-
权限回调
这个方法是Activity的一个回调方法。每一次调用 requestPermissions()
方法,都会回调一次这个方法。
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE: {
// grantResults是一个数组,和申请的数组一一对应。
// 如果请求被取消,则结果数组为空。
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限同意了,做相应处理
} else {
// 权限被拒绝了
}
return;
}
}
}
-
权限再次申请
当用户拒绝了某个权限时,我们可以再次去申请这个权限。但是这个时候,你应该告诉用户,你为什么要申请这个权限。
系统有一个API可以判断一个权限是否被用户拒绝了。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
}
注意:
这个API有两种情况会返回false:
- 用户申请的权限没有被用户拒绝。
- 用户在系统权限弹窗中,选中了 不再提醒 选项。
-
整合代码
看看官方给的代码示例
// 首先检查权限
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 检查用户是否拒绝了这个权限
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// 给出一个提示,告诉用户为什么需要这个权限
} else {
// 用户没有拒绝,直接申请权限
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
//用户授权的结果会回调到FragmentActivity的onRequestPermissionsResult
}
}else {
//已经拥有授权
//TODO: 正常业务逻辑
}
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) {
readContacts();
} else {
// 权限拒绝了。
}
return;
}
}
}
注意:
这里有一个问题,就是当用户在弹窗中用户选择了 不再提示 。
前面也说了,选中了 不再提示 会导致shouldShowRequestPermissionRationale
返回false。
同时系统的权限弹窗也不会再出现了。就算调用代码
ActivityCompat.requestPermissions()
也不会有弹窗。
这里我们就需要自己做一个判断,如果用户选择了 不再提示 (没有API,自己加标记判断吧),我们可以给个提示,并且跳转到设置界面,让用户手动设置,跳转设置界面代码如下:
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent, PERMISSIONS_REQUEST_READ_CONTACTS);
-
示例代码
判断用户是否选择了 不再提示 代码示例:
SharedPreferences sp; // 用来保存配置
private void setFlag(boolean f) {
SharedPreferences.Editor edit = sp.edit();
edit.putBoolean("flag", f);
}
private boolean getFlag() {
return sp.getBoolean("flag", false);
}
private boolean dontShowAgain() {
return getFlag() && !ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
// 检查权限
private void permission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
// 权限拒绝了
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 用户拒绝过权限了,这里给用户个提示。
} else {
if (dontShowAgain()) {
// 用户选择了 “不再提示”。可以跳转到设置界面。
} else {
// 用户没有拒绝权限,这时正常申请权限。
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this,
permissions,
REQUEST_CODE);
}
setFlag(true);
}
} else {
// 权限同意了
}
}
网上有很多关于权限的封装库。在真正使用的时候可以在网上找一个封装库来使用,会方便很多。