前言
从Android6.0开始,Android系统对权限的处理产生了很大的变化。如果APP运行的设备系统版本为Android6.0或更高,并且target在23或更高,那么dangerious级别的权限将由之前的安装时授予变成运行时动态申请。这样一来,当运用到系统权限相关的功能时,就需要手动处理好权限申请的用户交互问题。本文将结合官网中的介绍,来全方位了解权限相关知识点,并介绍一些实际工作中可能用到的技巧。
本文的主要内容如下:
一、为什么要引入“权限”
Android系统引入权限的目的是为了保护Android用户的隐私。Android安全架构设计中一个明确点,默认情况下App是没有权限对其他App、操作系统和用户进行有害的操作。这些有害操作包括读写用户的隐私数据(如通讯录、电子邮件等)、读写其它App的文件、调起硬件设备(如蓝牙、Wifi、相机等),访问网络等。App如果想要进行这些操作,就需要申请相应的权限,如下网址提供了Android系统定义的所有权限【https://developer.android.google.cn/reference/android/Manifest.permission】,均以常量的形式供开发者使用,开发者可以通过Manifest.permission.X的方式调用。
二、Android权限与官网
官网(中文版:https://developer.android.google.cn)中对Andrid权限有非常全面的描述和使用指导,依次通过 首页 》 文档 》 指南,可以看到如下界面,和权限相关的知识点主要都在这里。咱们先了解一下里面都有些什么内容吧!
1、Overview
宏观上介绍了权限相关联的知识点,整体内容如下图所示:
(1)Permission approval
该部分主要从用户交互角度介绍了当前原声机上申请权限的交互形式。比如对话框显示怎样的内容等。
(2)Permissions for optional hardware feature
这部分主要讲手机硬件特征和与之相关的权限问题。比如手机没有相机时,是否允许安装需要申请相机权限的app等。
(3)Permission enforcement
这部分主要讲Permission在四大组件中的其它用法。Permission不仅仅只能用于请求系统功能,还可以通过自定义权限,来限制谁有权限启动/调用或访问指定的组件/数据等。Activity、Service、Broadcast Receiver和Content Provider这四大组件可以结合Permission这方面的功能,来防止被任意访问或修改。
(4)Automatic permission adjustments
这部分主要讲权限与版本前后兼容的问题。比如在高版本中才定义的新权限,在低版本中如何表现的问题等。
(5)Protection levels
App不同的操作,所可能产生的风险也是不一样的。比如获取当前的网络状态,最多也只会让App探测到周围的网络情况,而读写用户的通讯录则不同,用户的信息则有被篡改和道窃的风险。所以,根据可能产生的风险程度,系统将“权限”分为了不同的保护等级。对于第三方app而言,有三个等级,严重程度有轻到重依次为:普通权限(Normal Permission)、签名权限(Signature Permission)和危险权限(Dangerous Permission)。
1)普通权限
普通权限的覆盖区域为,app需要访问“沙盒”以外的数据以及资源的权限,这些对用户隐私和对其它app的操作产生的风险微乎其微。比如,修改系统时区、获取网络状态等。这部分还列出了一些常用的普通权限.
2)签名权限
系统会在安装app的时候授予该类权限,但是只有当这个试图使用这个权限的app和定义这个权限的app被相同的证书签名的时候,才能生效。有些签名权限就不用于第三方app的。该部分还列出了一些常见的可以供第三方app使用的签名权限。
3)危险权限
危险权限覆盖了如下情形,当用户需要的数据和资源涉及到用户隐私信息,或者可能影响到用户的存储数据或其它app的操作,比如读取用户的联系人的能力就是一个危险权限。该部分还列出了一些常见的危险权限。
4)特殊权限
除了上述的3中主要的权限分类外,还有两个特殊的权限:SYSTEM_ALART_WINDOW和WRITE_SETTINGS。至于其特殊性,咱们在后面的章节单独来介绍。
(6)Permission groups
该部分介绍了权限组相关的知识点。
(7)View an app`s permissions
该部分介绍了两个adb命令 ,一个用于查看app的权限申请情况,另外一个用于给app授予所有的权限。
(8)Additional resources
提供了一些链接,用于了解更多关于Android权限相关的知识。
2、Request app permission
这篇文章主要讲解如何在代码中实现权限检查,权限请求,以及如何处理拒绝/同样授权后的代码逻辑。这里面提供了Android API中的接口,具体的代码示例等。
3、App permissions best practices
这边文章主要是从用于体验的角度来指导开发者,如何设计交互,如何做好测试,以及需要注意的原则等。
4、Define custom permissions
这篇文章主要指导开发者如何自定义权限。
三、特殊权限
上一节中简单提到了特殊权限,其实就是两个权限:SYSTEM_ALART_WINDOW和WRITE_SETTINGS。Android6.0开始,除了危险权限需要动态申请外,这两个特殊权限也是一样需要动态申请。他们尤其敏感,行为也和其他的权限不一样,所以对于大多数app来说一般不应该使用它们。如果app需要其中一个权限,必须在manifest文件中申明,并发送一个intent请求用户授权。系统会显示一个详细的管理界面给用户,让用户决定是否来授权。如下截图是以“微信”为例,在设置中可以看到如下界面,“高级”模块中显示的两项,就是设置这两项权限的入口。
1、WRITE_SETTINGS
在Android6.0及以后,该权限的保护等级已经由原来的dangerious升级为signature,这意味着我们的APP需要用系统签名或者成为系统预装软件才能够申请该权限,并且还需要提示用户跳转到修改系统的设置界面去授予此权限。这就意味着,要想申请该权限,apk必须要签名而且打包,debug模式将无法申请该权限。
(1)官网描述
官网【https://developer.android.google.cn/reference/android/Manifest.permission.html#WRITE_SETTINGS】对该权限的描述如下所示:
(2)权限设置界面
点击“高级”中的“修改系统设置”项,可以进入到该权限的授予界面,如下图所示:
(3)权限申请使用示例
在第四节中将会讲解危险权限检测和申请的具体实例,但是WRITE_SETTINGS权限不能通过这种方式来处理。如果使用checkPermissions()检测该权限,无论你是否已经授权,它都会返回false。如下展示了具体处理该权限的实例:
首先,在AndroidManifest.xml文件中申请该权限
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
然后,根据官网中的描述,先通过API中的canWrite()方法判断是否允许修改,再通过指定的action跳转到设置界面。
/**
* 申请权限
*/
private void requestWriteSettings()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
//大于等于23 请求权限
if ( !Settings.System.canWrite(getApplicationContext()))
{
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
}
}else{
//小于23直接设置
}
}
最后,根据回调方法和requestCode来处理授权/拒绝逻辑。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_WRITE_SETTINGS)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
//Settings.System.canWrite方法检测授权结果
if (Settings.System.canWrite(getApplicationContext()))
{
//获取了权限
}else{
//拒绝了权限
}
}
} }
2、SYSTEM_ALART_WINDOW
(1)官网描述
官网【https://developer.android.google.cn/reference/android/Manifest.permission.html#SYSTEM_ALERT_WINDOW】的描述如下所示:
(2)权限设置界面
点击“高级”中的“显示在其它应用的上层”项,可以进入到该权限的授予界面,如下图所示:
(3)权限申请示例
该权限的申请和WRITE_SETTINGS类似,咱们这里参照前面,就不给出全部代码,仅提供一些关键函数:
在清单文件中声明
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
结合官网中的说明,使用系统提供的API来判断和跳转授权设置界面。
//检查是否已经授予权限
if (!Settings.canDrawOverlays(this)) {
//若未授权则请求权限
}
......
//前往设置界面授予权限
private void getOverlayPermission() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 0);
}
......
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
//根据requestCode来处理授权/拒绝逻辑。
}
从官网上的说明可以看到,这两个特殊权限的权限级别都是“signature”,可见这两个权限并不是独立的权限级别。
四、动态申请权限示例代码记录
如下代码是笔者平时工作当中经常使用的一个实例,方便实用,无需再引入第三方jar包。数组变量permissionRequest中定义了一个相机权限作为实例,读者可以根据需要拓展为多个。一般来说,申请权限是在进入app时第一个Activity中,或者在需要用到该权限的地方进行,所以checkAndRequestPermissions()一般在第一个Activity的onCreate()方法中调用,或者在需要使用该权限的地方调用。其他地方代码逻辑比较简单,就不赘述了。
private String[] permissionRequest = new String[]{
Manifest.permission.CAMERA
};
private boolean isNeedRequestPermission = false;
private static final int REQUEST_PERMISSION = 1;
......
public void checkAndRequestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (String permissionStr : permissionRequest) {
if (ActivityCompat.checkSelfPermission(this, permissionStr) != PackageManager.PERMISSION_GRANTED) {
isNeedRequestPermission = true;
break;
} else {
isNeedRequestPermission = false;
}
}
if (isNeedRequestPermission) {
requestPermissions(permissionRequest, REQUEST_PERMISSION);
} else {
//do what you want
}
}
}
......
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
for (int index = 0; index < grantResults.length; index++) {
if (PackageManager.PERMISSION_DENIED == grantResults[index]) {
finish();
return;
}
}
//do what you want
}
五、一种在Service中申请权限的方案
Android中申请权限的函数requestPermissions()和回调函数onRequestPermissionsResult()都是Acitivity.java中提供的,所以在Activity或Fragment中很容易调用这两方法来处理权限问题。但是在Service中就不容易处理了,因为Service中Android API没有提供类似的接口。而且Android中有不少应用是通过IPC方式,用Service向其它APP提供功能,没有Activity或Fragment,这样就无法直接请求权限了。这里介绍一种采用间接的方式处理权限的方案:需要申请权限时,跳转到一个透明的Activity中来完成权限的申请。
PermissionDemoActivity直接继承自Activity,在清单文件中如下设置theme,这样就成了一个透明的Activity。在onRusume()方法中调用finish()方法,否则会报错。
<activity
android:name=".PermissionDemoActivity"
android:theme="@android:style/Theme.NoDisplay" />
结合上一节中的代码示例,在PermissionDemoActivity的onCreate()方法中申请权限,这样就间接地通过一个透明的Acitivity来实现权限的申请了。
六、常用的与Permission相关的adb命令
android工具adb提供了一些命令,可以方便查看、授权、取消应用的权限,可以为调试程序带来不少的方便,下面简单介绍几个常见的命令。
1、查看指定app中权限申请情况
命令:adb shell dumpsys package [包名]
用途:该命令用于获取该app的package信息,Permission信息只是其中的一部分。
命令使用示例:
adb shell dumpsys package cn.aaa.bbb
如下下截图为该命令中关于权限的部分信息:
该图显示了4部分权限:
(1)declared permissions。该应用自己声明(即自定义)的权限,这里显示了权限名,权限等级,以及在什么时候获取该权限(INSTALLED 表示安装的时候就会授予该权限)。
(2)requested permissions。这里列出的是AndroidManifest.xml文件中所有request的权限,可以看出这里面包含了动态申请的权限和安装时申请的权限。
(3)install permissions:安装的时候就赋予的权限。可以和requested permissions对比一下,这里面少了一"android.permission.CAMERA"权限,该权限为动态申请权限。该列表中还展示了权限对应的授予情况,如granted所示,true表示已经被授予了权限。
(4)runtime permissions。这里显示的是运行时才需要申请的权限,即dangerous permission。
2、查看权限的声明者和使用者
命令:adb shell dumpsys package permission <权限名>
用途:该命令可以查看指定权限是谁声明的,有哪些应用申请了该权限。
命令使用示例:
adb shell dumpsys package permission cn.aaa.bbb.TEST_PERMISSION
如下节选了该权限的定义信息和其中一个使用该权限的应用的关键信息:
Permissions:
Permission [cn.aaa.bbb.TEST_PERMISSION] (d4d8316):
sourcePackage=cn.aaa.bbb
uid=10078 gids=null type=0 prot=signature|privileged
perm=Permission{f5b497 cn.aaa.bbb.TEST_PERMISSION}
packageSetting=PackageSetting{96e1684 cn.aaa.bbb/10078} Packages:
Package [cn.xxx.xxx] (5d0f51b):
......
declared permissions:
requested permissions:
install permissions:
cn.aaa.bbb.TEST_PERMISSION: granted=true ......
3、移除指定权限
命令:adb shell pm revoke [packageName] [permissionName]
用途:移除packageName应用的permissionName权限(可以同时移除多项权限)。
命令使用示例(如下为删除包名为cn.aaa.bbb 的相机权限):
adb shell pm revoke cn.aaa.bbb android.permission.CAMERA
执行完该命令后,用前文提到的命令“adb shell dumpsys package cn.aaa.bbb”查看该权限的信息如下:
通过实验发现,该命令对runtime permissions有效,却对install permissions无效,如以下异常信息所示:
4、授予指定权限
命令:adb shell pm grant [packageName] [permissionName]
用途:为packageName应用授予permissionName权限(可以同时授予多项权限)。该命令和上一条移除命令相对应。
参照上一条命令的实例,实验结果如下:
5、查看系统定义的所有权限
命令:adb shell pm list permissions -s[option] 不加-s会显系统中定义的所有权限名列表,加了-s会显示对这些权限的用途说明。
下面截图分别展示了命令不加-s和加了-s后的显示结果(重定向到文本中查看),其中不加-s的截图中,一共显示了571条权限,这里截取了一部分,其中可以看到不少自定义的权限。
6、按组查看权限
命令:adb shell pm list permissions -d -g
用途:查看权限的分组情况。这部分是上面一条命令的补充,参数可以根据自己的需要选择。
参考:【https://developer.android.google.cn/training/permissions/usage-notes#testing】
下列截图为结果的一部分。
7、授予所有权限
命令:adb shell install -g MyApp.apk
用途:当安装MyApp.apk到模拟器或测试机上时,如果加上-g,可以自动授予所有权限。这一点笔者没有实验过,读者可以自行测试。
参考:该处和第4点一样参考官网说明。
推荐阅读
【Android权限的一些细节https://blog.csdn.net/u013553529/article/details/53167072】
结语
Anroid权限知识点其实没有太多需要理解的逻辑问题,一般都是一些需要记住的知识点。本文的一些实际操作技巧,很多都是笔者工作中用到的,具有较强的实践参考作用。由于笔者的经验和水平有限,如果有描述不妥当或不准确的地方,请不吝赐教,谢谢!