Android的权限机制是用于保护用户的隐式信息。APP访问敏感用户数据(如联系人和SMS)或某些系统功能(如Camera和Internet)时,必须请求用户授予对应的权限。根据被访问的特性,系统会自动授予权限或请求用户授予权限。本文将对Android权限是怎样工作的进行概述,包括:权限请求提示框是怎样展示给用户的、“安装时请求权限”与“运行时请求权限”的区别、权限是如何执行的、权限的类型及分组。
权限授予
APP需要在Manifest文件中用<uses-permission>
显示申请所需的权限。例如,APP需要发送SMS,那么需要在manifest中做如下申请:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
**<uses-permission android:name="android.permission.SEND_SMS"/>**
<application ...>
...
</application>
</manifest>
对于APP在Manifest中申请的普通权限(不会给用户隐私和设备操作带来风险的权限),系统会自动授予权限给APP;对于APP在Manifest中申请的危险权限(会给用户隐私和设备操作带来风险的权限),如SEND_SMS权限,用户必须显式授予权限给APP。关于普通权限和危险权限的详细信息,请见“安全等级”。
请求授予危险权限
只有危险权限才需要用户的授予。Android系统请求用户授予危险权限的方法与用户设备上运行的Android版本和APP的Target Version有关。
运行时请求权限(Android 6.0及以上)
如果Android系统版本为Android 6.0(API 23)及以上,APP的targetSdkVersion是23及以上,那么在安装APP时不提示用户授予权限,而在APP运行时提示用户授予危险权限。系统会向用户展示一个提示框(如图1左边所示),提示用户APP当前正在请求哪个权限。对话框包括一个Deny按钮和一个Allow按钮。
如果用户不允许授予此权限,那么APP下次请求这个权限时,弹出的提示框上会多一个单选框。如果用户勾选提示框中的“Never ask again”,那么APP下次再请求同一个权限时,系统不会再提示用户授予权限。(如图1右边所示)。
图1:左边为首次弹出的权限提示框,右边为第二次弹出的权限提示框
即使用户授予了APP请求的权限,用户仍然可以在设置中禁止或开启此权限,所以APP需在运行时检查和请求权限,以防止运行时出现SecurityException。
安装时请求权限(Android 5.1.1及以下)
如果Android系统版本为Android 5.1.1(API 22)及以下,或者APP的targetSdkVersion是22及以下,那么系统在安装APP时提示用户授予危险权限(如图2)。
图2:安装时展示权限提示框
如果用户点击Accept,那么将授予APP请求的所有权限;如果用户不同意授权,那么系统取消安装此APP。如果更新后的APP需要请求新的权限,那么需要在更新APP前先让用户先授权这个新的权限。
硬件特性的权限
APP访问某些硬件特性(如Bluetooth和Camera)时也需要请求权限,但不是所有Android设备都有这些特性。所以,如果APP请求CAMERA 权限,应该在Manifest文件中用 申明是否需要使用此硬件特性,如:
<uses-feature android:name="android.hardware.camera" android:required="false" />
如果把一个特性设置为android:required=”false” ,那么Google Play允许APP安装到一个不支持此特性的设备上,且APP必须在运行时调用 PackageManager.hasSystemFeature()来检查当前设备是否支持此特性,如果设备不支持此特性就优雅的屏蔽对应的功能。
如果申请了访问此特性的权限,但是不提供 ,系统就会认为APP需要使用此特性,即相当于在中设置android:required=”true”。Google Play就会认为APP需要使用这个特性,那么它不允许APP安装到不支持这个特性的设备上。更多信息,可参考 Google Play and feature-based filtering
权限执行
权限不仅用于限制对系统功能的请求,而且用于限制Service、Activity、Broadcast等的请求。
Activity的权限执行
在Manifest文件中,给 tag设置android:permission的属性,用以限制谁可以启动此Activity。在 Context.startActivity()和Activity.startActivityForResult()时会检查这个权限。如果调用者没有这个权限,那么会抛出SecurityException异常。
Service的权限执行
在Manifest文件中,给 tag设置android:permission的属性,用以限制谁可以start或bind此Service。在 Context.startService()、 Context.stopService()和Context.bindService()时会检查这个权限。如果调用者没有这个权限,那么会抛出SecurityException异常。
Broadcast的权限执行
在Manifest文件中,给 tag设置android:permission的属性,用以限制谁可以向这个静态注册的BroadcastReceiver发送广播。在 Context.sendBroadcast() return后会检查这个权限。如果调用者没有这个权限,那么不会抛出异常,而是系统不把广播发送给对应的BroadcastReceiver。
类似的方法,在调用Context.registerReceiver()时设置权限,用以限制谁可以向这个动态注册的BroadcastReceiver发送广播。相反的,可以在调用Context.sendBroadcast()时设置权限,用以限制谁可以接受这个Broadcast发送的广播。
注意,当receiver和broadcaster都设置了权限时,两边的权限都检查通过了,才能成功发送和接收广播。
Content Provider的权限执行
在Manifest文件中,给 tag设置android:permission的属性,用以限制谁可以访问这个 ContentProvider中的数据。与其它组件不同,你可以给 ContentProvider分别设置两个权限: android:readPermission用于限制谁可以从 这个ContentProvider读取数据,android:writePermission用于限制谁可以向这个ContentProvider写入数据。如果一个ContentProvider同时设置了android:readPermission和android:writePermission,那么一个只有写权限的APP不能从这个ContentProvider读数据。
当APP第一次检索或操作这个ContentProvider时,系统会检查APP是否有对应权限,如果没有,就会抛出SecurityException异常。调用ContentResolver.query()时需要读权限,调用 ContentResolver.insert()、ContentResolver.update()、ContentResolver.delete()时需要写权限。
URI的权限执行
ContentProvider不仅用上述的android:readPermission、android:writePermission,还用URI permissions来限制谁可以读写这个ContentProvider。例如访问email需要权限控制,如果一个image附件的URI给了一个查看者,但是这个查看者没有查看所有email的权限,所以他无法查看这个附件。为了解决这个问题,可以用一次性授权的方式:当startActivity时,给Intent设置Intent.FLAG_GRANT_READ_URI_PERMISSION 或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION标志。
其它权限执行
如果APP在处理来自其他进程的调用时,可以使用 Context.checkCallingPermission()、Context.checkPermission() 、PackageManager.checkPermission() 来检查其它进程是否有对应的权限。
自动适配权限
随着时间的推移,新的权限可能会添加到平台上,为了使用某些API,APP必须请求它以前不需要的权限。由于现有的APP假定是可以不用权限就可以访问这些API的,Android就把新的权限请求适配到应用程序的Manifest中,以避免破坏新平台版本的app。Android根据APP的 targetSdkVersion值判断APP是否需要这个权限。例如:READ_EXTERNAL_STORAGE权限是在API level 19才开始添加的,如果APP的targetSdkVersion值为18或更低,那么在新的Android版本上这个权限将被添加到APP
权限等级
权限被分为几个等级,系统更具权限的等级来决定是否需要在运行时请求权限。所有的权限请见Manifest.permission。
一般权限
一般权限覆盖的范围为:APP需要访问其它进程内的数据和资源,但是对用户隐私和对其它APP的操作不会带来风险。例如,设置时区的权限就是一般权限。
如果APP在Manifest中申明了要使用某个一般权限,那么系统会在安装这个APP时自动授予这个权限。系统不会让用户来授予一般权限,用户也不能撤销一般权限。
危险权限
危险权限的覆盖范围为:APP需要访问的数据和资源涉及用户隐私,或能影响到用户的存储数据,或能影响到其它APP的操作。例如,读取联系人的权限就是危险权限。如果APP申请使用危险权限,那么需要用户在APP运行时显式授予权限。
特殊权限
有几个权限与一般权限和危险权限的表现形式不同,如 SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS特别敏感,所以极少的APP会使用它们。如果APP需要使用这些权限,必须在Manifest文件中申明权限,且发送一个Intent请求用户的授权,系统就会向用户展示一个详细的权限管理界面。
权限组
根据设备的功能和特性,权限被分为权限组。系统以权限组的形式来处理权限请求,一个权限组可能对应Manifest中申请的几个权限。例如,SMS权限组包括READ_SMS 和RECEIVE_SMS权限。权限组的方式让用户更容易理解权限,和更方便处理APP的权限请求,防止过多的复杂的授予单独的权限。
所有的权限,不管它是什么权限等级,都属于某一个权限组。只有当权限为危险权限时,权限组才影响到用户体验。
如果设备运行的系统为Android 6.0 (API level 23),APP的 targetSdkVersion为23或更高,当APP请求危险权限时,系统的表现如下:
1、如果当前APP没有获得过权限组中的任何权限,APP请求权限组中的权限时,系统会向用户展示APP请问访问权限组的提示框。提示框不会单独描述权限组中的某一个权限。例如APP请求访问READ_CONTACTS权限,提示框只描述APP需要访问设备的联系人。如果用户授予APP这个权限,那么APP只获得这个权限。
2、如果当前APP获得过权限组中的某个权限,APP请求权限组中的其它权限时,系统会立刻授予APP这个权限,而不需要与用户做任何交互。例如APP之前已经请求并被授予READ_CONTACTS权限,如果它再请求 WRITE_CONTACTS权限,那么系统会立刻授予APP这个权限,而不需要弹出权限提示框。
如果设备运行的系统为Android 5.1 (API level 22)或更低,或targetSdkVersion为22或更低,系统中安装APP时让用户授予权限。系统只告诉用户APP需要哪些权限组,而不相信展示所需的单个权限。例如,APP请求 READ_CONTACTS权限,那么安装提示框只列出Contacts权限组。如果用户同意授权这个权限,那么APP只获得这个权限。
查看APP的权限
你可以在“设置”APP中,或用shell命令adb shell pm list permissions查看当前系统定义的权限。如果通过“设置”APP查看,进入Settings > Apps,选择要查看的APP,就能看到这个APP使用的权限。对于开发者,key用adb命令查看APP使用的权限,例如:
$ adb shell pm list permissions -s
All Permissions:
Network communication: view Wi-Fi state, create Bluetooth connections, full
internet access, view network state
Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location
Services that cost you money: send SMS messages, directly call phone numbers
...
你也可以在安装APP时用adb -g命令自动授予APP所有权限,例如:
$ adb shell install -g MyApp.apk