概要
android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。
一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西。
难怪一些坏蛋利用这个缺陷恶意收集用户数据用来做坏事了!
android小组也知道这事儿。7年了!权限系统终于被重新设计了。
在android6.0棉花糖,app将不会在安装的时候授予权限。取而代之的是,app不得不在运行时一个一个询问用户授予权限。
注意权限询问对话框不会自己弹出来。开发者不得不自己调用。如果开发者要调用的一些函数需要某权限而用户又拒绝授权的话,函数将抛出异常直接导致程序崩溃。
那么问题就来了怎样解决呢
这个新的运行时权限仅当我们设置targetSdkVersion to 23
(这意味着你已经在23上测试通过了)才起作用,当然还要是M系统的手机。app在6.0之前的设备依然使用旧的权限系统。
所以我们可以设置targetSdkVersion为22
但这样毕竟不是好的方式 我们还是好好学学新版的权限这样使用
正文
新版的权限可以分为两大类普通权限
和运行时权限
运行时权限
需要询问用户
普通权限
只要在AndroidManifest.xml
中声明就好了,安装应用时会自动赋予
普通权限
普通权限
包含以下权限
1 | android.permission.ACCESS_LOCATION_EXTRA_COMMANDS |
运行时权限
权限组 | 权限 |
---|---|
android.permission-group.CALENDAR |
|
android.permission-group.CAMERA |
|
android.permission-group.CONTACTS |
|
android.permission-group.LOCATION |
|
android.permission-group.MICROPHONE |
|
android.permission-group.PHONE |
|
android.permission-group.SENSORS |
|
android.permission-group.SMS |
|
android.permission-group.STORAGE |
|
从上图中我们可以看到 权限都被分了组
同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS
被授权了,app也有READ_CONTACTS
和GET_ACCOUNTS
权限了。
是时候让我们的app支持新权限模型了,从设置compileSdkVersion
and targetSdkVersion
为 23
开始吧.
1 | android { |
请求单个权限
假如我们要添加联系人
1 | //添加联系人的方法 |
下一步像以前一样在AndroidManifest.xml
添加声明权限。
1 | <uses-permission android:name="android.permission.WRITE_CONTACTS"/> |
光是这样的话还是没有权限,所以我们要询问用户授权
定义全局变量
1 | final private int REQUEST_CODE_ASK_PERMISSIONS = 123; |
1 | private void insertDummyContactWrapper() { |
如果已有权限,insertDummyContact()
会执行。
否则,requestPermissions
被执行来弹出请求授权对话框
不论用户同意还是拒绝,activity的onRequestPermissionsResult
会被回调来通知结果(通过第三个参数: grantResults)
如下:
1 |
|
处理 用户点击了“不再提醒”的情况
如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项的来防止app以后继续请求授权。
如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions
时,对话框就不弹出来了,结果就是,app啥都不干。
这将是很差的用户体验,用户做了操作却得不到响应。这种情况需要好好处理一下。
在请求requestPermissions
前,我们通过activity的shouldShowRequestPermissionRationale
方法来检查是否需要弹出请求权限的提示对话框
- 第一次请求权限时,用户拒绝了,下一次:
shouldShowRequestPermissionRationale()
返回true
,应该显示一些为什么需要这个权限的说明
- 第一次请求权限时,用户拒绝了,下一次:
- 第二次请求权限时,用户拒绝了,并选择了“不在提醒”的选项时:
shouldShowRequestPermissionRationale()
返回false
- 第二次请求权限时,用户拒绝了,并选择了“不在提醒”的选项时:
- 设备的策略
禁止
当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()
返回false
- 设备的策略
代码如下:
1 | private void insertDummyContactWrapper() { |
当一个权限第一次被请求和用户标记过不再提醒的时候,我们写的对话框被展示。
最后一种情况,onRequestPermissionsResult
会收到PERMISSION_DENIED
,系统询问对话框不展示。
一次请求多个权限
当然了有时候需要好多权限,可以用上面方法一次请求多个权限。
不要忘了为每个权限检查“不再提醒”的设置。
修改后的代码:
添加全局常量
1 | final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124; |
1 | private void insertDummyContactWrapper() { |
用兼容库来做兼容(非必需)
以上代码是通过判断SDK的版本
来调用不同的方法来兼容不同的版本。当然也可以使用兼容包
。
我建议用v4兼容库,已对这个做过兼容,用这个方法代替:
- ContextCompat.checkSelfPermission()
被授权函数返回PERMISSION_GRANTED
,否则返回PERMISSION_DENIED
,在所有版本都是如此。 - ActivityCompat.requestPermissions()
这个方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED 。 - ActivityCompat.shouldShowRequestPermissionRationale()
在M之前版本调用,永远返回false。
用v4包的这三方法,完美兼容所有版本!这个方法需要额外的参数,Context or Activity。
别的就没啥特别的了。下面是代码:
1 | private void insertDummyContactWrapper() { |
我们也可以在
Fragment
中使用,用v13兼容包:FragmentCompat.requestPermissions()
FragmentCompat.shouldShowRequestPermissionRationale()
。
第三方库简化代码
以上代码真尼玛复杂。
为解决这事,有许多第三方库已经问世了。
项目没用Rxjava
建议用 hotchemi’s PermissionsDispatcher
。
如果项目用了Rxjava
更建议用RxPermissions
简单实例
添加依赖
1 | compile 'io.reactivex.rxjava2:rxjava:2.0.5' |
代码
1 | private void questAllPersission(){ |
上面用到了Lambda
表达式 具体参见:Android开发使用Lambda表达式
结论建议
我相信你对新权限模型已经有了清晰的认识。我相信你也意识到了问题的严峻。
但是你没得选择。新运行时权限已经在棉花糖中被使用了。我们没有退路。我们现在唯一能做的就是保证app适配新权限模型.
欣慰的是只有少数权限需要运行时权限模型。
大多数常用的权限,例如,网络访问,属于普通权限 在安装时自动会授权,当然你要声明,以后无需检查。因此,只有少部分代码你需要修改。
两个建议:
- 1.严肃对待新权限模型
- 2.如果你代码没支持新权限,不要设置targetSdkVersion 23。尤其是当你在Studio新建工程时,不要忘了修改!
Kotlin
申请权限
1 | ActivityCompat.requestPermissions( |
回调
1 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { |