CHA1-Structure——9.理解应用的权限

原文:Understanding App Permissions

  —How to request the permissions you need


概述


 默认地,Android应用在创建时未被授予任何权限。当应用需要使用设备中任何受保护的特性时(发送网络请求,使用照相机,发送短信等),必须先从用户手中获取对应的权限才能实现这些操作。

 在Marshmallow(Android 6.0)系统之前,权限的申请在应用安装时进行,并在项目的AndroidManifest.xml文件中指定。全部的权限列表可以查看此处。在Marshmallow系统之后,在使用权限前必须在运行时请求。有几个可用的库可让运行时的权限申请更容易些。如果你想快速了解这些,请参考我们的指南Managing Runtime Permissions with PermissionsDispatcher

Marshmallow(Android 6.0)之前的权限申请方式


 在Marshmallow(API 23)系统之前,权限申请的方式非常简单。所有的权限申请都是在应用安装时完成处理。当用户从应用商店获取一个应用并准备安装时,首先会列出应用运行所需的所有权限。用户可以选择接受所有的权限申请并继续安装应用或者决定不去安装应用。这种方式要么全都同意,要么全都不同意。没办法做到只授予应用所需的部分权限,也没办法让用户在安装应用之后只调用特定的权限。

 Dropbox应用在Marshmallow版本之前的系统上申请权限的例子:

Imgur

 对应用开发者来说,权限申请非常简单。要请求这些权限中的一部分时,只要简单地在AndroidManifest.xml中指定它就可以:
 例如,一个应用需要读取用户的通信录,它将会添加以下内容再AndroidManifest.xml中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    ...
</manifest>

 这就是它所拥有的一切,即使安装完应用程序之后,用户也无法更改权限。这使得开发人员很容易处理权限,但这不是最好的用户体验。

Marshmallow(Android 6.0)系统中的权限更新


 Marshmallow版本在权限处理方面带来了很多改变。并且它引入了运行时权限的概念。有些权限是在应用运行过程中申请的(并非在应用安装之前)。这些权限可由用户决定是否授予。对于已批准的权限也能在之后进行撤销。

 这意味着针对Marshmallow版本上的应用处理权限时,有一些需要注意的问题。切记,targetSdkVersion版本必须>=23,并且模拟器/真机必须运行Marshmallow系统,才能看到新的权限管理模块。如果不是这种情况,请参阅向后兼容部分,来了解权限在配置上的行为。

普通权限

 当你需要添加一个新的权限时,可以先通过Normal Permission简介查看该权限是否是PROTECTION_NORMAL权限。在Marshmallow系统中,Google将特定的权限设计为“安全的”,并将它们称作“普通权限”。比如ACCESS_NETWORK_STATE, INTERNET等,这些不会产生损害的权限。普通权限是在安装时自动进行的授权,不会对用户弹出是否授权的提示。

重点:普通权限必须被添加到AndroidManifest中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

运行时权限

 如果你要添加的权限不属于普通权限的范畴,那么你需要以“运行时权限”的方式来处理。运行时权限是指,当应用运行过程中,在用到的时候才进行请求的权限。这些权限通常会以对话框的形式向用户请求授权,如下图所示:


 添加“运行时权限”的第一步是将它添加到AndroidManifest中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.codepath.androidpermissionsdemo" >

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    ...
</manifest>

 接下来,你需要初始化权限请求并处理对应的结果。下面的代码展示了在Activity的上下文环境中如何处理“运行时权限”的处理,但这在Fragment中也是可行的。

// MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 在实际的应用中,你可能会在用户执行的操作需要某个权限时才去请求它
        getPermissionToReadUserContacts();
    }

    // 权限请求的标识符
    private static final int READ_CONTACTS_PERMISSIONS_REQUEST = 1;

    // 当用户执行读取联系人的操作时调用
    public void getPermissionToReadUserContacts() {
        // 1) 使用support库的ContextCompat.checkSelfPermission(...)避免检测构建版本,因为Context.checkSelfPermission(...)只在Marshmallow中可用
        // 2) 总是执行权限的检测(即使权限已经被授予),因为用户可用通过“设置”在任意时刻撤销授权
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {

            // 权限还未被授予时
            // 检测用户是否已经被询问该权限的授予并拒绝授权。如果这样的话,应该针对为什么需要该权限做出更多解释
            if (shouldShowRequestPermissionRationale(
                    Manifest.permission.READ_CONTACTS)) {
                // 在实际需要该权限时通过自定义的UI向用户解释为什么需要读取联系人的权限
            }

            // 消除异步请求,实际获取权限
            // 它将展示权限请求的标准对话框界面
            requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
                    READ_CONTACTS_PERMISSIONS_REQUEST);
        }
    }

    // 调用requestPermissions(...)的请求回调
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        // 确保它是最初的READ_CONTACTS权限请求
        if (requestCode == READ_CONTACTS_PERMISSIONS_REQUEST) {
            if (grantResults.length == 1 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Read Contacts permission granted", Toast.LENGTH_SHORT).show();
            } else {
                // showRationale = false 如果用户点击了不再提示, 否则为true
                boolean showRationale = shouldShowRequestPermissionRationale( this, Manifest.permission.READ_CONTACTS);

                if (showRationale) {
                   // 处理退化模式
                } else {
                   Toast.makeText(this, "Read Contacts permission denied", Toast.LENGTH_SHORT).show();
                }
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

权限分组

权限分组可以防止滥用用户的权限请求,同时允许开发者只要求在任意时间点内最小数量的权限请求。

 相关的权限可被分到以下的任意一个权限组中。当应用请求某个特定权限组中的权限时(例如READ_CONTACTS),Android系统会询问用户更高级别的权限请求(CONTACTS)。这样,当应用以后再需要WRITE_CONTACTS权限时,Android系统会自动完成授权而不用再询问用户。

 在与权限处理API的大多数交互中,你可能处理的是单个的权限而不是权限组。但是一定要注意API想要处理什么内容,因为权限和权限组都是字符串。

向后兼容性

 当提到向后兼容性的时候,主要有两个场景需要考虑:

 1、应用的目标API版本低于Marshmallow (TargetSdkVersion < 23),但模拟器/设备是Marshmallow系统:

  • 你的应用将继续使用旧版本的权限处理模块。
  • AndroidManifest中列出的所有权限都会在安装时被询问是否授权。
  • 用户只能在应用安装之后撤销权限。对于这种情景的测试非常有必要,因为没有对应权限而执行特定的动作可能会导致不可预期的结果。

 2、模拟器/设备上运行的是Marshmallow之前的系统,但应用的目标API是Marshmallow(TargetSdkVersion >= 23):

  • 你的应用仍将继续使用旧版本的权限处理模块。
  • AndroidManifest中列出的所有权限都会在安装时被询问是否授权。

如何请求授权

 Google推荐了这部视频,当说起授权问题时,这里有四种模式需要考虑:

 每种模式都指明了一种权限请求的不同方式。例如当请求一个关键但不清楚的权限时,使用一个介绍页面帮助理解为什么需要请求该权限。对于关键权限,例如相机应用需要camera权限,要先查询。次要功能可在之后的上下文中请求,例如在要求location权限的地理标记应用中。对于次要和不清楚的权限,如果真的需要,应该包含解释说明。

存储权限

 重新思考下你是否需要读/写存储的权限(例如android.permission.WRITE_EXTERNAL_STORAGE or android.permission.READ_EXTERNAL_STORAGE),这可以使你访问存储卡上的所有文件。相反地,你应该使用上下文Context中的方法访问外部存储中特定包下的目录。你的应用总是有读/写以下目录的权限,所以不必再请求授权:

// 特定应用能够调用的,不需要请求外部存储权限的有:
// Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_PODCASTS, Environment.DIRECTORY_RINGTONES, 
// Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PICTURES, or Environment.MOVIES

File dir = MyActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);

使用ADB管理权限

 权限也可以通过命令行的adb,使用以下命令进行管理。
 列出所有的Android权限:

$ adb shell pm list permissions -d -g

 抓取应用权限的状态:

$adb shell dumpsys package com.PackageName.enterprise

 授予或撤销运行时权限:

$adb shell pm grant com.PackageName.enterprise some.permission.NAME
$adb shell pm revoke com.PackageName.enterprise android.permission.READ_CONTACTS

 安装应用并授权全部权限:

$adb install -g myAPP.apk

参考引用


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 发热放入Android6.0带来了新的权限管理方式,根据提供的官方文档,再加上自己的理解,做了以下汇总仅供大家伙参...
    PapiAP阅读 1,656评论 0 6
  • 写在前面:在看《有道云笔记|记录,成为更好的自己》这本书之前,我已经是印象笔记的用户了。但还是盯了图书馆好久,书一...
    琴心未央阅读 1,291评论 6 25
  • 自信,是一个眼神,一个动作,一句话,一种心态。 如果在网上查自信是什么,它会告诉你:自信是一个心理学名词。 在心理...
    江月的书妆台阅读 1,571评论 13 12
  • 夜半更深,梳洗更迟,多少信笺难寄语。万家熄却窗前火,唯有孤灯光如许。 提笔忘字,半阙残词,笔下字行时时断,流年...
    奔跑的大肥羊阅读 160评论 0 1