从 Android 6.0 开始,按照是否需要动态申请分为 普通权限 和 特殊权限
创新互联长期为近1000家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为霍城企业提供专业的成都网站建设、网站制作,霍城网站改版等技术服务。拥有十余年丰富建站经验和众多成功案例,为您定制开发。
特殊权限 需要程序运行时申请并通过之后才能使用,或者程序安装为系统应用或系统签名。
注意:
1、动态申请也需要在清单文件中配置(AndroidMenifest.xml)
2、权限是分组的,同一组的权限申请其中一个,同组的权限就全部都申请了
特殊权限共有 9 组:
9 组具体权限列表:
普通权限在清单文件配置(AndroidMenifest.xml)即可使用
(1)在 AndroidMenifest.xml 清单文件中申请
(2)单个申请
(2)批量申请
(3)回调处理
我们知道 Android 应用程序是沙箱隔离的,每个应用都有一个只有自己具有读写权限的专用数据目录。但是如果应用要访问别人的组件或者一些设备上全局可访问的资源,这时候权限机制就能系统化地规范并强制各类应用程序的行为准则。
Android 安全性概览
在 Android 中,一个权限,本质上是一个字符串,一个可以表示执行特定操作的能力的字符串。比如说:访问 SD 卡的能力,访问通讯录的能力,启动或访问一个第三方应用中的组件的能力。 权限被授予了之后,首先会在内存和本地中有记录,这在调用系统binder服务和其他应用组件时做鉴权依据,比如调用系统binder服务时会通过Binder.getCallingUid()拿到调用者的Uid,而Uid一般都是与应用包名一一对应的,再拿这个Uid到PMS里去查这个应用对应的权限。 其次会按被授予的权限将应用分到某个组。 可以参考
自定义权限的应用场景在于限制其它应用对本应用四大组件的访问。具体用法可以参考
pm list permissions -f 命令可以详细查看 Android 所有预定义的权限。
更详细的权限信息参考
可以看到一个权限的信息包括:定义的包名、标签、描述、 权限组 和 保护级别 。
权限根据设备的功能或特性分为多个组。如果应用已在相同权限组中被授予另一危险权限,系统将立即授予该权限,如READ_CONTACTS和WRITE_CONTACTS。
SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 由于其特殊性,其申请方式与其它权限都不同。
其授予流程如下:
(关于 AppOpsManager 是什么可以参考: )
这里简要分析下ActivityCompat#requestPermissions的流程:
更详细的权限授予流程源码分析可以参考:
普通权限: 清单文件中声明即可。
危险权限: 方式一: pm grant application_package android.permission.CHANGE_CONFIGURATION 方式二:appops set application_package permission_num 0/1
appops可以授予的权限参考 android.app.AppOpsManager 中的声明
系统签名权限: 方式一:将app迁移到system/priv-app目录中。 方式二:看不懂,参考
android 4.4 访问sd卡需要申请权限。 您的应用在 Android 4.4 上运行时无法读取外部存储空间上的共享文件,除非您的应用具有 READ_EXTERNAL_STORAGE 权限。也就是说,没有此权限,您无法再访问 getExternalStoragePublicDirectory() 返回的目录中的文件。但是,如果您仅需要访问 getExternalFilesDir() 提供的您的应用特有目录,那么,您不需要 READ_EXTERNAL_STORAGE `权限。
android 6.0 运行时权限。 此版本引入了一种新的权限模式,如今,用户可直接在运行时管理应用权限。这种模式让用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。用户可为所安装的各个应用分别授予或撤销权限。 对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。 如需了解有关在您的应用中支持新权限模式的详情,请参阅 使用系统权限 。如需了解有关如何评估新模式对应用的影响的提示,请参阅 权限最佳做法 。
android 7.+ 应用间共享文件要使用FileProvider。 对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。 要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider `类。如需了解有关权限和共享文件的详细信息,请参阅 共享文件 。
android 8.+
同一权限组的权限在被授予了之后也需要显式的再申请一次。
在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。 对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。 例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 。应用请求 READ_EXTERNAL_STORAGE ,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE ,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE ;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE ,则系统会立即授予该权限,而不会提示用户。
android 9
隐私权限变更。
为了增强用户隐私,Android 9 引入了若干行为变更,如限制后台应用访问设备传感器、限制通过 Wi-Fi 扫描检索到的信息,以及与通话、手机状态和 Wi-Fi 扫描相关的新权限规则和权限组。
android 10
隐私权变更。
外部存储访问权限范围限定为应用文件和媒体,在后台运行时访问设备位置信息需要权限,针对从后台启动 Activity 的限制等。
android 11
隐私权限变更。
更详细的版本变更请参考
一个新建的Android应用默认是没有权限的,这意味着它不能执行任何可能对用户体验有不利影响的操作或者访问设备数据。为了使用受保护的功能,你必须包含一个或者多个标签在你的app manifest中。
1、Android 6.0中权限分为两种,普通权限和危险权限(即运行时权限,下面统称运行时权限)。
1.1普通权限
如果你的应用manifest中只申明了普通权限(也就是说,这些权限对于用户隐私和设备操作不会造成太多危险),系统会自动授予这些权限。
1.2运行时权限
如果你的应用manifest中声明了运行时权限(也就是说,这些权限可能会影响用户隐私和设备的普通操作),系统会明确的让用户决定是否授予这些权限。系统请求用户授予这些权限的方式是由当前应用运行的系统版本来决定的。
1.2.1 Android6.0及以上的系统
如果你的设备运行的是Android6.0(API level 23)及以上的系统,并且你的应用的targetSdkVersion也是23或者更高,那么应用向用户请求这些权限是实时的。这意味着用户可以随时取消 这些运行时权限的授权。所以应用在每次需要用到这些运行时权限的时候都需要去检查是否还有这些权限的授权。
1.2.2 Android 5.1及以下的系统
如果你的设备运行在Android5.1(API level 22)及以下的系统中,或者你的app的targetSdkVersion是22或者更低。系统会请求用户在apk安装的时候授予这些权限。
如果你的应用更新的时候添加了一个权限,系统会在用户更新应用的时候请求用户授予这个权限 , 一旦用户安装了这个应用,唯一可以取消授权的方式就是卸载掉这个应用。 注意这句话的意思,想一下如果app的targetSdkVersion是22或者以下,但是运行在Android6.0及以上的设备中会有什么问题?后面会分析这个问题 。
常常来说一个授权失败会抛出SecurityException,然而这并不是在 所有情况下都会发生。比如,发送一个广播去检查授权(SendBroadcast(Intent)),数据会被发送给所有接收者,但是当这个方法的请求返 回的时候,你不会收到任何一个因为授权失败抛出的异常,其实在大多数情况下,授权失败只会打印系统日志。
1.3自动权限调整
简单的说,如果你的app targetSdkVersion是3,而你当前运行的系统版本是4,那么在android version 4 中新添加的权限会自动添加到你的app中。
比如 WRITE_EXTERNAL_STORAGE权限是在api 4的时候添加的,而你的应用的targetSdkVersion是3,那么这个权限会自动添加到你的应用中。而且在官方商店上这个权限也会列出来(尽管可能你并不需要这个权限)。
所以建议经常更新你的targetSdkVersion到最新版本。
下面来回答上面的那个问题,如果app的targetSdkVersion是22或者以下,但是运行在android 6.0或以上版本的手机中,会发生什么?
安装过程中,会一起请求用户授予所有 权限,如果用户拒绝,将不能安装这个app,只有用户全部同意这些授权,才能安装这个应用,但是问题来了,安装好了这个应用之后,android6.0以 上的系统中,用户是可以去设置中取消授权的,而且是随时都可以取消,所以很多运行时权限可能也得不到,目前官方的做法是,如果用户取消该项授权,那么依赖 该项授权的方法的返回值为null,所以你的app可能会报空指针异常。以后是否会针对22以下的app做改变还不得而知,毕竟crash是很难让人接受 的,但是crash是由用户造成的,用户应该也可以理解。
2、普通权限和运行时权限
系统权限会被传递给两种不同的保护级别,我们所知道这两种最重要的保护级别就是普通权限和运行时权限。
2.1 普通权限
普通权限的覆盖区域是在你的app需要访问沙盒以外的数据和资源的时候,但是对用户隐私和其他app的操作只有很少的影响,比如开启手电筒的权限。这个时候,系统会自动授权这些普通权限。
Normal Permissions:
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_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
2.2 运行时权限
运行时权限的覆盖区域是你的app想要的数据和资源涉及用户的隐私信息,或者是可能潜在的影响用户的存储数据或者其他app的操作。比如,请求获取用户联系人信息的权限。如果一个app申明了运行时权限,用户必须明确的授权这些权限给app。
2.3 权限组
所有的运行时权限都属于对应的权限组,如果你的app运行在android6.0及以上系统,下面的规则都适用:
2.3.1 如果一个app在manifest中请求了一个运行时权限,而且app还没有得到这个运行时权限所在的权限组中的任何一个运行时权限授权,那么系统会弹出一个对话框,描述app想要访问的运行时权限的权限组,这个对话框不会描述这个权限组中某一个特定的权限。比如,你的app想要请求READ_CONTACT权限,对话框只会描述app想要请求设备的联系人,如果用户授权通过,系统会授予app所请求的该项权限。
2.3.2 如果一个app在manifest中请求一个运行时权限,并且这个app已经在相同的权限组中有了另一个运行时权限的授权,那么系统不会弹出对话框,而是会立即授予app该项运行时权限的授权。比如,一个app之前请求过一个READ_CONTACT的权限并且被授权通过,之后又请求一个WRITE_CONTACT(在同一个权限组)权限,那么系统会自动授予该权限。
其实所有权限(普通权限、运行时权限、用户自定义权限)都属于特定的权限组,但是只有运行时权限的权限组才会影响用户体验。
2.3.3运行时权限组和权限组列表
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
可以通过adb shell pm list permissions -d -g进行查看。
看到上面的dangerous permissions,会发现一个问题,好像危险权限都是一组一组的,恩,没错,的确是这样的,那么有个问题:分组对我们的权限机制有什么影响吗?的确是有影响的,如果app运行在Android 6.x的机器上,对于授权机制是这样的。如果你申请某个危险的权限,假设你的app早已被用户授权了 同一组 的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。比如你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过。此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。不过需要注意的是,不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。
3、相关API
3.1 在AndroidManifest文件中添加需要的权限。
这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有声明的权限可能会导致程序崩溃。
3.2 检查权限
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
}else{
//
}
这里涉及到一个API,ContextCompat.checkSelfPermission,主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了
3.3 申请授权
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框 逐一 询问用户是否授权。
3.4 处理权限申请回调
@Override
publicvoidonRequestPermissionsResult(intrequestCode,
String permissions[],int[] grantResults) {
switch(requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if(grantResults.length 0 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the contacts-related task you need to do.
}else{
// permission denied, boo! Disable the functionality that depends on this permission.
}
return;
}
}
}
ok,对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了!
那么将上述几个步骤结合到一起就是:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
谢幕,至此有关于android6.0 以上权限相关的内容已经详细讲完了!