Android权限管理秘籍
Android权限管理秘籍
前不久在项目中遇到了一些权限问题导致的Crash,虽然问题简单通过添加权限后解决了,但是关于权限的一些疑问还是在我心里埋下了种子,于是决定搞一搞清楚。经过几天的琢磨,研究,学习,啃码后浓缩为这一篇Android权限管理秘籍。本秘籍不会涉及到太多的源码,试图以尽量简单的方式帮助你理解Android中对权限的管理。
minSdkVersion VS targetSdkVersion
Android开发者一定都有接触过他们,其中targetSdkVersion对后面说到的授权阶段的处理策略有很大的影响,所以这里先解释一下它们的含义。当然还有一个maxSdkVersion,但maxSdkVersion意义不大,这里就不单独重点说明了。
minSdkVersion:
其值是一个整数值,表示该应用能支持的最低API level。低于该值的系统在安装该应用时将被拒绝安装。与其对应的是maxSdkVersion,maxSdkVersion表示该应用能支持的最高API level。
targetSdkVersion:
其值也是一个整数值,他表示该应用已经在该值指定的API level版本的系统上进行过充分测试。当应用运行在比该值更高的系统版本时,系统会采用兼容性策略来运行你的应用,以最小化新的系统上的一些特性或变更对应用造成的影响。同时该值也表示应用是基于targetSdkVersion指定的版本的API开发的。还需要向前兼容到minSdkVersion指定的版本,这个兼容工作得由应用开发者自己处理。
比如你在应用中指定了targetSdkVersion为23,那么意味着你的应用是基于API level 23(Android 6.0)系统的API及新特性开发的,但是Android 6.0以下的系统也可能会运行你的应用,所以这个兼容工作得开发者自己完成。同时当运行在API level 24(Android 7.0)系统时,系统将会采用兼容性策略来运行你的应用。这代表着应用可能被动的接受了一些在新版本系统上添加的特性的默认策略或措施。
权限使用
Note:权限的使用可以理解为两个过程:
- 请求权限
由需要使用权限的应用完成。- 授予权限
由用户手动(运行时权限)或者系统自动(安装时权限)完成。只有两个过程都顺利完成,对应用来说才认为权限请求成功,才可以使用权限相关的功能。
Android中定义的权限分了几个级别(ProtectionLevel),后面ProtectionLevel小节会详细介绍,这里主要关注常用的两个级别:
- normal:低风险的权限采用此级别。比如获取网络状态,设置闹钟等。
- dangerous:较高风险的权限,此级别的权限意味着请求权限的应用可能会访问用户的隐私数据或者控制设备,这可能给用户带来负面影响。
请求权限
一个基本的应用默认是没有请求任何权限的,也就意味着这个应用不能做任何超出该应用本身权限的事情(比如读取短信,打开摄像头等等)。为了使用这些被保护的功能,你必须在应用的AndroidManifest.xml中包含一个或多个uses-permission标签来请求权限。
我的例子中需要访问当前网络的状态和读取联系人信息,那么我需要请求以下权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catsuo.permissiondemo">
<!-- 我请求的权限-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Note:
android.permission.ACCESS_NETWORK_STATE权限是normal级别的。
android.permission.READ_CONTACTS权限是dangerous级别的。
frameworks/base/core/res/AndroidManifest.xml文件中定义了系统中的权限,其中详细的描述了各个权限的protectionLevel等属性。本地没有源码的话可以通过传送门查阅Android各个版本的源代码。(最好还是本地下套源码方便,这个传送门有点慢)。
这只是完成了权限的请求阶段,还需要通过授权阶段后才可以正常的使用权限。
授予权限
normal权限
系统会在安装应用时自动授予应用normal级别的权限,完成授权阶段的工作。这里因系统版本不同会有微小区别:
os version <= Android 5.1:
normal级别的权限在应用安装时会提示用户应用请求了哪些该级别的权限。
os version > Android 5.1
normal级别的权限在应用安装时不会提示给用户。
不管在安装时提不提示用户,最终安装完成后系统都会自动将normal权限授予应用。
dangerous权限
系统会让用户去决定是否授权给应用。由于Android 6.0针对dangerous类型的权限加入了运行时授权的策略,所以针对dangerous权限授权阶段的处理将受以下三个因素影响:
- 权限的级别(ProtectionLevel)。
- 当前系统的版本。
- 应用的targetSdkVersion。
具体的策略如下:
os version >= Android 6.0 && targetSdkVersion >= 23 :
dangerous级别的权限在应用安装时不会提示给用户。
由于os version >= Android 6.0,所以在安装时normal级别的权限也没有提示给用户。
这些权限在应用安装后默认都处于未授权状态,需要在使用权限时通过代码主动申请权限,最终由用户来决定是否要为应用授权。用户也能在任何时候撤销授权,所以应用必须在使用dangerous权限相关的功能之前去检查是否已被授予相应的权限。关于更多的申请权限的信息可以参阅这篇文档。
os version <= Android 5.1 && targetSdkVersion <= 22 :
dangerous级别的权限在应用安装时会提示给用户。
由于os version <= Android 5.1,所以在安装时normal级别的权限也被提示给用户。
应用被安装后将默认授予请求的权限,此时唯一的撤销授权方式就是删除该应用。因为在Android 6.0才加入的权限动态管理。
os version >= Android 6.0 && targetSdkVersion <= 22
dangerous级别的权限在应用安装时会提示给用户。
由于os version >= Android 6.0,所以在安装时normal级别的权限也没有提示给用户。
此时用户除了通过卸载应用来撤销授权外还可以在权限管理中手动撤销授权,但是这很可能导致应用在使用的过程中崩溃,因为此时的系统Android 6.0是支持运行时权限的,但是应用的targetSdkVersion为22或者更低注定了应用是不支持运行时权限的,当用户手动禁用某权限后,应用中没有实现运行时申请权限,导致崩溃发生。所以在这种情况下撤销授权系统也会提示用户存在的风险。
这里描述的是两种常用的的级别normal和dangerous的授权阶段策略,在后面ProtectionLevel小节中会详细说明另一种特殊的级别signature的权限授权策略。
小结
targetSdkVersion对授权的影响其实就体现了之前说的系统会以兼容性策略来运行应用。正是因为Android 6.0对权限管理做了较大升级和改动,所以当targetSdkVersion指定的API level低于Android 6.0对应的API level,并且应用的运行时环境是Android 6.0或更新系统时,系统要以兼容性策略来运行应用以最小化未适配Android 6.0的应用在新系统上运行时遇到的问题。
自动授权
随着时间的推移,Android的系统在不断升级。一些新的约束条件可能会被添加到系统中,包括一些新的权限,有可能在应用之前使用一些API时不需要太多限制,但是在新的Android版本中对那些API添加了权限控制,需要应用请求授权后才能正常使用。这时候为了保证你的应用在新的Android系统中能够正常运行,系统会自动为你的应用请求系统中新添加的权限。系统去判断是否为一个应用请求新的权限也是决定于应用的targetSdkVersion属性。如果这个属性值低于某个权限被添加的API level,系统就会自动为应用请求该权限。
比如,WRITE_EXTERNAL_STORAGE权限是在API level 4的时候添加的,用于限制对应用对存储空间的访问。如果你的应用的targetSdkVersion属性是3或者更低,那么当你的应用运行在API level 4或更高版本的系统时,系统将自动为你的应用申请WRITE_EXTERNAL_STORAGE权限。
Note:如果一个权限被自动的添加到你的应用,在应用市场中也会将系统自动为你申请的权限也展示出来,虽然你的应用可能根本不需要用到这些权限。
所以,为了避免这种情况,去除这些系统默认为你请求但实际你并不需要的权限。你应该总是保证你的应用的targetSdkVersion属性的值是最新的。
查看权限
你可以查看当前系统中已定义的权限:
adb shell pm list permissions
All Permissions:
permission:android.permission.REAL_GET_TASKS
permission:android.permission.ACCESS_CACHE_FILESYSTEM
permission:android.permission.REMOTE_AUDIO_PLAYBACK
permission:com.google.android.apps.photos.permission.C2D_MESSAGE
permission:android.permission.REGISTER_WINDOW_MANAGER_LISTENERS
permission:android.permission.INTENT_FILTER_VERIFICATION_AGENT
permission:android.permission.BIND_INCALL_SERVICE
permission:com.google.android.gms.trustagent.framework.model.DATA_CHANGE_NOTIFICATION
permission:android.permission.WRITE_SETTINGS
permission:com.google.android.vending.verifier.ACCESS_VERIFIER
permission:android.permission.CONTROL_KEYGUARD
permission:getui.permission.GetuiService.com.linkedin.android
permission:com.google.android.calendar.permission.C2D_MESSAGE
permission:com.taobao.taobao.permission.C2D_MESSAGE
permission:android.permission.CONFIGURE_WIFI_DISPLAY
permission:android.permission.CONFIGURE_DISPLAY_COLOR_MODE
permission:android.permission.ACCESS_WIMAX_STATE
permission:android.permission.SET_INPUT_CALIBRATION
permission:android.permission.RECOVERY
...
也可以加上-s选项,结果将会进行分组,并以用户看到的格式展现出来:
adb shell pm list permissions -s
All Permissions:
汽车信息: 汽车供应商渠道, 汽车行驶里程, 汽车油量
通讯录: 修改您的通讯录, 查找设备上的帐号, 读取联系人
电话: 读取通话记录, null, 读取手机状态和身份, 使用即时通讯通话服务, 拨打电话, null, 新建/修改/删除通话记录, 拨打/接听SIP电话, 重新设置外拨电话的路径, 添加语音邮件
日历: 读取日历活动和机密信息, 添加或修改日历活动,并在所有者不知情的情况下向邀请对象发送电子邮件
相机: 拍摄照片和视频
身体传感器: 访问身体传感器(如心率监测器), 使用指纹硬件
位置信息: 访问确切位置信息(以 GPS 和网络为依据), 车速, 访问大致位置信息(以网络为依据)
存储空间: 读取您的USB存储设备中的内容, 修改或删除您的USB存储设备中的内容
麦克风: 录音
短信: 读取短信, 接收讯息 (WAP), 接收讯息(彩信), 接收讯息(短信), 发送短信, 读取小区广播消息
...
也可以查看某个应用定义了哪些权限,申请了哪些权限:
adb shell dumpsys package com.tencent.mm
// 定义的权限
declared permissions:
com.tencent.mm.plugin.permission.WRITE: prot=signature, INSTALLED
com.tencent.mm.plugin.permission.READ: prot=signature, INSTALLED
com.tencent.mm.permission.MM_MESSAGE: prot=signature, INSTALLED
com.tencent.mm.ext.permission.READ: prot=signature|privileged, INSTALLED
com.tencent.mm.ext.permission.WRITE: prot=signature|privileged, INSTALLED
com.tencent.mm.ext.permission.SPORT: prot=normal, INSTALLED
com.tencent.mm.wear.message: prot=signature, INSTALLED
com.tencent.mm.permission.C2D_MESSAGE: prot=signature, INSTALLED
// 请求的权限
requested permissions:
android.permission.CHANGE_WIFI_MULTICAST_STATE
com.tencent.mm.permission.MM_MESSAGE
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.CAMERA
android.permission.GET_TASKS
android.permission.INTERNET
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.RECEIVE_BOOT_COMPLETED
...
// 请求并被授权的权限
install permissions:
android.permission.DOWNLOAD_WITHOUT_NOTIFICATION: granted=true
com.google.android.c2dm.permission.RECEIVE: granted=true
android.permission.USE_CREDENTIALS: granted=true
android.permission.MODIFY_AUDIO_SETTINGS: granted=true
android.permission.MANAGE_ACCOUNTS: granted=true
android.permission.NFC: granted=true
android.permission.WRITE_SYNC_SETTINGS: granted=true
android.permission.RECEIVE_BOOT_COMPLETED: granted=true
com.tencent.mm.permission.MM_MESSAGE: granted=true
com.android.launcher.permission.UNINSTALL_SHORTCUT: granted=true
...
ProtectionLevel
protectionLevel是定义权限时的一个重要属性,它表示一个权限的级别,在很大程度上它也决定了一个权限被授权的方式(由系统安装时自动授权或者由用户来决定是否授权)。
protectionLevel可以分为两类:基础权限级别和附加权限级别。
基础权限级别:
- normal
- dangerous
- signature
- signatureOrSystem
附加权限级别:
除了基础权限级别的其他权限级别都属于附加权限级别。它们必须附加在基础权限级别上使用。从目前系统定义的权限来看,附加权限级别基本都是与signature基础权限级别搭配使用。
可以理解为附加权限级别是在为signature级别的权限开后门,使signature级别的权限在特定的条件下能够授权给特定类型的应用。
protectionLevel属性可以取以下值,如果设置多个值需要用’|’分隔开。如果没有定义protectionLevel,则默认为normal。
- normal:低风险的权限采用此级别。在app安装的时候,app请求的normal权限将在应用安装时由系统自动完成授权。
- dangerous:较高风险的权限,此级别的权限意味着请求权限的app将要访问用户的隐私数据或者控制设备,这可能给用户带来负面影响。因为dangerous权限会引入潜在的风险,所以系统不会自动授权此类权限给app。例如,在安装app的时候,会将dangerous权限展示给用户,并请求用户确认。在Android 6.0及以上的版本中更是需要用户主动授权,用户还可以随时对这类型权限撤销授权。
- signature:如果请求权限的app与声明权限的app签名一致,系统会自动赋予权限,而不会通知用户或者征求用户的同意。 否则需要通过intent将用户引导到权限管理界面由用户决定是否授权。后面会举例说明。
- signatureOrSystem:有两种应用可以自动获取该类型权限的授权:
- 与定义这个权限的apk拥有相同的签名的应用(这一点和Signature类型的权限相同)。
- 在/system/priv-app和/system/framework目录下的应用(即拥有超级权限的系统应用)。
- privileged:只能与signature同时使用。signature | privileged与signatureOrSystem意义相同。
- system:与privileged相同,是privileged的老版本。
- development:development applications可以被自动授予此权限。
- appop:此类权限会与AppOpsManager来配合完成对应用操作的限制(AppOpsManager在后面的小节介绍)。
- pre23:应用请求此类权限后,系统将在应用安装时自动授权给那些targetSdkVersion在23(Android 6.0)以下的应用。
- installer:此类权限自动被授权给那些负责安装apk的系统app。
- verifier:此类权限自动被授权给那些负责验证apk的系统app。
- preinstalled:此类权限可以自动被授权给任何预安装在system image中的app,不只是privileged app。
- setup:此类权限自动被授予‘安装向导’app。
下面举几个例子说明:
例子一 signature级别权限
编写两个应用:
com.catsuo.permissiondemo:
自定义权限com.catsuo.permissiondemo.TEST_PERMISSION,并标明protectionLevel为signature。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catsuo.permissiondemo">
<!--
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
-->
<!-- 定义com.catsuo.permissiondemo.TEST_PERMISSION权限-->
<permission android:name="com.catsuo.permissiondemo.TEST_PERMISSION"
android:protectionLevel="signature"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
com.catsuo.usepermission:
请求com.catsuo.permissiondemo中定义的com.catsuo.permissiondemo.TEST_PERMISSION权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catsuo.usepermission">
<!-- 请求com.catsuo.permissiondemo中定义的com.catsuo.permissiondemo.TEST_PERMISSION权限-->
<uses-permission android:name="com.catsuo.permissiondemo.TEST_PERMISSION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
接下来使用前面介绍的命令验证是否授权成功。
case 1 : 两个应用签名一致
adb shell pm list permissions | grep “catsuo”
permission:com.catsuo.permissiondemo.TEST_PERMISSION
adb shell dumpsys package com.catsuo.usepermission
...
Packages:
Package [com.catsuo.usepermission] (c18541b):
userId=10192
pkg=Package{7de39b8 com.catsuo.usepermission}
codePath=/data/app/com.catsuo.usepermission-1
resourcePath=/data/app/com.catsuo.usepermission-1
legacyNativeLibraryDir=/data/app/com.catsuo.usepermission-1/lib
primaryCpuAbi=null
secondaryCpuAbi=null
versionCode=1 minSdk=15 targetSdk=24
versionName=1.0
splits=[base]
apkSigningVersion=2
applicationInfo=ApplicationInfo{b84d391 com.catsuo.usepermission}
flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
privateFlags=[ RESIZEABLE_ACTIVITIES ]
dataDir=/data/user/0/com.catsuo.usepermission
supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity]
timeStamp=2017-06-28 23:21:16
firstInstallTime=2017-06-28 23:21:27
lastUpdateTime=2017-06-28 23:21:27
signatures=PackageSignatures{32a72f6 [9bdb9ea1]}
installPermissionsFixed=true installStatus=1
pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
# 请求权限com.catsuo.permissiondemo.TEST_PERMISSION
requested permissions:
com.catsuo.permissiondemo.TEST_PERMISSION
# 成功授权com.catsuo.permissiondemo.TEST_PERMISSION(granted=true)
install permissions:
com.catsuo.permissiondemo.TEST_PERMISSION: granted=true
User 0: ceDataInode=870037 installed=true hidden=false suspended=false stopped=true notLaunched=true enabled=0
runtime permissions:
...
case 2 : 两个应用签名不同
adb shell pm list permissions | grep “catsuo”
permission:com.catsuo.permissiondemo.TEST_PERMISSION
adb shell dumpsys package com.catsuo.usepermission
...
Packages:
Package [com.catsuo.usepermission] (93a6b3e):
userId=10193
pkg=Package{a80bd9f com.catsuo.usepermission}
codePath=/data/app/com.catsuo.usepermission-1
resourcePath=/data/app/com.catsuo.usepermission-1
legacyNativeLibraryDir=/data/app/com.catsuo.usepermission-1/lib
primaryCpuAbi=null
secondaryCpuAbi=null
versionCode=1 minSdk=15 targetSdk=24
versionName=1.0
splits=[base]
apkSigningVersion=2
applicationInfo=ApplicationInfo{a915eec com.catsuo.usepermission}
flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
privateFlags=[ RESIZEABLE_ACTIVITIES ]
dataDir=/data/user/0/com.catsuo.usepermission
supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity]
timeStamp=2017-06-28 23:42:11
firstInstallTime=2017-06-28 23:42:22
lastUpdateTime=2017-06-28 23:42:22
signatures=PackageSignatures{15982b5 [3c291418]}
installPermissionsFixed=true installStatus=1
pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
# 请求权限com.catsuo.permissiondemo.TEST_PERMISSION,但是并没有被授权
requested permissions:
com.catsuo.permissiondemo.TEST_PERMISSION
User 0: ceDataInode=870023 installed=true hidden=false suspended=false stopped=true notLaunched=true enabled=0
runtime permissions:
...
例子二 辅助权限级别
在系统中定义了一个android.permission.SYSTEM_ALERT_WINDOW权限:
frameworks/base/core/res/AndroidManifest.xml
...
<!-- Allows an app to create windows using the type
{@link android.view.WindowManager.LayoutParams#TYPE_SYSTEM_ALERT},
shown on top of all other apps. Very few apps
should use this permission; these windows are intended for
system-level interaction with the user.
<p class="note"><strong>Note:</strong> If the app
targets API level 23 or higher, the app user must explicitly grant
this permission to the app through a permission management screen. The app requests
the user's approval by sending an intent with action
{@link android.provider.Settings#ACTION_MANAGE_OVERLAY_PERMISSION}.
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
<p>Protection level: signature -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
android:protectionLevel="signature|preinstalled|appop|pre23|development" />
...
这个权限用很多辅助权限级别修饰了signature这个基础权限级别,也就是说只要修饰它的几个辅助权限级别中任意满足一个的授权条件,就算signature本身的授权条件不满足,系统依然会为应用授权。参照上表的描述信息,这几个辅助权限级别中pre23比较容易满足,下面是实现代码:
case 1 :targetSdkVersion = 22
com.catsuo.usepermission AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catsuo.usepermission">
<!-- 请求android.permission.SYSTEM_ALERT_WINDOW权限-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
com.catsuo.usepermission build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.catsuo.usepermission"
minSdkVersion 15
// 指定targetSdkVersion为22
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
adb shell dumpsys package com.catsuo.usepermission
...
Packages:
Package [com.catsuo.usepermission] (22e3e9c):
userId=10194
pkg=Package{d6c60a5 com.catsuo.usepermission}
codePath=/data/app/com.catsuo.usepermission-1
resourcePath=/data/app/com.catsuo.usepermission-1
legacyNativeLibraryDir=/data/app/com.catsuo.usepermission-1/lib
primaryCpuAbi=null
secondaryCpuAbi=null
versionCode=1 minSdk=15 targetSdk=22
versionName=1.0
splits=[base]
apkSigningVersion=2
applicationInfo=ApplicationInfo{1359c7a com.catsuo.usepermission}
flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
dataDir=/data/user/0/com.catsuo.usepermission
supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity]
timeStamp=2017-06-28 23:59:52
firstInstallTime=2017-06-29 00:00:03
lastUpdateTime=2017-06-29 00:00:03
signatures=PackageSignatures{9dcfc2b [3c291418]}
installPermissionsFixed=true installStatus=1
pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
# 应用请求了android.permission.SYSTEM_ALERT_WINDOW权限
requested permissions:
android.permission.SYSTEM_ALERT_WINDOW
# 应用请求的android.permission.SYSTEM_ALERT_WINDOW权限被授权(granted=true)
install permissions:
android.permission.SYSTEM_ALERT_WINDOW: granted=true
User 0: ceDataInode=869828 installed=true hidden=false suspended=false stopped=true notLaunched=true enabled=0
runtime permissions:
...
case 2 :targetSdkVersion >= 23
com.catsuo.usepermission AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catsuo.usepermission">
<!-- 请求android.permission.SYSTEM_ALERT_WINDOW权限-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
com.catsuo.usepermission build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.catsuo.usepermission"
minSdkVersion 15
// 指定targetSdkVersion为23
targetSdkVersion 23
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
adb shell dumpsys package com.catsuo.usepermission
...
Packages:
Package [com.catsuo.usepermission] (77e176c):
userId=10195
pkg=Package{55b1135 com.catsuo.usepermission}
codePath=/data/app/com.catsuo.usepermission-1
resourcePath=/data/app/com.catsuo.usepermission-1
legacyNativeLibraryDir=/data/app/com.catsuo.usepermission-1/lib
primaryCpuAbi=null
secondaryCpuAbi=null
versionCode=1 minSdk=15 targetSdk=23
versionName=1.0
splits=[base]
apkSigningVersion=2
applicationInfo=ApplicationInfo{b02a9ca com.catsuo.usepermission}
flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
dataDir=/data/user/0/com.catsuo.usepermission
supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity]
timeStamp=2017-06-29 00:07:52
firstInstallTime=2017-06-29 00:08:03
lastUpdateTime=2017-06-29 00:08:03
signatures=PackageSignatures{c1a073b [3c291418]}
installPermissionsFixed=true installStatus=1
pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
# 应用请求了android.permission.SYSTEM_ALERT_WINDOW权限,但是没有被授权
requested permissions:
android.permission.SYSTEM_ALERT_WINDOW
User 0: ceDataInode=869960 installed=true hidden=false suspended=false stopped=true notLaunched=true enabled=0
runtime permissions:
...
当com.catsuo.usepermission指定其targetSdkVersion < 23时,因为满足了android.permission.SYSTEM_ALERT_WINDOW的protectionLevel中pre23这个辅助权限级别的条件,所以应用得以授权成功。
当com.catsuo.usepermission指定其targetSdkVersion >= 23时,不满足android.permission.SYSTEM_ALERT_WINDOW的protectionLevel中pre23这个辅助权限级别的条件,同时其他辅助权限级别的条件也不满足,更不满足基本权限级别signature的条件(我的应用并没有系统签名)。所以应用授权失败。
在frameworks/base/core/res/AndroidManifest.xml能够看到只有两个权限被pre23修饰,一个是android.permission.SYSTEM_ALERT_WINDOW,另一个是android.permission.WRITE_SETTINGS。这是因为这两个权限原来是dangerous级别的,是在Android 6.0时才提升为signature级别的,这里提供pre23也只是一个兼容性策略。感觉Google的意图是想在后续的版本中收紧这两个权限。
小结
另外还有一个基础权限级别signatureOrSystem,它其实等同于signature | privileged,也就是说除了signature本身的条件外,如果申请授权的应用满足privileged级别的条件,该权限也可以被授权通过。
这两个例子是想说明protectionLevel为signature的权限的授权阶段与上面分析的normal和dangerous权限的授权阶段策略是不同的。针对辅助权限级别这里只用pre23举了一个例子,其他的辅助权限级别在修饰signature级别时其实也一样,只要有一个辅助权限级别条件满足,系统将授权给请求权限的应用。不过系统中定义的signature级别的权限大部分都是hide的,第三方应用的开发很少用到,ROM产商的应用可能会用的比较多一些。针对第三方应用来说signature级别的权限使用更多是在自定义权限时。比如第一个例子中,签名相同的应用之间可以通过signature级别的权限更安全的完成一些应用间的功能调用或者数据访问。
权限组
Note:所有类型的权限都可以属于一个权限组,包括上面提到的其他基本权限以及我们自定义的权限。但只有当这个权限是一个dangerous权限时权限组的概念才会影响用户。所以这里只关注类型为dangerous的权限的权限组。在frameworks/base/core/res/AndroidManifest.xml中也能看到系统在定义权限时大部分指定了权限组的权限都是dangerous类型。
对权限组的处理分以下两种情况:
os version >= Android 6.0 && targetSdkVersion >= 23
当应用向系统请求一个dangerous类型的权限时,有可能出现下列两种情况:如果应用没有获得与当前申请的权限在同一权限组的其他权限的授权,那么系统将以这个权限组的描述信息去提示用户,而不是具体申请的权限的描述信息。比如,一个应用申请了READ_CONTACTS权限,系统会提示用户”应用需要访问设备的联系人(包含读写)”,如果用户同意授权,系统只会赋予应用之前申请的权限(在这里就只是READ_CONTACTS)。
如果应用已经获得了与正在申请的权限同一个权限组的其他权限的授权,那么系统会自动将正在申请的权限授予应用,不需要任何与用户的交互行为。比如,如果一个应用之前已经获得了READ_CONTACTS权限的授权,那么在之后应用请求WRITE_CONTACTS权限时,系统会自动将该权限授予应用。
os version <= Android 5.1 || targetSdkVersion <= 22
当应用向系统请求一个dangerous类型的权限时,系统将在用户安装应用时询问用户是否授权。同样的,系统只会告诉用户应用需要的权限组的描述信息,而不是单独的权限的描述信息。由于这种情况下不存在动态权限的使用,权限都在安装时完成授权,应用在运行过程中不会动态请求新的权限,所以不会存在同权限组的其他权限已被授权而导致请求的权限自动被授权的情况。
下表为系统中定义的权限组及所属权限:
AppOpsManager
这个类是在Android 4.4的时候加入的,用来管理应用的操作行为,它与原本系统的权限机制是不相同的,可以理解为这是两套框架,它们是相互配合来完成应用行为管理和约束。在引入了AppOpsManager后,应用需要使用某个功能时除了需要请求相应的权限外,还需要通过AppOpsManager的认证才能正常使用。在AppOpsManager中定义了很多操作行为,一个操作行为一般情况下会与一个权限对应,同时为每个操作行为提供了默认的认证策略:
AppOpsManager.java
public class AppOpsManager {
...
// 这里定义了四种类型的认证结果
// MODE_ALLOWED : 认证通过
// MODE_IGNORED : 认证失败,但不会抛出异常
// MODE_ERRORED : 认证失败,会抛出异常
// MODE_DEFAULT : AppOpsManager不处理,由权限框架来处理,只有当操作行为对应的权限被appop修饰时使用
/**
* Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
* allowed to perform the given operation.
*/
public static final int MODE_ALLOWED = 0;
/**
* Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
* not allowed to perform the given operation, and this attempt should
* <em>silently fail</em> (it should not cause the app to crash).
*/
public static final int MODE_IGNORED = 1;
/**
* Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the
* given caller is not allowed to perform the given operation, and this attempt should
* cause it to have a fatal error, typically a {@link SecurityException}.
*/
public static final int MODE_ERRORED = 2;
/**
* Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller should
* use its default security check. This mode is not normally used; it should only be used
* with appop permissions, and callers must explicitly check for it and deal with it.
*/
public static final int MODE_DEFAULT = 3;
// 针对每种操作行为的默认认证结果列表,数组索引代表不同的操作行为
// 如果没有特殊情况,一般对某个操作行为的认证都使用该默认结果列表
/**
* This specifies the default mode for each operation.
*/
private static int[] sOpDefaultMode = new int[] {
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_IGNORED, // OP_WRITE_SMS
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_DEFAULT, // OP_WRITE_SETTINGS
AppOpsManager.MODE_DEFAULT, // OP_SYSTEM_ALERT_WINDOW
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_DEFAULT, // OP_GET_USAGE_STATS
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ERRORED, // OP_MOCK_LOCATION
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND
};
...
}
可以看到针对大部分的操作行为AppOpsManager默认都认证通过,这里重点看一下认证结果为MODE_DEFAULT的操作行为:
- OP_WRITE_SETTINGS
- OP_SYSTEM_ALERT_WINDOW
- OP_GET_USAGE_STATS
与它们对应的权限分别是:
- android.permission.WRITE_SETTINGS
- android.permission.SYSTEM_ALERT_WINDOW
- android.permission.PACKAGE_USAGE_STATS
这三个权限的protectionLevel都指定了appop辅助权限级别:
frameworks/base/core/res/AndroidManifest.xml
...
<!-- Allows an application to read or write the system settings.
<p class="note"><strong>Note:</strong> If the app targets API level 23
or higher, the app user
must explicitly grant this permission to the app through a permission management screen.
The app requests the user's approval by sending an intent with action
{@link android.provider.Settings#ACTION_MANAGE_WRITE_SETTINGS}. The app
can check whether it has this authorization by calling {@link
android.provider.Settings.System#canWrite Settings.System.canWrite()}.
<p>Protection level: signature
-->
<permission android:name="android.permission.WRITE_SETTINGS"
android:label="@string/permlab_writeSettings"
android:description="@string/permdesc_writeSettings"
android:protectionLevel="signature|preinstalled|appop|pre23" />
<!-- Allows an app to create windows using the type
{@link android.view.WindowManager.LayoutParams#TYPE_SYSTEM_ALERT},
shown on top of all other apps. Very few apps
should use this permission; these windows are intended for
system-level interaction with the user.
<p class="note"><strong>Note:</strong> If the app
targets API level 23 or higher, the app user must explicitly grant
this permission to the app through a permission management screen. The app requests
the user's approval by sending an intent with action
{@link android.provider.Settings#ACTION_MANAGE_OVERLAY_PERMISSION}.
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
<p>Protection level: signature -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
android:protectionLevel="signature|preinstalled|appop|pre23|development" />
<!-- @SystemApi Allows an application to collect component usage
statistics
<p>Declaring the permission implies intention to use the API and the user of the
device can grant permission through the Settings application. -->
<permission android:name="android.permission.PACKAGE_USAGE_STATS"
android:protectionLevel="signature|privileged|development|appop" />
...
这就验证了介绍protectionLevel时对appop辅助权限级别的说明,被appop修饰的权限将与AppOpsManager相互配合来约束用户的行为(目前来看这种配合只是AppOpsManager不处理,处理权交由权限控制框架)。更多关于AppOpsManager的细节可以参阅源码。
总的来说这个类其实也是一个跟权限管理功能类似的约束应用操作行为的类,它早在Android 4.4就被加入,用它其实可以实现类似权限动态管理的功能(其实Google也做了,只是Release版本从来都屏蔽了),到目前为止他的大部分接口仍然hide。该类的注释中也提到该类一般不用于第三方应用开发者。很多手机厂商在Android 6.0之前实现的权限管理应该就是基于这套逻辑做的。AppOpsManager的初衷应该是想动态约束应用的操作行为,但又不想把这种动态约束应用操作行为的能力public。可该来的还是要来,在Android 6.0动态权限管理全面铺开后,AppOpsManager的作用就略微显得有些尴尬了。
结束语
文章很多地方是将结论直接写出来,目的是尽量简单直接的表明Android权限控制的思想。要究其原理细节可以参阅源码。理解了权限管理的大致思想,在查看源码时能助你一臂之力。
如有理解有误的地方欢迎指出并改正。谢谢!
参考
http://blog.csdn.net/u013553529/article/details/53167072
https://developer.android.com/guide/topics/permissions/requesting.html
https://stackoverflow.com/questions/4568267/android-min-sdk-version-vs-target-sdk-version
https://segmentfault.com/a/1190000007943075?from=timeline