关于Android 6.0权限的动态适配详解
前言
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:
- 发照片时,申请照片权限
- 发语音时,申请麦克风权限
- 用户每次点击拒绝,都弹出自定义对话框,提示用户设置权限
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。