关于Android 6.0权限的动态适配详解
作者:马果果 发布时间:2021-09-16 07:56:34
前言
Android6.0代号棉花糖。尽管是在15年I/O大会上Google被正式发布的了。但是看看大多数人的项目中大家的 targetSdkVersion 是不是还都用的22。大家都认为6.0+的市场占有率还没那么高。那么就请看谷歌2017年9月份公布的版本分布图。
从数据来看确实没那么高O(∩_∩)O。6.0+的市场占有率仅为50% ̄□ ̄||。只因安卓用户的基数太大了吧。延伸至各种人群。虽然说占比才一半但时基数大总的用户数量还是蛮多的。这两天刚做完6.0权限的适配。那么请说一下自己测试的时候踩的坑吧(*╹▽╹*)
权限管理系统的变化
在Android6.0(M)之前,在用户安装应用的时候会产生一个权限列表,只有用户允许这些权限后,应用才可以正常的安装,这就会产生一个问题,这些权限对用户是不具有感知性的,也就是说用户都不知道你要这些权限干什么,我明明装的是一个阅读类型的应用,你却要我拨打电话的权限,你想干嘛呢?当然绝大部分的开发者是善意的,但也避免不了一些特殊人群利用这些“漏洞”做一些不好的事情。
而在Android6.0(M)之后,用户是可以不管权限直接安装应用的,当应用需要调用某些权限的时候,会给予用户一个通知与说明,我要这些权限干什么,这样下来可以让用户有更加清醒的权限分配意识,也在一定程度上更加人性化的保护了用户的隐私,避免了“权限一刀切”。
权限的分组
在Android6.0(M)之后,对权限进行了分类,大致有这三种:
普通权限
危险权限
特殊权限
普通权限:也就是正常权限,是对手机的一些正常操作,对用户的隐私没有太大影响的权限,比如手机的震动,网络访问,蓝牙等权限,这些权限会在应用被安装的时候默认授予,用户不能拒绝,也不能取消。
普通权限列表:
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
对于上面这些普通权限 在Android6.0以前我们只需要在清单文件中声明该权限即可。
危险权限:其实就是运行中需要处理的权限,也是我们最需要注意的权限,这些权限会关系到用户的隐私或影响到其他应用的运行,这些危险权限,谷歌还做了一个权限组,以分组的形式来呈现:
由于运行权限机制的出现,我们需要对新开发的应用去做适配。
当你的应用targetSdkVersion小于23的时候,当应用用于6.0以上的系统时候,它也会默认采用以前的权限管理机制。当你的targetSdkVersion大于等于23的时候且在Andorid6.0(M)系统上,它才会采用新的这套权限管理机制。
所以如果你想逃开这个“麻烦”,只要把targetSdkVersion的版本设置为低于23就可以了,不过不建议采用这种方案,该来的总是要来的,随着国产手机ROM的更新,比如小米,华为等也开始有部分机型进行了系统升级,所以这是种趋势。
说了这么多,那么来看下怎么进行Android6.0(M)的权限管理适配吧,其实很简单,只需要记住下面几个API方法就可以:(API23之后提供)
int checkSelfPermission(String permission)
用来检测应用是否已经具有权限void requestPermissions(String[] permissions, int requestCode)
进行请求单个或多个权限void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
请求权限结果回调
checkSelfPermission(String permission) 方法返回值有两个:
PERMISSION_DENIED = -1:代表当前检查的权限没有被授权
PERMISSION_GRANTED = 0;代表当前的检查的权限已经被授权
requestPermissions(String[] permissions, int requestCode)
参数一:要请求的权限组 权限2请求码
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
请求的回调。
参数3对应 对应permissions的权限请求结果(PERMISSION_GRANTED或者PERMISSION_DENIED)
看完关键的三个方法接下来上我的油条:
object MQPermissionUtil {
private var mRequestCode = -1
private val isOverMarshmallow: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
private var mOnPermissionListener: OnPermissionListener? = null
fun requestPermissions(activity: Activity, requestCode: Int, permissions: Array<String>, isCancelFinish: Boolean, onPermissionGrantedListener: OnPermissionGrantedListener) {
requestPermissionsResult(activity, requestCode, permissions, object : OnPermissionListener {
override fun onPermissionGranted() {
onPermissionGrantedListener.onPermissionGranted()
}
override fun onPermissionDenied() {
if (isCancelFinish) {
showTipsDialogWel(activity)
} else {
showTipsDialog(activity)
}
}
})
}
private fun requestPermissionsResult(activity: Activity, requestCode: Int, permissions: Array<String>, callback: OnPermissionListener) {
mOnPermissionListener = callback
if (checkPermissions(activity, *permissions)) {
if (mOnPermissionListener != null)
mOnPermissionListener!!.onPermissionGranted()
} else {
val deniedPermissions = getDeniedPermissions(activity, *permissions)
if (deniedPermissions.isNotEmpty()) {
mRequestCode = requestCode
ActivityCompat.requestPermissions(activity, deniedPermissions
.toTypedArray(), requestCode)
}
}
}
fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode == mRequestCode) {
if (verifyPermissions(grantResults)) {
if (mOnPermissionListener != null)
mOnPermissionListener!!.onPermissionGranted()
} else {
if (mOnPermissionListener != null)
mOnPermissionListener!!.onPermissionDenied()
}
}
}
private fun startAppSettings(context: Context) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:" + context.packageName)
context.startActivity(intent)
}
private fun verifyPermissions(grantResults: IntArray): Boolean {
if (grantResults.isEmpty())
return false
// 循环判断每个权限是否被拒绝
for (grantResult in grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return false
}
}
return true
}
private fun getDeniedPermissions(context: Context, vararg permissions: String): List<String> {
val deniedPermissions = ArrayList<String>()
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) {
deniedPermissions.add(permission)
}
}
return deniedPermissions
}
private fun checkPermissions(context: Context, vararg permissions: String): Boolean {
if (isOverMarshmallow) {
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) {
return false
}
}
}
return true
}
fun showTipsDialog(activity: Activity) {
AlertDialog.Builder(activity)
.setTitle("提示信息")
.setMessage("当前应用缺少必要权限,無法正常使用,请单击【确定】按钮前往设置中心进行权限授权。")
.setNegativeButton("取消", null)
.setPositiveButton("确定") { _, _ ->
activity.finish()
startAppSettings(activity)
}.show()
}
fun showTipsDialogWel(activity: Activity) {
AlertDialog.Builder(activity)
.setTitle("提示信息")
.setMessage("当前应用缺少必要权限,無法正常使用,请单击【确定】按钮前往设置中心进行权限授权。")
.setNegativeButton("取消") { _, _ -> activity.finish() }
.setPositiveButton("确定") { _, _ ->
activity.finish()
startAppSettings(activity)
}.show()
}
interface OnPermissionGrantedListener {
fun onPermissionGranted()
}
interface OnPermissionListener {
fun onPermissionGranted()
fun onPermissionDenied()
}
}
写的不好。大家自行修改吧。
Activity中的使用在onCreate中一开始调用一下代码:
MangoPermissionUtil.requestPermissions(this@IndexActivity, Constant.PERMISSION_OPERATION_CODE_SCAN, arrayOf(Manifest.permission.CAMERA), false, object : MangoPermissionUtil.OnPermissionGrantedListener {
override fun onPermissionGranted() {
//在这表示用户同意了权限申请。
//假如用户拒绝了权限申请在这儿我是没让他进入到应用中的效果如下
}
})
只要有任何一个权限用户没通过都会弹出这个Dialog。直到用户全部授权。。。。
点击取消退出应用。确定按钮去到设置界面为应用授权。。。。
下面是应用启动的场景(很舒服2333)
还有个剧TM恶心的问题这些所有的逻辑在除了小米6.xxx的设备上跑是没问题的。必须全部授权才能进入应用。但是小米6.xxx的设备上当我第一次拒绝了权限申请之后。第二次进入应用判断权限的时候它竟然在checkPermisssion的方法中给我返回了PERMISSION_GRANTED这就比较尴尬了。这样我是可以进入掉权限请求成功的回调。但是我进去之后确实没权限啊。对应权限相关的操作一样不能执行。。。不得不说小米的6.xxx设备是真的坑。。。。
还有一点油条用的时候还要在当前申请的Activity中调用一下来执行到油条中自定义的回调
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
MangoPermissionUtil.onRequestPermissionsResult(requestCode, permissions, grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
最后
对于一些比较特别的权限,比如文件的读写权限,一般在我们第一次开启APP的时候就要去获取了,假设我们一开始没有获取到这个权限,那么如果我的首页有轮播广告图,这个广告图是网络获取的,做了 * 缓存,这样就会到导致磁盘缓存无法写入。这边提供一个解决方法,就是在你引导APP启动的时候,就引导用户去获取权限,当用户拒绝的时候,应该给出弹出框并跳转对应的应用权限管理界面(需要对不同机型进行设置)。
可以参考微信的做法:
启动app,在闪屏页的时候向用户提出权限的申请
存储空间权限,关闭微信
电话权限,关闭微信
位置权限,关闭微信
进入app:
发照片时,申请照片权限
发语音时,申请麦克风权限
用户每次点击拒绝,都弹出自定义对话框,提示用户设置权限
来源:https://juejin.im/post/5a0444fb6fb9a0450c48dba2


猜你喜欢
- 1.使用response对象提供的sendRedirect()方法可以将网页重定向到另一个页面。重定向操作支持将地址重定向到不同的主机上,这
- 本文实例讲述了Android编程之Sdcard相关代码。分享给大家供大家参考,具体如下:1. 检测Sdcard是否可用:public sta
- 一、使用方式可以采用Transactional,配置propagation即可。打开org.springframework.transact
- 你还在生产节点开放Swagger吗,赶紧停止这种暴露接口的行为吧。学习目标快速学会使用注解关闭Swagger2,避免接口重复暴露。使用教程禁
- 本文实例为大家分享了Android仿微信二维码和条形码的具体代码,供大家参考,具体内容如下package your.QRCode.names
- 上一篇文章讲解了Spring Cloud 整合 nacos 实现服务注册与发现,nacos除了有服务注册与发现的功能,还有提供动态配置服务的
- 本文实例讲述了winform实现创建最前端窗体的方法。分享给大家供大家参考。具体实现方法如下:一、需求:1).需要这个窗体始终处于前端而且可
- 前言目前Google已经发布了Android13的正式版,虽然国内的手机能用上Android13还有一段时间,不过开发者们可以通过模拟器来体
- POST接口formdata传参模板记录var res = ""; HttpClient _httpClient = n
- 简单来说抽象类通常用来作为一个类族的最顶端的父类,用最底层的类表示现实中的具体事物,用最顶层的类表示该类族所有事物的共性。用abstract
- BufferedReader读取文件指定字符集问题默认的读取方式BufferedReader bufferedReader = new Bu
- 我们知道springboot中的Bean组件的成员变量(属性)如果加上了@Value注解,可以从有效的配置属性资源中找到配置项进行绑定,那么
- 在上篇文章给大家介绍了WebService教程详解(一)使用工具的原因:1、 使用工具可以更好的了解WebService请求的过程 2、 使
- ListView是开发中最常用的控件了,但是总是会写重复的代码,浪费时间又没有意义。最近参考一些资料,发现一个万能ListView适配器,代
- 本文初步讲述了C#的CLR内存原理。这里所关注的内存里面说没有寄存器的,所以我们关注的只有托管堆(heap),栈(stack), 字符串常量
- 一般我们在java中运行其它类中的方法时,无论是静态调用,还是动态调用,都是在当前的进程中执行的,也就是说,只有一个java虚拟机实例在运行
- PictureBox 控件可以显示来自位图、图标或者元文件,以及来自增强的元文件、JPEG 或 GIF 文件的图形。如果控件不足以显示整幅图
- 前言idea作为一个java开发的便利IDE工具,个人是比较喜欢的,今天来探索个小功能: 导出单个类文件为jar包!JAR文件的全称是Jav
- spring cloud zuul增加header传输在使用OAuth2.0传输权限认证,为了再调用其他的项目的时候获取token,必须在t
- 在此之前,脚本之家已经为大家整理了很多关于经典问题红黑树的思路和解决办法。本篇文章,是通过分析java.util.TreeMap源码,让大家