Android M 行为变更(6.0)
运行时权限:
此版本引入了一种新的权限模式,如今,用户可直接在运行时管理应用权限。这种模式让用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。用户可为所安装的各个应用分别授予或撤销权限。
对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。
Android O 行为变更(8.0)
权限
在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对 Android O 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android O,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。
关于运行时权限
在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装(某些深度定制系统另说),App一旦安装后就可以偷偷的做一些不为人知的事情了。
在Android6.0开始,App可以直接安装,App在运行时一个一个询问用户授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App(这个Dialog不能由开发者定制),当App需要用户授予不恰当的权限的时候,用户可以拒绝,用户也可以在设置页面对每个App的权限进行管理。
特别注意:这个对话框不是开发者调用某个权限的功能时由系统自动弹出,而是需要开发者手动调用,如果你直接调用而没有去申请权限的话,将会导致App奔溃。
也许你已经开始慌了,这对于用户来说是好事,但是对于开发者来说我们不能直接调用方法了,我们不得不在每一个需要权限的地方检查并请求用户授权,所以就引出了以下两个问题。
哪些权限需要动态申请
新的权限策略讲权限分为两类,第一类是不涉及用户隐私的,只需要在Manifest中声明即可,比如网络、蓝牙、NFC等;第二类是涉及到用户隐私信息的,需要用户授权后才可使用,比如SD卡读写、联系人、短信读写等。
Normal Permissions
此类权限都是正常保护的权限,只需要在AndroidManifest.xml中简单声明这些权限即可,安装即授权,不需要每次使用时都检查权限,而且用户不能取消以上授权,除非用户卸载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
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
Dangerous Permissions
所有危险的Android系统权限属于权限组,如果APP运行在Android 6.0 (API level 23)或者更高级别的设备中,而且targetSdkVersion>=23时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有做动态权限的申请将会导致App崩溃,因此你需要注意:
此类权限也必须在Manifest中申明,否则申请时不提使用用户,直接回调开发者权限被拒绝。同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,App也有READ_CONTACTS和GET_ACCOUNTS了。申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请READ_EXTERNAL_STORAGE,系统会提示"允许xxx访问设备上的照片、媒体内容和文件吗?"。如果App运行在Android 5.1 (API level 22)或者更迭级别的设备中,或者targetSdkVersion<=22时(此时设备可以是Android 6.0 (API level 23)或者更高),在所有系统中仍将采用旧的权限管理策略,系统会要求用户在安装的时候授予权限。其次,系统就告诉用户App需要什么权限组,而不是个别的某个权限。
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
使用adb命令可以查看这些需要授权的权限组:
adb shell pm list permissions -d -g
使用adb命令同样可以授权/撤销某个权限:
adb shell pm [grant|revoke] ...
关于运行时权限的一些建议
只请求你需要的权限,减少请求的次数,或用Intent来代替,让其他的应用来处理。
如果你使用Intent,你不需要设计界面,由第三方的应用来完成所有操作。比如打电话、选择图片等。如果你请求权限,你可以完全控制用户体验,自己定义UI。但是用户也可以拒绝权限,就意味着你的应用不能执行这个特殊操作。防止一次请求太多的权限或请求次数太多,用户可能对你的应用感到厌烦,在应用启动的时候,最好先请求应用必须的一些权限,非必须权限在使用的时候才请求,建议整理并按照上述分类管理自己的权限:
普通权限(Normal PNermissions):只需要在Androidmanifest.xml中声明相应的权限,安装即许可。
需要运行时申请的权限(Dangerous Permissions):
必要权限:最好在应用启动的时候,进行请求许可的一些权限(主要是应用中主要功能需要的权限)。
附带权限:不是应用主要功能需要的权限(如:选择图片时,需要读取SD卡权限)。
解释你的应用为什么需要这些权限:在你调用requestPermissions()之前,你为什么需要这个权限。
例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在requestpermissions()之前告诉用户你为什么需要这个权限。
使用兼容库support-v4中的方法
ContextCompat.checkSelfPermission()
ActivityCompat.requestPermissions()
ActivityCompat.shouldShowRequestPermissionRationale()
几个重要的方法与常量解释
PackageManager中的两个常量:
PackageManager.PERMISSION_DENIED:该权限是被拒绝的。
PackageManager.PERMISSION_GRANTED:该权限是被授权的。
Activity中或者Fragment都会有以下几个方法:
int checkSelfPermission(String)
void requestPermissions(int, String...)
boolean shouldShowRequestPermissionRationale(String)
void onRequestPermissionsResult()
上述四个方法中,前三个方法在support-v4的ActivityCompat中都有,建议使用兼容库中的方法。最后一个方法是用户授权或者拒绝某个权限组时系统会回调Activity或者Fragment中的方法。
checkSelfPermission() 检查权限
检查某一个权限的当前状态,你应该在请求某个权限时检查这个权限是否已经被用户授权,已经授权的权限重复申请可能会让用户产生厌烦。
该方法有一个参数是权限名称,有一个int的返回值,用这个值与上面提到的两个常量做比较可判断检查的权限当前的状态。
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 没有权限,申请权限。
}else{
// 有权限了,去放肆吧。
}
requestPermissions() 申请权限
请求用户授权几个权限,调用后系统会显示一个请求用户授权的提示对话框,App不能配置和修改这个对话框,如果需要提示用户这个权限相关的信息或说明,需要在调用 requestPermissions() 之前处理,该方法有两个参数:
int requestCode,会在回调onRequestPermissionsResult()时返回,用来判断是哪个授权申请的回调。
String[] permissions,权限数组,你需要申请的的权限的数组。
由于该方法是异步的,所以无返回值,当用户处理完授权操作时,会回调Activity或者Fragment的onRequestPermissionsResult()方法处理权限结果回调
该方法在Activity/Fragment中应该被重写,当用户处理完授权操作时,系统会自动回调该方法,该方法有三个参数:
*int requestCode,在调用requestPermissions()时的第一个参数。
*String[] permissions,权限数组,在调用requestPermissions()时的第二个参数。
*int[] grantResults,授权结果数组,对应permissions,具体值和上方提到的PackageManager中的两个常量做比较。
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MMM: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被用户同意,可以去放肆了。
} else {
// 权限被用户拒绝了,洗洗睡吧。
}
return;
}
}
}
shouldShowRequestPermissionRationale()
望文生义,是否应该显示请求权限的说明。
第一次请求权限时,用户拒绝了,调用shouldShowRequestPermissionRationale()后返回true,应该显示一些为什么需要这个权限的说明。用户在第一次拒绝某个权限后,下次再次申请时,授权的dialog中将会出现“不再提醒”选项,一旦选中勾选了,那么下次申请将不会提示用户。
第二次请求权限时,用户拒绝了,并选择了“不在提醒”的选项,调用shouldShowRequestPermissionRationale()后返回false。设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()返回false 。加这个提醒的好处在于,用户拒绝过一次权限后我们再次申请时可以提醒该权限的重要性,面得再次申请时用户勾选“不再提醒”并决绝,导致下次申请权限直接失败。
综上所述,整合代码后:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {// 没有权限。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
// 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
} else {
// 申请授权。
ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MMM: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被用户同意,可以去放肆了。
} else {
// 权限被用户拒绝了,洗洗睡吧。
}
return;
}
}
}