从6.0 MarshMallow开始,Android支持动态权限管理,即有些权限需要在使用到的时候动态申请,根据用户的选择需要有不同的处理,具体表现可以看下图:

本文并不关心权限适配的原理,原理可以参考[Android权限管理原理
](http://www.jianshu.com/p/9938...,这里只是针对6.0中的表现做适配,先思考以下几个问题:

  • 为什么6.0权限需要适配

  • 什么权限需要动态适配

  • 怎样动态适配权限

  • 怎么样实现第三方库,简化代码及适配流程 权限兼容库 PermissionCompat

  • 对于国产ROM的影响

为什么6.0需要权限适配

6.0之前Android的权限都是在安装的时候授予的,6.0之后,为了简化安装流程,并且方便用户控制权限,Android允许在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,当运行在Android 6.0 +的手机上时,就会调用6.0相关的API,达到动态控制权限的目的。但是,如果仅仅是将targetSdkVersion设置为23,而在代码层面没有针对Android 6.0做适配,就可能在申请系统服务的时候,由于权限不足,引发崩溃。

  • targetSDKVersion:该属性用于通知系统,您已针对目标版本进行测试,标识App能够适配的系统版本,有些新的API是只有新的系统才有的。

什么权限需要动态适配

并非所有的权限都需要动态申请,Android6.0将权限分为两种,普通权限跟敏感(危险)权限,普通权限是不需要动态申请的,但是敏感权限需要动态申请。

  • 1、普通权限(Normal permissions):不会泄露用户隐私,同时也不会导致手机安全问题。如网络请求权限、WIFI状态等,这类权限只需要在Manifest列出来,之后,系统会自动赋给APP权限:

    • ACCESS_NETWORK_STATE

    • ACCESS_NOTIFICATION_POLICY

    • ACCESS_WIFI_STATE

    • BLUETOOTH

    • BLUETOOTH_ADMIN

  • 2、敏感权限(Dangerous permissions):与普通权限对应,可能会影响用户的隐私,存储数据等,比如拍照、存储、通讯录、地理GPS等,这类权限需要在Manifest列出来,在需要的的时候,显示的请求用户准许。

    • CALENDAR

    • CAMERA

    • CONTACTS

    • LOCATION

    • PHONE

    • SENSORS

    • SMS

    • STORAGE

敏感权限的请求是按照分组进行提醒的,并非仅仅针对一条,比如通讯录的读取权限与写权限,只要一个权限获到,下次请求权限的时候会自动提供,当然也要请求。否则还是有问题。

  • 3、特殊权限(Special Permissions) --不在本文分析范围

There are a couple of permissions that don't behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS

怎样动态适配权限

对于敏感权限的适配有一个原则,那就是实时检查,因为权限随时可能被回收,比如用户可以在设置里面把权限给取消,但是APP并不一定知道,因此每次都需要检查,一旦没有,就需要请求,之后,根据返回结果处理后续逻辑。

实现步骤

  • 1、在Manifest中列出来

    无论普通权限还是敏感权限,都需要在Manifest中列出来,同时也是对6.0之前的版本的一种兼容。<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.snail.labaffinity"><uses-permission android:name="android.permission.CAMERA"/><uses-permission android:name="android.permission.CALL_PHONE"/>
  • 2、需要时,显示的请求

在权限没被授予前提下,系统会显示授权对话框,让用户操作,目前授权对话框不可定制,不过可以在申请之前添加一些解释,告诉用户为什么需要该权限,但是Google提醒,不要做过多的解释,可能会使用户感到厌烦,用法如下:

ActivityCompat.requestPermissions(target.getActivity(), permissions, requestCode);public static void requestPermissions(final @NonNull Activity activity,final @NonNull String[] permissions, final int requestCode) {if (Build.VERSION.SDK_INT >= 23) {ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);} else if (activity instanceof OnRequestPermissionsResultCallback) {Handler handler = new Handler(Looper.getMainLooper());handler.post(new Runnable() {@Overridepublic void run() {final int[] grantResults = new int[permissions.length];PackageManager packageManager = activity.getPackageManager();String packageName = activity.getPackageName();final int permissionCount = permissions.length;for (int i = 0; i < permissionCount; i++) {grantResults[i] = packageManager.checkPermission(permissions[i], packageName);}((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(requestCode, permissions, grantResults);}});}
}
  • 3、处理授权回调

    • 兼容6.0之前的处理:在这里只需要处理获得权限即可,因为6.0之前只存在Install权限,一旦安装,所有权限都是默认授予的,虽然国内ROM对权限管理做了自己的一些定制,但基本都是兼容的。

    • 需要对6.0的授权成功、失败、永不询问做处理

           public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if(this.mOnGrantedListener != null) {<!--6.0之前-->if(PermissionUtils.getTargetSdkVersion(this) < 23 && !PermissionUtils.hasSelfPermissions(this, permissions)) {this.mOnGrantedListener.onGranted(this, permissions);return;}             //<!--6.0之后-->  需要根据结果进行验证if(PermissionUtils.verifyPermissions(grantResults)) {this.mOnGrantedListener.onGranted(this, permissions);} else if(!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {this.mOnGrantedListener.onNeverAsk(this, permissions);} else {this.mOnGrantedListener.onDenied(this, permissions);}}}

具体APP中不同的实现方案

  • 1、简单的封装回调

  • 2、基于APT,采用注解方式简化编码逻辑,自动封封回调

先看一下直接回调的方式

采用最直接的回调

首先在基类Activity或者Fragment中统一设置授权回调监听,这里我们用一个

 public class BasePermissionCompatActivity extends AppCompatActivity {private SparseArray<OnGrantedListener<BasePermissionCompatActivity>> mOnGrantedListeners = new SparseArray<>();@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);OnGrantedListener<BasePermissionCompatActivity> listener = mOnGrantedListeners.get(requestCode);if (listener == null)return;if (PermissionUtils.verifyPermissions(grantResults)) {listener.onGranted(this, permissions);} else {if (PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {listener.onDenied(this, permissions);} else {listener.onNeverAsk(this, permissions);}}}@Overrideprotected void onDestroy() {super.onDestroy();mOnGrantedListeners.clear();mOnGrantedListeners = null;}public void requestPermissions(final @NonNull String[] permissions, OnGrantedListener<BasePermissionCompatActivity> onGrantedListener) {int requestCode = getNextRequestCode();ActivityCompat.requestPermissions(this, permissions, requestCode);mOnGrantedListeners.put(requestCode, onGrantedListener);}private static int sNextCode;private static int getNextRequestCode() {return sNextCode++;}
}

之后在需要时候的请求,并根据结果处理后续逻辑即可。

   requestPermissions(activity, P_CAMERA, new OnGrantedListener() {// 根据permissions自行处理,可合并,可分开@Overridepublic void onGranted(SecondActivity target, String[] permissions,int requestCode) {}@Overridepublic void onDenied(SecondActivity target, String[] permissions,int requestCode) {}@Overridepublic void onNeverAsk(SecondActivity target, String[] permissions,int requestCode) {}@Overridepublic void onShowRationale(SecondActivity target, String[] permissions,int requestCode) {});

上面的方法比较直接,灵活,不过每次都要自己实现回调监听Listener,接下来看第二种实现,基于APT,通过注解的方式,自动添加Listener,这种实现参考了ButterKnife的实现方式。

基于APT与注解,编译过程中生成代码,自动添加回调

  • 1、基于APT,定义一系列Annotation,并动态生成辅助Listener类

  • 2、添加Android支持库,在基类统一处理回调,

  • 3、添加工具类,连接绑定Listener与Activity(Fragment)

相应的实现分三个库:

  • 注解库

  • APT生成支持库

  • Android支持库

注解库:

主要用来定义一些回调方法注解、及请求实体的类注解

* ActivityPermission
* FragmentPermission
* OnDenied
* OnGranted
* OnGrantedListener
* OnNeverAsk
* OnShowRationale

APT生成支持库

主要用来在编译阶段,动态生Listener类

PermissionProcessor.java

部分参考代码:


@AutoService(Processor.class)
public class PermissionProcessor extends AbstractProcessor {private Elements elementUtils;private Set<Class<? extends Annotation>> getSupportedAnnotations() {Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();annotations.add(OnDenied.class);annotations.add(OnGranted.class);annotations.add(OnNeverAsk.class);annotations.add(OnShowRationale.class);return annotations;}@Overridepublic synchronized void init(ProcessingEnvironment env) {super.init(env);elementUtils = env.getElementUtils();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (!checkIntegrity(roundEnv))return false;Set<? extends Element> elementActivities = roundEnv.getElementsAnnotatedWith(ActivityPermission.class);Set<? extends Element> elementFragments = roundEnv.getElementsAnnotatedWith(FragmentPermission.class);return makeListenerJavaFile(elementActivities) && makeListenerJavaFile(elementFragments);}...

Android支持库

主要会封装了一些工具类,基类以及对回调的处理

* BasePermissionCompatActivity.java
* BasePermissionCompatFragment.java
* PermissionCompat.java
* PermissionUtils.java

参考代码:


public class PermissionCompat {private static int sNextRequestCode;static final Map<Class<?>, OnGrantedListener> BINDERS = new LinkedHashMap<>();// 分批次请求权限public static void requestPermission(BasePermissionCompatActivity target, String[] permissions) {Class<?> targetClass = target.getClass();try {// 找到监听Listener类,并实例一个OnGrantedListener<BasePermissionCompatActivity> listener = findOnGrantedListenerForClass(targetClass, permissions);if (PermissionUtils.hasSelfPermissions(target, permissions)) {listener.onGranted(target, permissions);} else if (PermissionUtils.shouldShowRequestPermissionRationale(target, permissions)) {// 拒绝过,再次请求的时候,这个函数是否有必要,不在询问后,返回false,第一次返回false,//listener.onShowRationale(target, permissions);startRequest(target, listener, permissions);} else {startRequest(target, listener, permissions);}} catch (Exception e) {throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);}}private static void startRequest(BasePermissionCompatActivity target, OnGrantedListener listener, final @NonNull String[] permissions) {target.setOnGrantedListener(listener);ActivityCompat.requestPermissions(target, permissions, getNextRequestCode());}

使用

  • 1、Activity继承BasePermissionCompatActivity

  • 2、用注解写回调函数,支持权限分组,跟单独处理,但是每个分组都要写自己的回调函数(目前回调函数,不支持参数)

  • 3、回调必需配套,也就是一个权限必须对应四个函数,否则编译不通过

  • 4、请求的权限必须有回调函数,不然报运行时错误--崩溃

    @ActivityPermission
    public class PermssionActivity extends BasePermissionCompatActivity {

     。。。@OnGranted(value = {Manifest.permission.CAMERA})
    void granted() {LogUtils.v("granted");
    }@OnDenied(value = {Manifest.permission.CAMERA})
    void onDenied() {LogUtils.v("onDenied");
    }@OnNeverAsk(value = {Manifest.permission.CAMERA})
    void OnNeverAsk() {LogUtils.v("OnNeverAsk");}
    @OnShowRationale(value = {Manifest.permission.CAMERA})
    void OnShowRationale() {LogUtils.v("OnShowRationale");
    }
    <!--何时的时机调用-->@OnClick(R.id.get)
    void get() {PermissionCompat.requestPermission(this, new String[]{Manifest.permission.CAMERA});
    }

    }

国产ROM兼容性

6.0之前权限管理即不是原生功能又没有制定相应标准,每个厂家的实现都是完全不同的,虽然4.3 Google官方试图推出AppOpsManager来动态适配权限管理,但由于不成熟,一直到6.0也没走向前台。不过,看6.0之前国内ROM的表现,基本是在每个服务内部触发鉴权请求,对原生权限的判断并没多大影响,所以兼容没太大问题。

最后附上GitHub Demo及第三方库链接 权限兼容库 PermissionCompat

作者:看书的小蜗牛
原文链接: Android6.0权限适配及兼容库的实现

参考文档

1、Requesting Permissions at Run Time
2、PermissionDispatcher
3、Android6.0权限适配之WRITE_EXTERNAL_STORAGE(SD卡写入)

Android6.0权限适配及兼容库的实现相关推荐

  1. android 6.0 短信权限,Android6.0权限适配

    Code4Android .jpg 前言 现在谈论Android权限适配可能有点没必要,因为网上关于权限适配的文章很多,搜一下Android6.0权限适配关键词能搜到一堆文章,而且很多写的还很不错.不 ...

  2. android6.0权限适配RxPermissions

    1.直接获取权限(使用Retrolambda使代码更加简洁,当然并不是必须使用): // 必须在初始化阶段调用,例如onCreate()方法中 RxPermissions.getInstance(th ...

  3. Android6 0权限机制(一):介绍

    本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布 Android6.0权限机制(一):介绍 Android6.0权限机制(二):封装 Android6.0权限机制(三):6. ...

  4. android拍照所需的权限,eclipse --- Android拍照,相册选择图片以及Android6.0权限管理...

    [实例简介] eclipse --- Android拍照,相册选择图片以及Android6.0权限管理 [实例截图] [核心代码] camreainandroidm └── camreainandro ...

  5. Android6.0权限大全和权限分类

    自从出了Android6.0权限管理之后,再也不能像以前那样粘贴复制了,必须认识权限了,所以总结一下方便以后自己使用. 一.所有权限 访问登记属性 android.permission.ACCESS_ ...

  6. Android教程 -05 Android6.0权限的管理

    视频为本篇博客知识的讲解,建议采用超清模式观看, 欢迎点击订阅我的优酷 height="498" width="510" src="http://pl ...

  7. android关闭权限管理,Android6.0权限管理以及使用权限该注意的地方

    Android 6.0 Marshmallow首次增加了执行时权限管理,这对用户来说,能够更好的了解.控 制 app 涉及到的权限.然而对开发人员来说却是一件比較蛋疼的事情.须要兼容适配,并保证程序功 ...

  8. Android6.0权限

    前言 谷歌在2015年8月份时候,发布了Android 6.0版本,代号叫做"棉花糖"(Marshmallow ),其中的很大的一部分变化,是在用户权限授权上,或许是感觉之前默认授 ...

  9. Android打电话功能权限报错,从打电话权限报错看Android6.0权限变化

    引言:去年Android 6.0发布后,其新引入的(Requesting Permissions at Run Time)运行时权限就备受开发者关注,随着今年国内手机厂商对6.0系统的普及,觉得大家有 ...

最新文章

  1. 全面支持三大主流环境 |百度PaddlePaddle新增Windows环境支持
  2. day16——函数式编程和内置函数
  3. 四十七、面试前,必须搞懂Java中的线程池ThreadPoolExecutor(上篇)
  4. Thrift序列化字节数组存取redis VS 对象转Json存取Redis
  5. linux-远程管理-xshell
  6. 系列 | 高性能存储-MySQL数据库之存储过程揭秘
  7. C++ Template 使用简介
  8. [图示]抢逼围:项目开发3字经
  9. 同济大学 线性代数 第六版 pdf_线性代数同济大学第六版第一章课后习题答案
  10. Revisiting RCNN: On Awakening the Classification Power of Faster RCNN解读
  11. oracle 11g 重置,oracle数据库重置
  12. 【微信小程序系列】微信小程序超简单教程,基本语法,获取用户基本数据信息,实现对云数据库的增删改查及小程序外部api的引用示例(附源码)
  13. matlab遗传算法配送路径,基于遗传算法的生鲜配送的路径优化问题
  14. mysql-5.5.20-winx64_mysql-5.7.20-winx64命令安装
  15. Android动画制作
  16. 字符串函数的使用和剖析(三)
  17. 树莓派开发笔记(十一):蓝牙的使用,BlueZ协议(双树莓探测rssi并通过蓝牙互传获取的rssi信号强度)
  18. flask url_for用法
  19. RationalDMIS 2020直线度评价
  20. 低频压电陶瓷驱动器功率放大器的测试研究

热门文章

  1. zookeeper入门学习之java api会话建立《四》
  2. Akka异步通讯《three》译
  3. TensorFlow入门(1)
  4. 高精度双目立体视觉测量
  5. ASP.NET 运行时详解 揭开请求过程神秘面纱
  6. 设计模式之桥接模式(Java语言描述)
  7. 20款绝佳的HTML5应用程序示例
  8. 并查集--Java实现
  9. 关于折半查找的细节思考
  10. Matlab图形修饰之裁剪处理