第六章 Android 权限问题总结

  • 一、前言
  • 二、权限分类
  • 三、权限申请
  • 四、总结

一、前言

安卓平台权限一直有被流氓应用随便利用诟病, android M(SDK 23)的发布彻底解决了这一问题,取而代之的是,app不得不在运行时一个一个询问用户授予权限。

Android 6.0,代号棉花糖,其主要的特征运行时权限就很受关注。因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变。Android 6.0(api23)系统中,做了一些限制, 开发者在使用到每条权限时必须自己调用相关代码请求。如果没有获得某项权限,直接使用相关功能,则会导致自己程序crash。

没有深入了解运行时权限的开发者通常会有很多疑问,比如什么是运行时权限,哪些是运行时的权限,我的应用是不是会在6.0系统上各种崩溃呢,如何才能支持运行时权限机制呢。本文讲尝试回答这一些问题,希望读者阅读完成之后,都能找到较为完美的答案。


二、权限分类

  • Android 6.0以前版本:权限管理总结为一句话是:权限一刀切。
    在6.0以前的系统,都是权限一刀切的处理方式,只要用户安装,Manifest申请的权限都会被赋予,并且安装后权限也撤销不了。

  • Android6.0及以后版本:Android M(SDK23) 进行了权限分类,运行时动态申请权限。
    尽管Android中有很多权限,但并非所有的权限都是敏感权限,于是6.0系统就对权限进行了分类,一般为下述三类:

正常(Normal)权限
危险(Dangerous)权限
特殊(Particular)权限
  • 正常(Normal)权限

一般权限都是一些系统认为比较权限的权限,流氓应用就是拥有这些权限也干不出多大坏事,Normal 权限会在应用安装是直接授权。 正常(Normal)权限的列表:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT
UNINSTALL_SHORTCUT

上述的权限基本设计的是关于网络,蓝牙,时区,快捷方式等方面,只要在Manifest指定了这些权限,就会被授予,并且不能撤销。

  • 危险(Dangerous)权限

这些权限都是一些敏感性权限,一些广告平台或是流氓应用会用这些权限干一些坏坏的事情,因此系统将这类权限分了几个类别, 应用每次都要检测下是否有权限,没有的化必须弹出对话框申请,只要一个组别中的一个权限得到了授权,整个组的权限都会的到授权。

危险权限实际上才是运行时权限主要处理的对象,这些权限可能引起隐私问题或者影响其他程序运行。Android中的危险权限可以归为以下几个分组:

CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

各个权限分组与其具体的权限,可以参考下图:


权限分组与其具体的权限

这部分权限也是我们重点在M系统上关注和适配的部分。

6.0的运行时权限,我们最终都是要支持的,通常我们需要使用如下的API:

int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调

具体如何操作,参考本文:权限申请模块。


  • 特殊(Particular)权限

特殊权限,顾名思义,就是一些特别敏感的权限,在Android系统中,主要由两个

SYSTEM_ALERT_WINDOW 设置悬浮窗,进行一些黑科技
WRITE_SETTINGS 修改系统设置

SYSTEM_ALERT_WINDOW and WRITE_SETTINGS, 这两个权限比较特殊,不能通过代码申请方式获取,必须得用户打开软件设置页手动打开,才能授权.

关于上面两个特殊权限的授权,做法是使用startActivityForResult启动授权界面来完成。

官方文档中这样描述:

There are a couple of permissions that don’t behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are particularly sensitive, so most apps should not use them. If an app needs one of these permissions, it must declare the permission in the manifest, and send an intent requesting the user’s authorization. The system responds to the intent by showing a detailed management screen to the user.

例如:请求SYSTEM_ALERT_WINDOW,设置悬浮窗

    private static final int REQUEST_CODE_OVERLAY_PERMISSION = 110;
    private  void requestAlertWindowPermission() {
        if (!Settings.canDrawOverlays(getBaseContext())) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            intent.setData(Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQUEST_CODE);
        }
       
    }
  
    protected void onActivityResult1(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) {
            if (Settings.canDrawOverlays(this)) {
                // 已成功授权
            }else{
                // 未授权
            }
        }
    }
    

提示

  • 使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION启动隐式Intent
  • 使用"package:" + getPackageName()携带App的包名信息
  • 使用Settings.canDrawOverlays()方法来判断授权结果
Settings.ACTION_MANAGE_OVERLAY_PERMISSION权限授权页面

请求修改设置权限 WRITE_SETTINGS

private static final int REQUEST_CODE_WRITE_SETTINGS = 120;
private void requestWriteSettings() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_WRITE_SETTINGS) {
        if (Settings.System.canWrite(this)) {
            Log.i(LOGTAG, "onActivityResult write settings granted" );
        }
    }
}

提示

上述代码需要注意的是

  • 使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS 启动隐式Intent
  • 使用"package:" + getPackageName()携带App的包名信息
  • 使用Settings.System.canWrite方法检测授权结果
    注意:关于这两个特殊权限,除非需要,否则一般不建议应用申请。

三、权限申请

前面已经提到,系统将这类权限分了几个类别, 应用每次都要检测下是否有权限,没有的化必须弹出对话框申请,只要一个组别中的一个权限得到了授权,整个组的权限都会的到授权。

6.0的运行时权限申请时,通常我们需要使用如下的API:

int checkSelfPermission(String permission) 用来检测应用是否已经具有权限,参数就是对应的权限名,如:相机权限是Manifest.permission.CAMERA
void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调

提示:

开发过程中,我们不是马上就申请权限,而是先判断是否已具有权限,方法是int checkSelfPermission(String permission) 。如果没有权限,才去申请void requestPermissions(String[] permissions, int requestCode) 。然后,拿到回调void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) ,判断用户是否授权了。

下面以以一个请求读取联系人的权限为例进行说明:

API的讲解就跟着申请权限步骤一起了:

  • 第一步:在AndroidManifest文件中添加需要的权限。

这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有在AndroidManifest文件中声明的权限可能会导致程序崩溃。

  • 第二步:检查权限
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
}else{
    //
}

这里涉及到一个API,ContextCompat.checkSelfPermission,主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了。

  • 第三步:申请授权
 ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。

  • 第四步:处理权限申请回调
@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;
        }
    }
}

ok,对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了~

当然,到此我们的权限申请的步骤,基本介绍就如上述。不过还有个API值得提一下:

// 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.

}

这个API主要用于给用户一个申请权限的解释,该方法只有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框,你需要给用户一个解释,为什么要授权,则使用该方法。

乐视2授权页面示意图.jpg
  • 小结:那么将上述几个步骤结合到一起就是:
// 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.
    }
}

四、总结

好在运行时相关的API也比较简单,所以适配起来并不会非常痛苦。对于其他的权限,其实申请的逻辑是类似的;唯一不同的肯定就是参数。

Android6.0版本最大的特性:权限。对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如不是每个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。

特别提示

在测试过程中,用的锤子·坚果2,发现不弹授权框。换了部乐视2手机,可以弹出。

所以总结:以下原因不会弹框

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

推荐阅读更多精彩内容