Android M 权限管理详解

声明权限

每一个Android APP都是运行在一个受限的沙盒中,如果一个app想要使用外部的资源或信息,就必须申请相应的权限,通常我们会在App Manifest文件中声明我们需要的所有权限。

根据权限的敏感程度,系统会自动申请一些敏感程度低的权限,而敏感程度高的就必须要显式的申请。例如,如果你的app需要打开闪光灯的权限,系统会自动帮你获取,但是如果你的app要访问通讯录,那么系统就会询问用户是否允许app使用该权限。根据不同的系统版本,用户在Android 5.1及以下的版本安装时提示申请权限,而在Android 6.0及以上的版本运行时申请权限。

判断你需要哪些权限

一般如果app想要使用非app内部信息,或者有影响设备或其他app的操作时就需要申请权限。例如,如果app需要访问网络,使用手机摄像头,或者开关和关闭Wi-Fi,那么就需要申请相应的权限。以下是一般和危险的权限列表:

Android M 权限管理详解_第1张图片
Android一般和危险权限列表

你的app只需要申请你直接操作的权限,如果调用其他app的操作或信息则不需要申请权限,因为其他app自己会处理。例如,如果你的app需要访问用户的通讯录,那么你需要申请READ_CONTACTS权限。但是如果你使用intent去访问手机上的其他通讯录app,则不需要申请权限,但是通讯录app就必须要申请READ_CONTACTS权限。

添加权限到Manifest文件

如果你要申请某个权限,需要在app manifest文件中的根节点下创建一个子节点,同时将相应的权限放入子节点中间。例如,你需要申请发送SMS信息的权限,那么你需要在app manifest文件下完成以下代码:



    
    

    
        ...
    


系统会根据你申请权限的敏感度做相应的操作,如果申请的权限不影响用户的隐私,则系统会自动帮你获取到该权限,如果涉及用户的隐私,那么系统就会询问用户是否允许使用该权限。

运行时申请权限

从Android 6.0(API level 23)开始,申请敏感度高的权限需要在运行时完成,而非安装时,这样可以简化app的安装流程,因为用户不需要在安装和升级app时申请权限。同时也增加了用户对app功能的控制权,例如用户可以控制允许访问摄像头而不允许访问当前地址,用户可以在系统设置中任意控制app的权限。

系统权限被分为了两种类型,一般权限和危险权限:

  • 一般权限不会直接威胁用户的隐私,如果你在app manifest文件中申明一般权限,系统会自动帮你完成申请
  • 危险权限允许app访问用户的私密信息,如果你在app manifest文件中申明危险权限,那么系统将会询问用户是否允许app使用该权限。

在所有的Android版本中,你的app一般都需要申请一般和危险权限,那么不同的系统版本会有不同的处理:

  • 手机系统版本为Android 5.1及以下,或者app的目标SDK为22及以下:如果你在manifest文件中申请了危险权限,那么在安装阶段需要申请这些权限,如果安装时用户拒绝了某些权限,那么系统就不会安装该app
  • 手机系统版本为Android 6.0及以上,或者app的目标SDK为23及以上:如果你在manifest文件中申请了危险权限,那么在app运行到需要这些权限时就必须显式地申请该权限,如果安装时用户拒绝了某些权限,那么app就无法使用该权限

从Android 6.0(API level 23)开始,用户可以系统设置里任意控制app的权限

检查权限

如果你的app需要危险权限,你必须每次在用到该权限时都要检查是否允许使用,用户可以随时打开或者关闭权限,因此即使昨天你可以正常访问手机摄像头,今天你也仍要申请。

为了检查是否拥有某个权限,需要调用ContextCompat.checkSelfPermission()方法,例如,下面一段代码就是检查是否有访问日历权限:

// 假定thisActivity就是当前的activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_CALENDAR);

如果app有这个权限,该方法返回PackageManager.PERMISSION_GRANTED, 并且app可以操作相应的权限。如果app没有该权限,则该方法返回PERMISSION_DENIED,并且app不得不再次询问用户是否允许拥有该权限。

申请权限

如果你的app在manifest文件中申请一个危险权限,你必须询问用户申请该权限,为此Android提供了几个方法,调用这些方法可以唤起标准的Dialog,且不可自定义。

如果你的app没有拥有某个权限,那么必须调用requestPermissions()中的一个方法。当你要申请某个权限,需要传递一个整型的request值给指定的权限,当用户在对话框中允许或者拒绝后,系统将会返回申请结果,同时返回相同的request值。

下面的代码展示了检查是否拥有访问用户通讯录的权限,并申请该权限:

// 此处thisActivity就是当前的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)) {
        // 显示需要该权限的理由,此处等待用户的反馈,当用户看过后在尝试申请权限
    } else {
        // 此处已经获得了权限,因此无需再解释.
        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);
        // MY_PERMISSIONS_REQUEST_READ_CONTACTS是自定义的整型常数,
        // 回调方法将会据此返回相应的结果。
    }
}

当调用equestPermissions()方法时,系统会为用户展示一个标准的对话框,你无法对其进行修改和自定义。如果你需要向用户展示说明信息,那么你应该在调用之前完成。

处理权限申请结果

当你的app申请权限,系统会为用户显示一个对话框,当用户做出判断后,系统就会回调onRequestPermissionsResult()方法,你必须重载该方法以判断用户是否允许使用权限。回调方法将会返回之前自定义的request整型值,例如如果app申请访问通讯录权限,将会有如下回调方法:

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // 如果申请被拒绝,那么结果数组将为空。
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 申请权限通过,你将可以实现具体的业务逻辑
            } else {
                // 权限被拒绝,相关的功能也要有所处理。
            }
            return;
        }
        // other 'case' lines to check for other
        // permissions this app might request
    }
}

系统提供的对话框展示了你需要访问的权限组,而不是一个个具体的权限。例如,如果你申请READ_CONTACTS权限,系统对话框只会说app需要访问设备的通讯录。用户只需要同意其中的一个权限即可。如果app需要访问权限组的其他权限(在app manifest文件列举),系统会自动帮你完成申请。申请成功的结果是系统通过调用app重写的onRequestPermissionsResult()回调方法并且返回PERMISSION_GRANTED。

你仍然需要明确地请求每一个你需要的权限,即使用户已经在权限组中同意了其他权限。此外,权限组中的权限在未来的Android版本中有可能会改变。你的代码不应该依赖所需要的权限是否在同一个权限组。

例如,假定你在app manifest文件中列举了READ_CONTACTS和WRITE_CONTACTS,如果你申请READ_CONTACTS并且用户同意允许使用该权限,那么当你需要WRITE_CONTACTS权限时,系统就不会弹框提示用户选择而是直接同意使用。

如果用户拒绝了你的权限申请,你的app应该做好足够的应对。例如,你需要显示一个对话框解释某个操作是因为没有权限所以才无法正常使用。

如果你申请的权限已经被用户拒绝,在申请权限之前,你可以先调用ActivityCompat#shouldShowRequestPermissionRationale(Activity,

  • String)方法展示额外的说明文案。

当系统询问用户是否允许使用某个权限时,用户可以选择勾选不再提示,那么,当app之后再次调用requestPermissions()申请权限时,系统将会直接拒绝申请。系统将会回调onRequestPermissionsResult()方法并返回PERMISSION_DENIED,这种情况同样适用用户已经明确拒绝过请求的情况。也就是说当你调用requestPermissions()时,你不应该假定和用户的交互一定会出现。

下一篇文章将会介绍Android权限最佳实践以及github热门权限SDK的代码分析。

你可能感兴趣的:(Android M 权限管理详解)