Android6.0带来了新的权限管理方式,本文主要来源于官方文档,加入了自己的理解,目的是想总结Android6.0权限管理的新方式,其他部分可能主要是总结式的带过,后续再详细分析。
一.Security Architecture(安全体系结构)
Android安全体系结构的核心是:
默认情况下没有任何应用有权限去执行对其他应用、操作系统、用户有不利影响的操作。这是一个核心的设计理念。记住这句话对后面的权限管理可以很好的理解。
正式由于这样的设计理念,默认情况下应用不能去读写用户的私有数据(比如Email和Contacts),不能去读写其他App的文件,不能执行网络访问,不能保持设备始终唤醒等等。
因为每一个Android 应用都是在一个进程沙盒中运行的,应用必须明确分享的资源和数据,通过申明需要的额外权限这种方式(这些额外权限不由基本沙河提供)。应用静态的声明这些权限(在Manifest里面),然后Android系统会请求用户同意这些权限。
Android应用沙盒不依赖于创建应用的技术(这句话不是很懂,懂的大神可以指 一下),特别的是Dalvik虚拟机并不是安全边界的,所有的应用都可以运行native code(比如参见NDK),所有类型的应用-Java,native,hybird,都是以同样的方式封装在沙盒内并且相互之间是同样的安全等级。
二.Application signing(应用签名)
所有的Apk文件都必须由他的开发者使用私有的签名证书签名,这个证书是开发者身份的唯一标识,这个证书是由开发者自己生成的开放式的证书,用于自己签名应用。证书的目的是标识应用的身份,这样可以让系统知道是该允许还是拒绝应用访问签名级别的权限(signature-level
permissions),以及允许还是拒绝应用所请求的给予相同Linux身份来作为不同的应用。
三.User IDs and File Access(用户ID和文件访问)
在安装一个app package的时候,android系统会给每一个package一个独立的Linux user ID。这个User ID在这个应用在当前设备的生命周期内都是固定不变的,在不同的设备,相同的package的用户ID可能各不相同,但可以确定的是在一台设备上一个 package的用户ID是固定不变的。
因为安全执行是发生在进程层面的,两个不同的package不能运行在相同的进程中,他们会被作为不同的Linux用户来运行。
但是你可以在manifest中使用sharedUserId属性来指定不同的package有相同的User ID,这样这两个不同的package将会被视为相同的APP,会有相同的User
ID和文件权限。
当然为了保证安全,只有两个APP签名一致且申明了相同的sharedUserId才会被给予相同的User ID。
四.Using Permission(使用权限)
一个新建的Android应用默认是没有权限的,这意味着它不能执行任何可能对用户体验有不利影响的操作或者访问设备数据。为了使用受保护的功能,你必须包含一个或者多个<uses-permission>标签在你的app manifest中。
Android 6.0中权限分为两种,普通权限和危险权限(即运行时权限,下面统称运行时权限)。
4.1普通权限
如果你的应用manifest中只申明了普通权限(也就是说,这些权限对于用户隐私和设备操作不会造成太多危险),系统会自动授予这些权限。
4.2运行时权限
如果你的应用manifest中声明了运行时权限(也就是说,这些权限可能会影响用户隐私和设备的普通操作),系统会明确的让用户决定是否授予这些权限。系统请求用户授予这些权限的方式是由当前应用运行的系统版本来决定的。
4.2.1 Android6.0及以上的系统
如果你的设备运行的是Android6.0(API level 23)及以上的系统,并且你的应用的targetSdkVersion也是23或者更高,那么应用向用户请求这些权限是实时的。这意味着用户可以随时取消 这些运行时权限的授权。所以应用在每次需要用到这些运行时权限的时候都需要去检查是否还有这些权限的授权。具体请求方式后面会讲到。
4.2.2 Android 5.1及以下的系统
如果你的设备运行在Android5.1(API level 22)及以下的系统中,或者你的app的targetSdkVersion是22或者更低。系统会请求用户在apk安装的时候授予这些权限。
如果你的应用更新的时候添加了一个权限,系统会在用户更新应用的时候请求用户授予这个权限,一旦用户安装了这个应用,唯一可以取消授权的方式就是卸载掉这个应用。注意这句话的意思,想一下如果app的targetSdkVersion是22或者以下,但是运行在Android6.0及以上的设备中会有什么问题?后面会分析这个问题。
常常来说一个授权失败会抛出SecurityException,然而这并不是在 所有情况下都会发生。比如,发送一个广播去检查授权(SendBroadcast(Intent)),数据会被发送给所有接收者,但是当这个方法的请求返 回的时候,你不会收到任何一个因为授权失败抛出的异常,其实在大多数情况下,授权失败只会打印系统日志。
任何应用也可以定义和执行他们自己的权限。
4.3自动权限调整
简单的说,如果你的app targetSdkVersion是3,而你当前运行的系统版本是4,那么在android version 4 中新添加的权限会自动添加到你的app中。
所以建议经常更新你的targetSdkVersion到最新版本。
下面来回答上面的那个问题,如果app的targetSdkVersion是22或者以下,但是运行在android 6.0或以上版本的手机中,会发生什么?
安装过程中,会一起请求用户授予所有 权限,如果用户拒绝,將不能安装这个app,只有用户全部同意这些授权,才能安装这个应用,但是问题来了,安装好了这个应用之后,android6.0以 上的系统中,用户是可以去设置中取消授权的,而且是随时都可以取消,所以很多运行时权限可能也得不到,目前官方的做法是,如果用户取消该项授权,那么依赖 该项授权的方法的返回值为null,所以你的app可能会报空指针异常。以后是否会针对22以下的app做改变还不得而知,毕竟crash是很难让人接受
的,但是crash是由用户造成的,用户应该也可以理解。
五.普通权限和运行时权限
系统权限会被传递给两种不同的保护级别,我们所知道这两种最重要的保护级别就是普通权限和运行时权限。
5.1普通权限
普通权限的覆盖区域是在你的app需要访问沙盒以外的数据和资源的时候,但是对用户隐私和其他app的操作只有很少的影响,比如开启手电筒的权限。这个时候,系统会自动授权这些普通权限。
5.2运行时权限
运行时权限的覆盖区域是你的app想要的数据和资源涉及用户的隐私信息,或者是可能潜在的影响用户的存储数据或者其他app的操作。比如,请求获取用户联系人信息的权限。如果一个app申明了运行时权限,用户必须明确的授权这些权限给app。
5.3权限组
所有的运行时权限都属于对应的权限组,如果你的app运行在android6.0及以上系统,下面的规则都适用:
5.3.1
如果一个app在manifest中请求了一个运行时权限,而且app还没有得到这个运行时权限所在的权限组中的任何一个运行时权限授权,那么系统会弹出一个对话框,描述app想要访问的运行时权限的权限组,这个对话框不会描述这个权限组中某一个特定的权限。
比如,你的app想要请求READ_CONTACT权限,对话框只会描述app想要请求设备的联系人,如果用户授权通过,系统会授予app所请求的该项权限。
5.3.2
如果一个app在manifest中请求一个运行时权限,并且这个app已经在相同的权限组中有了另一个运行时权限的授权,那么系统不会弹出对话框,而是会立即授予app该项运行时权限的授权。
比如,一个app之前请求过一个READ_CONTACT的权限并且被授权通过,之后又请求一个WRITE_CONTACT(在同一个权限组)权限,那么系统会自动授予该权限。
其实所有权限(普通权限、运行时权限、用户自定义权限)都属于特定的权限组,但是只有运行时权限的权限组才会影响用户体验。
5.3.3运行时权限和权限组列表
Permission Group | Permissions |
---|---|
CALENDAR | |
CAMERA | |
CONTACTS | |
LOCATION | |
MICROPHONE | |
PHONE | |
SENSORS | |
SMS | |
STORAGE |
有两个权限比较特殊,分别是SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS
都属于比较敏感的权限,很多时候都永不到,如果需要这个权限,应该首先声明,然后发送Intent去请求用户授权,然后系统会显示详细的管理界面。
5.3.4运行时权限的申请
第一.判断
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
第二.如果是android6.0以上的系统,则检查是否获取授权
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);
如果返回值为PackageManager.PERMISSION_GRANTED,则可以继续之后的操作,如果返回值为PERMISSION_DENIED,则代表没有授权该权限。
第三.shouldShowRequestPermissionRationale()可以得到是否需要弹出一个解释申请该权限的提示给用户,如果为true,则可以弹。
第四.请求该权限
示例如下:
// 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. } } else { 执行获取权限后的操作 }
第五.请求权限之后,在onRequestPermissionsResult()返回值中可以得到用户是否授权,如果授权,就可以操作该运行时权限对应的方法
@Override public void onRequestPermissionsResult(int requestCode, 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; } // other ‘case‘ lines to check for other // permissions this app might request } }
运行时权限的特点是,实时性,用户可以随时取消授权,所以每次调用运行时权限的方法都需要判断或者请求一次运行时权限。
在执行运行时权限申请的同时想一下是否真的有必要,想一下使用Intent的方式启动其他应用是否可以达到需求,比如ACTION_IMAGE_CAPTURE,是直接申明CAMERA的权限自己做一个照相机还是发送ACTION_IMAGE_CAPTURE请求让别的应用处理并在onActivityResult()返回值更方便
如果设备运行在5.1或者以下的设备,或者targetSdkVersion在22或以下,系统会在安装app的时候让用户授权权限。再说一遍,系统只会提示用户app需要的权限组,而不会提示某一个特定的权限。
六.自定义权限
为了执行自定义权限,你必须在你的manifest中声明一个或多个<permission>标签。
比如:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.me.app.myapp" > <permission android:name="com.me.app.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>
<protectionLevel>属性是必须的,告诉系统当app申请该权限的时候,要怎样通知用户。
<permissionGroup>属性是可选的,可以帮助系统显示自定义属性属于哪个权限组,当通知用户弹出框的时候,当然你可以选择某一个自定义权限属于已知的权限组,也可以属于某一个自定义权限组,建议属于已知的权限组。
<android:label>相当于权限组的提示,要简短
<android:description>是某一个特定权限的描述,规则是两句话,第一句描述,第二句警告用户如果授权会发生什么后果。
比如,CALL_PHONE权限
<string name="permlab_callPhone">directly call phone numbers</string> <string name="permdesc_callPhone">Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers.</string>
之后,你会在setting—>Application中看到该自定义权限。