运行时权限

Android应用权限简要介绍

    1. 一个Android应用默认情况下是不拥有任何权限的, 一个应用是没有权利去进行一些可能会造成不好影响的操作的. 这些不好的影响可能是对其它应用,操作系统,或者是用户.如果应用需要一些额外的能力,则它需要在AndroidManifest.xml中静态地声明相应的权限.
      如果应用没有在manifest中声明权限, 却使用了相应的功能, 在调用到相应功能的时候, 将会抛出异常.
      比如程序要发送一个请求,却忘记加Internet权限, 那么在发送这个请求的时候程序就会抛出异常,一般不会catch这个异常,所以程序直接就崩溃了:
      Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)
      Android 6.0开始, 一部分比较危险的权限需要在程序运行时显式弹框,请求用户授权.
      至于什么时候弹这个框,由应用程序自己决定.
      对于其他权限,认为不是很危险,所以仍然保持原来的做法,在用户安装应用程序时就予以授权.

Permission的保护等级

permission的保护等级通过protectionLevel属性设置, 共有4种: normal,dangerous,signature,signatureOrSystem.
签名相关的比较不常用, 剩下的两种是normaldangerous
总结下来就是: 所有的权限仍然在manifest中静态声明, normal权限的在安装的时候自动授权, 而dangerous的权限需要应用明确地请求用户授权.
Dangerous Permissions:

Table 1. Dangerous permissions and permission groups.

Permission Group Permissions
CALENDAR` READ_CALENDAR * WRITE_CALENDAR`
CAMERA` CAMERA`
CONTACTS` * READ_CONTACTS* WRITE_CONTACTS* GET_ACCOUNTS`
LOCATION` * ACCESS_FINE_LOCATION * ACCESS_COARSE_LOCATION`
MICROPHONE * RECORD_AUDIO
PHONE * READ_PHONE_STATE* CALL_PHONE* READ_CALL_LOG* WRITE_CALL_LOG* ADD_VOICEMAIL* USE_SIP* PROCESS_OUTGOING_CALLS
SENSORS * BODY_SENSORS
SMS * SEND_SMS* RECEIVE_SMS* READ_SMS* RECEIVE_WAP_PUSH* RECEIVE_MMS
STORAGE * READ_EXTERNAL_STORAGE * WRITE_EXTERNAL_STORAGE

可以总结为:
1.所有的权限都在manifest中声明.
2.如果(1)你的app的targetSdkVersion是23及以上,并且(2)app运行在Android 6.0及以上的设备,危险权限必须动态请求.
当权限被拒绝,app理应还是能够使用的,只不过权限相关的部分功能不能用.
3.上一条中的两个条件(1)(2)没有同时满足,即属于其他情况, 所有权限在安装时请求,如果用户不接受,则不安装.

特别注意这种情况: 旧应用新系统.
如果targetSdkVersion小于23,即被认为是Android 6.0发布之前开发的应用, 还没有兼容6.0.
这种应用即便是被装在Android 6.0的机器上,也是采用原来的安装即授予权限逻辑, 所有权限在应用安装时全部被授权.
在Android 6.0的设备上安装targetSdkVersion小于23的应用之后, 可以在应用的设置中查看,发现所有的dangerous权限状态都是打开.

Permission group
所有的权限都有自己的permission group.
系统弹框请求某一个permission时也是只说明了它的类别,当用户同意,系统会给予它该条permission.(只有这一条).
但是如果app已经有了该group下的另一条permission,系统将会自动授予权限(也即请求权限的callback直接返回),这过程中不与用户交互.

动态权限请求的实现

原文: https://developer.android.com/training/permissions/requesting.html

因为权限动态检查相关的API是Android 6.0才加入的, 所以minSdkVersion不是23时,推荐使用SupportLibrary来实现,好处是: 程序里不必加if来判断当前设备的版本.

1.检查权限状态

如果执行的操作需要一个dangerous permission, 那么每次在执行操作的地方都必须check你是否有这个permission, 因为用户可以在应用设置里随意地更改授权情况, 所以必须每次在使用前都检查是否有权限.

检查权限的方法: ContextCompat.checkSelfPermission()两个参数分别是Context和权限名.

返回值是:PERMISSION_GRANTED if you have the permission, or PERMISSION_DENIED if not.

比如:

if (PackageManager.PERMISSION_GRANTED== ContextCompat.checkSelfPermission(
MainActivity.this, Manifest.permission.READ_CONTACTS)) { 
    //has permission, do operation directly
    ContactsUtils.readPhoneContacts(this);
    Log.i(DEBUG_TAG, "user has the permission already!");
    } else { 
      //do not have permission
}

2.动态请求权限

如果上面权限检查的结果是DENIED, 那么就需要显式地向用户请求这个权限了.

Android提供了几个方法来动态请求权限, 调用这些方法会显示出一个标准的Dialog, 这个Dialog目前是不能被定制的.

2.1有时候可能需要解释为什么需要这个权限

有时候你可能会需要跟用户解释一下权限的用途.

注意不是每条权限都需要解释,显而易见的那种可以不解释,太多的解释会降低用户体验.

一种方式是,当用户拒绝过这个权限,但是又用到了这个功能, 那么很可能用户不是很明白为什么app需要这个权限, 这时候就可以先向用户解释一下.

为了发现这种用户可能需要解释的情形, Android提供了一个工具类方法: shouldShowRequestPermissionRationale()

如果app之前请求过该权限,被用户拒绝, 这个方法就会返回true.

如果用户之前拒绝权限的时候勾选了对话框中”Don’t ask again”的选项,那么这个方法会返回false.

如果设备策略禁止应用拥有这条权限, 这个方法也返回false.

注意具体解释原因的这个dialog需要自己实现, 系统没有提供.

2.2请求权限

请求权限的方法是: requestPermissions() 传入一个Activity, 一个permission名字的数组, 和一个整型的request code.

这个方法是异步的,它会立即返回, 当用户和dialog交互完成之后,系统会调用回调方法,传回用户的选择结果和对应的request code.

代码:

if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfP
ermission(MainActivity.this, Manifest.permission.READ_CONTACTS)) { 
//has permission, do operation directly
    ContactsUtils.readPhoneContacts(this);
    Log.i(DEBUG_TAG, "user has the permission already!");
} else { 
    //do not have permission
    Log.i(DEBUG_TAG, "user do not have this permission!");
   // Should we show an explanation?
      if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivit
        y.this, Manifest.permission.READ_CONTACTS)) { 
// Show an explanation 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.
       Log.i(DEBUG_TAG, "we should explain why we need this permission!");
    } else {
     // No explanation needed, we can request the permission.
        Log.i(DEBUG_TAG, "==request the permission==");

        ActivityCompat.requestPermissions(MainActivity.this, 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.
 }
}  

这个对话框是系统的,不能自定义.

经验证, 请求权限对话框中的”Don’t ask again”的选项, 只有该条权限之前的状态是Denied的时候,才会出现.

以前从未授权(即第一次弹框), 或者之前的状态是Granted(当然这种情况一般不会弹框询问), 出现的弹框都是不带该不再询问的选项的.

2.3处理请求权限的响应

当用户对请求权限的dialog做出响应之后,系统会调用onRequestPermissionsResult() 方法,传回用户的响应.

这个回调中request code即为调用requestPermissions()时传入的参数,是app自定义的一个整型值.

如果请求取消,返回的数组将会为空.

代码:

@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.
                ContactsUtils.readPhoneContacts(this);
                Log.i(DEBUG_TAG, "user granted the permission!");

            } else { // permission denied, boo! Disable the 
                // functionality that depends on this permission.
                Log.i(DEBUG_TAG, "user denied the permission!");
            } return;
        } // other 'case' lines to check for other 
// permissions this app might request
 }
}

系统自动回调的情况:

有一些情形下,调用

1.自动授权: 如果用户已经允许了permission group中的一条A权限,那么当下次调用requestPermissions()方法请求同一个group中的B权限时, 系统会直接调用onRequestPermissionsResult()` 回调方法, 并传回PERMISSION_GRANTED的结果.

2.自动拒绝: 如果用户选择了不再询问此条权限,那么app再次调用requestPermissions()方法来请求同一条权限的时候,系统会直接调用onRequestPermissionsResult()回调,返回PERMISSION_DENIED.

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

推荐阅读更多精彩内容