- 一.权限概述
- 二.权限的分类
- 三.权限保护水平
- 四.权限分组
- 五.自动权限调整
- 六.自定义权限
- 七.Android 6.0动态申请权限
- 八.API版本与targetSDKVersion关系
- 九.相关问题和资料
一.权限概述
1.Android 权限的含义及安全架构
1)Android 是一个权限分隔的操作系统,其中每个应用都有其独特的系统标识。
系统各部分也分隔为不同的标识,Linux 据此将不同的应用以及应用与系统分隔开来。
2)在默认情况下任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读取或写入用户的私有数据(例如联系人或电子邮件)、
读取或写入其他应用的文件、执行网络访问、使设备保持唤醒状态等。
由于每个 Android 应用都是在进程沙盒中运行,因此应用必须显式共享资源和数据,即声明需要哪些权限来获取基本沙盒未提供的额外功能
2.权限许可
1)Android应用可能需要请求访问敏感用户数据(如联系人和短信)以及某些系统功能(如相机和互联网)的权限。
根据功能,系统可能会自动授予权限,或者可能会提示用户批准请求。
2)应用必须通过 <uses-permission>在manifest中明确标记所需的权限。例如需要发送SMS消息的应用在manifest中应包含以下行:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.应用.my应用" > <uses-permission android:name="android.permission.RECEIVE_SMS" /> ... </manifest>
如果在manifest中列出了正常权限(即不会对用户的隐私或设备操作造成太大风险的权限),系统会自动将这些权限授予您的应用。
如果在manifest中列出了危险权限(即可能影响用户隐私或设备正常运行的权限),例如SEND_SMS权限,则用户必须明确同意授予这些权限。
二.权限的分类
1.可选硬件功能的权限
访问某些硬件功能(如蓝牙或相机)需要应用权限。但是并非所有设备都具有这些硬件功能。因此如果应用请求 CAMERA
权限,
那么还需要在manifest中使用<uses-feature> 声明是否确实需要此功能。例如:
<uses-feature android:name="android.hardware.camera" android:required="false" />
1)如果您声明android:required="false"
该功能,则Google Play允许你的应用安装在没有此功能的设备上。
然后你必须检查当前设备在运行时是否具有该功能 PackageManager.hasSystemFeature()
,并在该功能不可用时正常禁用该功能。
2)如果未提供 <uses-feature>声明
,那么当Google Play看到应用请求相应的权限时,它会认为应用需要此功能。因此它会从没有
该功能的设备中过滤该应用,就像android:required="true"
在 <uses-feature>标记中声明一样 。
有关详细信息,请参阅 Google Play和基于功能的过滤
2.服务许可执行
权限不仅适用于请求系统功能。应用提供的服务可以强制执行自定义权限,以限制谁可以使用它们。
有关声明自定义权限的详细信息,请参阅定义自定义应用权限。
1)activity权限执行
在manifest中的activity标签使用android:permission
属性来限制谁可以启动它。在执行Context.startActivity()
和 Activity.startActivityForResult()的时候如果没有需要的权限会抛出
SecurityException异常;
2)service权限执行
在manifest中的service标签使用android:permission
属性来限制谁可以启动或绑定它。在执行Context.startService()、
Context.stopService()
和
Context.bindService()
时检查权限,如果没有需要的权限会抛出
SecurityException异常;
3)广播权限执行
sendBroadcast(new Intent("com.example.NOTIFY"),Manifest.permission.SEND_SMS);
要接收这个广播, 接收方必须在manifest中注册权限
<uses-permission android:name="android.permission.SEND_SMS"/>
在receiver标签中使用android:permission
属性来限制谁可以发送广播到关联的BroadcastReceiver。系统尝试将提交的广播
传递给给定的接收者,因此权限检查失败不会抛异常给调用者; 它只是不响应这个intent。(这是个例外)
<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
或者在代码中注册接收器Context.registerReceiver()也可以使用权限来限制
谁可以向reveiver发送广播。
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
注意: receiver and a broadcaster 都可以设置需要某权限才可以访问。这时就必须双方权限检查都通过才能将intent传到关联方。
有关更多信息,请参阅 限制具有权限的广播。
4)Content Provider权限执行
在 <provider>标签中
使用android:permission
属性来限制谁可以访问 ContentProvider中的
数据 。
(ContentProvider有一个重要的额外安全设施叫做 URI权限,下面将对此进行描述)
ContentProvider
与其他组件不同,可以设置两个单独的权限属性:android:readPermission 限制谁可以从provider读取数据,
android:writePermission 限制谁可以写入数据。
注意:如果provider同时受读取和写入权限保护,则仅有写入权限并不可以从provider读取数据。
检索provider并且在provider上执行操作时将检查权限(如果没有权限则抛出SecurityException
异常)。使用 ContentResolver.query()
需要有读取权限;
使用 ContentResolver.insert()
,ContentResolver.update()
, ContentResolver.delete()
需要写权限。
在所有这些情况下,未获得所需权限会导致SecurityException异常
抛出。
5)URI权限执行
标准的权限系统对于provider来说是不够的。一个provider可能想保护它的读写权限,但与其直接关联的客户端也需要将特定的URI传递给其它应用,
以便其它应用能对该URI进行操作。
例如邮件应用的附件,因为邮件中是敏感的用户数据,所以需要用permission来保护。但是如果有一个指向图片附件的URI需要传递给图片浏览器,
而这个图片浏览器是不会有访问附件的权利的,因为它不可能有邮件的所有权限的。这个问题的解决方案就是per-URI permission:
当启动一个activity或者给一个activity返回结果的时候,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。
这就赋予了接收activity访问该Intent指定的URI的权限,而不用关系它是否有权限进入该intent 对应的 provider。
这种机制是实现减少应用所需要的权限而只留下和应用行为直接相关的权限的关键一步,这种用户交互(如打开一个附件、从列表中选择一个联系人)就可以采用这种细微权限的授与。
但是这些URI权限的获取需要持有这些URL的provider来配合,所以建议在provider中实现这种功能,并通过 android:grantUriPermissions
或者<grant- uri-permissions>标签来声明支持。
Uri uri = FileProvider .getUriForFile(mContext, BuildConfig.APP_LICATION_ID + ".provider", file);
mContext .grantUriPermission(BuildConfig.APP_LICATION_ID , uri , Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
更多的信息可以参考 Context.grantUriPermission()
, Context.revokeUriPermission()
函数。
6)其他权限检查
对其它应用进程的调用都可以强制执行任意细粒度的权限。在执行对其他进程的调用时通过Context.checkCallingPermission()
方法来检查是否已将该权限授予当前调用进程。
如果您具有另一个进程的进程ID(PID),则可以使用该Context.checkPermission()
方法检查针对该PID的权限。
如果您拥有其他应用的包名,则可使用PackageManager.checkPermission()
方法检查该特定程序包是否已被授予特定权限。
三.权限保护水平
权限分为几个保护级别。保护级别会影响是否需要运行时权限请求。
有三种保护级别会影响第三方应用: 正常,签名和危险权限。
1.正常权限
普通权限是涵盖应用需要访问应用沙盒外部数据或资源的区域,但用户隐私或其他应用操作的风险很小的权限。例如,设置时区的权限是正常权限。
如果应用在其清单中声明它需要正常权限,则系统会在安装时自动授予应用该权限。系统不会提示用户授予正常权限,用户也无法撤消这些权限。
2.签名权限
所有 发布到应用市场的APK(.apk
文件)都必须使用证书签署,证书用于识别应用的作者;这就允许系统决定应用是否可以有签名级权限的访问权及
3.用户ID和文件访问权限
在安装时系统为每个程序包提供不同的Linux用户ID。在程序包的生命周期内,该标识保持不变。在不同的设备上,同一个包可能有不同的UID;
重要的是每个包在给定设备上具有不同的UID。
正常情况下任何两个软件包需要作为不同的Linux用户运行,所以其代码不能在同一进程中运行。但是可以通过在
每个包的manifest中的 sharedUserId属性为它们分配相同的用户ID,通过这样做,两个包就被视为具有相同用户ID和文件权限的应用了。
注意:为了保持安全性,只有两个应用同时满足使用相同签名和相同的sharedUserId时被赋予相同的用户ID。
应用存储的任何数据都将分配该应用的用户ID,而其他软件包通常无法访问。
有关Android安全模型的更多信息,请参阅Android安全概述
4.危险的权限
危险权限包括应用需要涉及用户私人信息的数据或资源的区域,或可能会影响用户存储的数据或其他应用的操作。
例如,读取用户联系人的权限是一种危险的权限。如果应用声明它需要危险权限,则用户必须明确授予该应用的权限。
在用户批准该权限之前,您的应用无法提供依赖该权限的功能。
危险权限授予的方式在Android 6.0系统版本前后发生了巨大的变化,后面详细介绍。
5.特殊权限
有一些权限不像正常和危险的权限,在Android 6.0中增加了特殊的权限授予处理方式。像SYSTEM_ALERT_WINDOW和
WRITE_SETTINGS这些权限
特别敏感,
所以一般应用不应该使用它们。如果应用需要其中某个权限,则必须在manifest中声明权限,并且发送请求用户授权的intent。系统通过向用户显示详细的管理屏幕来响应intent。
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
在onActivityResult中通过Settings.System.canWrite()方法判断WRITE_SETTINGS权限是否授权,有关如何请求这些权限的详细信息,请参阅参考SYSTEM_ALERT_WINDOW
和 WRITE_SETTINGS
参考条目。
四.权限分组
在Android中会把权限分类在不同的权限组中,权限请求是在组级别处理。例如SMS组包括声明READ_SMS、
SEND_SMS和 RECEIVE_SMS
声明,
这样的方式方便用户对权限的理解和作出选择。
无论权限的保护级别如何,所有的权限都属于权限组。但是只有危险权限会影响用户体验。
1.正常权限
正常权限在应用需要使用的时候只要在manifest中声明系统就会自动授予应用相应权限,而不需要与用户交互和得到用户的许可。
2.危险权限
1)从Android 6.0开始系统加入了动态权限管理机制。如果设备系统是>=6.0且应用的targetSdkVersion>=
23时,则当应用请求危险权限时,以下系统行为适用:
- 如果应用当前在权限组中没有任何权限,系统会向用户显示描述应用要访问的权限组的权限请求对话框。该对话框不会描述该组中的特定权限。
例如如果某个应用请求了该READ_CONTACTS
权限,系统对话框只会告诉用户该应用需要访问设备的联系人。如果用户授予批准,系统将为应用提供其请求的权限。
- 如果应用已在同一权限组中被授予另一个危险权限,则系统会立即授予权限,而不与用户进行任何交互。例如,如果某个应用先前已请求并已获得该
READ_CONTACTS
权限,
然后它会请求WRITE_CONTACTS
,则系统会立即授予该权限,而不会向用户显示权限对话框。
警告:未来版本的Android SDK可能会将特定权限从一个组移动到另一个组。因此不要将权限组的结构作为应用判断权限逻辑的依据。
即使用户已在同一组中授予了其他权限,虽然组内其它权限申请的时候会自动授权,但是应用仍要明确请求其所需的每个权限。
2)如果设备运行的系统是<=Android 5.1(API级别22)或者应用 targetSdkVersion是<=
22,则系统会要求用户在安装时授予权限。
系统只告诉用户应用需要哪些权限组。用户接受时,仅授予应用申请的权限。
五.自动权限调整
随着系统版本的更新,可能会不断加入新的限制,从而来限制用户访问新添加的API功能。Android 是根据 targetSdkVersion 属性提供的值来决定
应用是否需要某权限。如果这个值低于该权限添加的版本,则 系统会自动添加该权限。
例如,API 级别 4 中加入了 WRITE_EXTERNAL_STORAGE
权限用以限制访问共享存储空间。如果你的 targetSdkVersion 为 3 或更低版本,
则系统会向Android 1.6及以上版本设备上的应用添加此权限。
注意:如果某权限自动添加到应用,则即使应用可能并不需要这些附加权限,Google Play 上的应用列表也会列出这些权限。
为避免这种情况,建议始终将 targetSdkVersion 更新至最高版本。可在 Build.VERSION_CODES
文档中查看各版本添加的权限。
六.自定义权限
要定义自己的权限,必须先使用一个或多个 <permission> 元素在 AndroidManifest
中声明它们。
例如,想要控制谁可以启动应用的某个Activity可如下所示声明此操作的权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp" > <permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" /> ... </manifest>
1)android:name:系统不允许多个应用使用同一个名称声明权限,如果使用了跟其它应用相同的权限名称会被系统禁止安装,
为避免命名冲突,建议对自定义权限使用包名➕权限名称的命名方式,例如 com.example.myapp.ENGAGE_HYPERSPACE
。
2)protectionLevel:
是必要属性,用于设置权限的保护级别,系统就知道怎样告知用户应用需要的权限,或者谁可以拥有该权限。
3)android:permissionGroup :是可选属性,用于帮助系统向用户显示权限。一般情况下,建议设为标准系统组( android.Manifest.permission_group
),
因为可简化向用户显示的权限 UI,但也可以自定义一个组。
4)android:label:权限标签。用户在查看权限列表时可以看到的字符串资源。标签应简短,用几个词描述权限保护的功能的关键部分。
5)android:description:权限描述。用户在查看单一权限详细信息时可以看到的字符串资源。description应该用几个句子描述权限允许持有人执行的操作。
一般是两句,第一句描述权限,第二句告诉用户授予权限之后可能会带来的不好的后果;
查看系统的当前定义的权限的shell 命令:adb shell pm list permissions
七.Android 6.0动态申请权限
前面提到在Android 6.0开始系统会在应用运行的各个阶段和场景中对使用危险权限的授予进行检查,如果没有相应权限会造成应用崩溃。
所以API 从6.0开始提供给开发者动态处理权限的接口,下面主要介绍如何在运行时动态处理危险权限。
1.检查权限
在应用中需要授予危险权限才能使用相应功能的地方通过调用ActivityCompat.checkSelfPermission方法来检查权限是否授予,如果返回0
(PackageManager.PERMISSION_GRANTED)说明已经有对应权限,如果返回是-1(PackageManager.PERMISSION_DENIED)则说明
权限没被授予。如果是检查一组权限,则必须全部都被授予才可以继续后续操作,否则要一直请求权限授权;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS)!= PackageManager.PERMISSION_GRANTED) { // Contacts permissions have not been granted. requestContactsPermissions(); } else { // Contact permissions have been granted. Show the contacts fragment. }
2.请求权限授权
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.READ_CONTACTS) || ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_CONTACTS) || ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.GET_ACCOUNTS)) { // Display a SnackBar with an explanation and a button to trigger the request. Snackbar.make(mLayout, R.string.permission_contacts_rationale, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.ok, new View.OnClickListener() { @Override public void onClick(View view) { ActivityCompat.requestPermissions(MainActivity.this, PERMISSION_CONTACTS,REQUEST_CONTACTS); } }.show(); } else { ActivityCompat.requestPermissions(this, PERMISSION_CONTACTS, REQUEST_CONTACTS); }
1)如果是第一次请求权限直接调用ActivityCompat.requestPermissions()方法,系统弹出请求用户授权的弹框(如下图)。activity要实现
ActivityCompat.OnRequestPermissionsResultCallback接口, 重写onRequestPermissionsResult()方法,则用户授予权限与否的结果就会
回调到这个方法,可以在回调中处理后续逻辑;
2)当用户第一次拒绝了授予权限之后,后面再请求该权限之前需要调用ActivityCompat.shouldShowRequestPermissionRationale()方法来判断
用户是否拒绝过权限的授权,如果是则返回true,这样可以允许应用在此时给用户一些申请该权限的解释说明(当然也可以不做)。这时再调用
ActivityCompat.requestPermissions()申请权限时,系统弹请求用户授予权限的弹框出现了变化(如下图)。
3)如果在上一步的弹框继续拒绝授予权限,则下次申请还是弹出第二步同样的弹框。但是如果用户拒绝之前同时勾选了”不在询问“选择框的话,后面再
请求权限时就会直接调用ActivityCompat.requestPermissions(),因为此时ActivityCompat.shouldShowRequestPermissionRationale()返回的是false,
并且系统不会弹出任何与用户交互的弹框,而直接返回权限授予失败给回调接口。
注意:由于此时的ActivityCompat.shouldShowRequestPermissionRationale()是false 就可以在onRequestPermissionsResult()回调中的权限授予失败
中区分出用户是否是勾选了” 不再询问 “,如果是这种情况的话开发者可以尝试给用户一些解释或者引导用户去设置页面开启该权限;
3.处理低配设备
在Android 6.0以下系统的设备上,不管targetSDKVersion是否大于23,只要是在manifest中注册的危险权限在应用安装的时候系统都会全部直接展示给用户授权,
只有全部授权才可以继续安装应用,一旦安装成功了用户就没法更改危险权限的授予情况了(第三方权限管理工具除外)。
八.API版本与targetSDKVersion关系
1.Normal Permission
写在xml文件里,那么应用安装时就会默认获得这些权限,即使是在Android6.0系统的手机上,
用户也无法在安装后动态取消这些normal权限,这和以前的权限系统是一样的。
2.Dangerous Permission
还是得写在xml文件里,但是应用安装时具体如果执行授权分以下几种情况:
targetSdkVersion/手机系统 |
API >= 6.0 |
API < 6.0 |
---|---|---|
targetSDKVersion >= 23 | 安装时不会获得权限,可以在运行时向用户申请权限。 用户授权以后仍然可以在设置界面中取消授权,用户主动在设置界面取消后,在应用运行过程中可能会出现crash |
安装时默认获得权限,且用户无法在安装应用之后取消权限 |
targetSDKVersion < 23 | 安装时默认获得权限,但是用户可以在安装应用完成后动态取消授权,但应用不能动态申请( 取消时手机会弹出提醒,告诉用户这个是为旧版手机打造的应用,让用户谨慎操作 ) | 安装时默认获得权限,且用户无法在安装应用之后取消权限 |
3.兼容性处理
targetsdkversion < 23 该怎么判断有没有权限呢?
如图就是API>=6.0D但targetSDKVersion<23时用户在设置中取消权限的提示:
ActivityCompat.requestPermissions
肯定是不行的,这是23以后提供的方法;
google在support V4里面提供了解决方案:
int permission = PermissionChecker.checkSelfPermission(context,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
permission == PermissionChecker.PERMISSION_GRANTED;
通过PermissionChecker
就可以判断有无权限了
注意:如果应用在使用途中用户取消权限,没有通知!也没有广播!
只能自己在Activity:onrestart
判断有无权限
九.相关问题和资料
1.对于正在使用某一个权限,但是突然用户手动关闭权限的处理(onResume)
2.第三方库中的危险权限处理(尽量不用请求过多危险权限的第三方库,需要在使用的时候判断权限)
3.对于android 8.0, 9.0权限设定的处理(主要是权限组的变更)
4.Android动态权限管理框架
6.0动态权限管理demo:AndroidRuntimePermissions-master.zip
Android系统提供的所有权限:Manifest.permission