Android开发之利用注解简单实现动态权限申请

搞开发的同学大家都知道,在Google推出6.0以后,提出了一个动态权限申请机制,逐步解决Android的安全隐患,算是在不断的进步吧,那么这会对我们的开发带来什么影响了?

如果我们在开发的时候,如果将项目的targetVersion指定为23以上,那么系统就会认为我们已经对新特新做了适配,在新的动态申请权限模式下,权限分为了两类,一种是普通的权限,一种是系统认为的危险权限,在之前的开发中,我们只需要在Manifest文件中申明我们工程所需要的权限,然后再程序安装的时候统一的向用户进行申请权限,这样在使用的过程中就不会出现权限拒绝的异常发生了。但是在6.0的系统中,系统不在为默认进行了新系统适配的程序自动授予危险的权限在APP安装的时候向用户申请了,而是在程序中又我们在使用的地方调用方法动态的向程序申请,这就有点恶心了。。。

那么我们就来先贴一张图,看看那些权限是我们需要动态申请的:
Android开发之利用注解简单实现动态权限申请_第1张图片

从上图中我们看见系统将我们的权限分为了几个权限组,每个组里面都有具体的权限,在申请权限的时候,如果我们同意了一个组下的某个权限,那么系统会自动授予改组下的其他权限,例如读写外部存储卡就只需要申请一次。好了,背景就介绍到这,下面进入文章的正题。。。

一 注解机制

在Android中我们经常看见有很多的注解,还有就是我们开发使用的ButterKnife也是使用注解的机制实现的,就拿我们经常看见的@override来说吧,一般当我们子类重写父类的方法的时候,会在子类的头上出现一个这样的注解,当然我们如果将它删除也并不会有什么影响,但是,如果我们子类中有一个方法,而在父类中这个方法不存在的话,我们就在编译过程就会出错,就如下图所示的错误:

这里写图片描述

我们在定义一个注解的过程中我们一般需要知道的几个知识点:

  • 我们的注解和类一样,使用@interface进行申明
  • 指定的注解的运行时机@Retention(RetentionPolicy.RUNTIME),这里我们指定是在运行时,有如下几种参数:Android开发之利用注解简单实现动态权限申请_第2张图片
  • 指定注解的类型@Target(ElementType.FIELD),它有如下几种参数:Android开发之利用注解简单实现动态权限申请_第3张图片
  • 然后就是定义我们的注解接收的参数,它可以接受基本的数据类型,String,数组等…

简单的介绍完了注解的机制,我们就来看看一个完整的注解定义长什么样子:
Android开发之利用注解简单实现动态权限申请_第4张图片

这时我们的注解就已经完成一半了,剩下的就是写我们的注解解析类了,这个我们接下来和实现Android的动态权限申请一起讲。

二 Android注解实现权限申请

首先我们定义一个注解类,让我们在在需要权限的时候进行申明,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface QueryPermission { 
    String[] permission();//申明我们接收一个字符数组
}

在上面的代码中我们让该注解运行在程序的运行期间,并且申明这个注解可以是在类前面和方法前面使用

@QueryPermission(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
public class WxDetailActivity extends BaseAndroidActivity {

上面的代码就是使用我们的注解,我们给注解传了一个写外部存储卡的字符串,有的人就觉得奇怪了,我们明明需要接收的是一个字符数组,可我们只传递了一个字符串,其实这样的可以得,甚至我们还可以将permission=去掉,那我们如何传递一个数组数据了?

permission={str1,str2,...}

传递数组数据的时候,我们只需要使用花括号括起来,并使用“,”号进行分隔就行了,那么我们的前期工作就做完了,这时我们就需要一个解析类来解析我们的注解了:

    public static void initPromissions(Activity targetAC){
        if (null==targetAC||!(targetAC instanceof Activity))
            return ;
        QueryPermission annotation = targetAC.getClass().getAnnotation(QueryPermission.class);
        if (null!=annotation){
            String[] promissons = annotation.promissons();
            ArrayList<String> requests=new ArrayList<>();
            for (String s:promissons){
                int result = ContextCompat.checkSelfPermission(targetAC, s);
                if (result!= PackageManager.PERMISSION_GRANTED){
                    requests.add(s);
                }
            }
            String[] promissionArr=new String[requests.size()];
            requests.toArray(promissionArr);
            if (promissionArr.length>0)
            ActivityCompat.requestPermissions(targetAC,promissionArr,PROMISSION_CODE);
        }
    }

我们来分析下上诉的代码,我们在类中定义了一个静态的方法,我们传入一个Activity对象,这个对象是我们后期申请权限必须的,然后进行非空判断,这个没啥说的,接下我们通过反射的方式拿到Activity的类,在Class中有个方法getAnnotation(注解的类型),通过这个方法我们可以拿到类头上指定类型的注解,这是类头上的,那么成员变量,方法上的注解我们也可以通过类似的方法拿到,例如下面的代码就是拿到成员变量上的注解:

        Field[] fields = targetAC.getClass().getDeclaredFields();
        if (null==fields||fields.length>0)
            return;
        for (Field field:fields){
            QueryPermission annotation = field.getAnnotation(QueryPermission.class);
            if (null==annotation)
                continue;
            String permission = annotation.value();

这就是一个拿到成员变量的注解的方式,一些findviewById的注解框架就是通过上诉的方式拿到的。

好,我们接着往下,当我们拿到注解后,我们就可以获取我们在注解中传入的参数了:

String[] promissons = annotation.promissons();

看到这里有人就会好奇了,为什么我们还需要额外的定义一个集合,这是因为我们需要一个长度可以变化的集合来暂时存储我们需要请求的权限集。在Android的support v4包下,Google为我们提供了一个向下兼容的方法用来判断我们需要的权限是否已经被授权了,它接收一个activity和一个我们需要的权限的字符串作为变量,调用后会给我们返回一个int的结果:

 int result = ContextCompat.checkSelfPermission(targetAC, s);

这个结果有两种:

  • PackageManager.PERMISSION_GRANTED 值为:0
  • PackageManager.PERMISSION_DENIED 值为:-1

当我们将结果进行比较,如果我们的权限返回的是0,那么我们就不用再申请了,如果是-1,就需要进行动态申请,于是将其加入我们的集合中。

接下来就是发起权限申请了,这个函数也是在support v4包下的,可能有的人发现我们的v4包下没有这两个方法,那么兄弟,你该更新下你的sdk了,申请权限的函数如下,第一个参数接收一个activity,第二个是一个权限的字符数组,第三个是一个int的请求码:

 ActivityCompat.requestPermissions(targetAC,promissionArr,PROMISSION_CODE)

那么这样我们的注解就可以使用了,我们来看下如何使用:

  • 在需要权限的activity类前加上注解,并传递需要的权限参数
  • 在oncreate方法中调用initPromissions(this)静态方法注入activity

这是我们在6.0并且targetVersion的机器上(这是我们可以先查看下设置页面的程序列表里面,点击我们的应用查看它的权限,是否有没有授权的,如果都授权了是不会出现现象的),启动我们的activity的时候就会弹出权限申请提示框,这个是系统的,例如:

Android开发之利用注解简单实现动态权限申请_第5张图片

这是我们可以看见一个如上图的提示框,有一点值得注意的是,我们如果每次调用请求权限的函数只传递一个权限字符,那么,系统就会弹一次这个窗口,如果连续多次会弹好几个,这样有点蛋疼啊,如果我们传递的是一个字符数组,那么就会在一个提示框里面分好几页,有时我们请求了三个都没有被授权的权限的时候,只需要我们授权两次,这是因为有两个权限是属于同一个权限组的。

这时还没有完,那么我们如何才能知道用户点击了什么了?这是我们就得需要在Activity中重写一个方法,有点类似onActivityResult:

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

这个函数中第一个参数是我们请求时的请求码,第二个参数是我们的申请的权限数组,第三个参数就是用户点击的结果集,0为同意,-1为不同意。然后就在这里面实现你的具体的逻辑。

可能大家也发现了上面的对话框中有一个不再显示的checkbox选项,当勾选后,用户会只能选择拒绝,如果用户选择后,我们以后请求该权限都不会在显示提示框了,但是回掉函数会执行,并且总是返回-1,那我们就想问了,是否也有一个这样的回掉函数了?不好意思,没有,这尼玛不坑爹吗?那我以后想同意该怎么办了,这是有两个办法,要么卸载重新安装,或者提示用户去设置页面中,上面介绍过的权限查看页面手动授予权限。那我们怎么去提示用户了?系统提供了一个方法:

   ActivityCompat.shouldShowRequestPermissionRationale(activity,permission);

这个方法会告诉我们当前权限是否用户点击了不在显示,那么我们就可以通过它来提示用户去设置页面修改权限了。

可能大家看到这里也发现了,这种形式的权限申请机制非常的鸡肋,只在我们activity初始化的时候去申请,一但用户取消了,在点击了我们需要权限的方法后,程序依然会崩溃掉,博主也是为了偷懒,因为博主的项目没有那么强的用户体验,只需要保证用户使用中不崩溃,并且权限是必须全部授权的,所以在申请的回调中加了判断,如果用户有没有授予的权限,我就会弹出对话框提示用户,然后调用actvity的recreate方法进行重启,再次执行权限申请,如果点击了不在显示,那么我会在处理类中运用上面的方法判断出来,强制调到设置页面去开启权限,因此这样的软件可能不适合大部分的软件,希望大家更具自己的项目需求来采取措施,目前也有第三方的框架来处理这个了,但我觉得没必要那么麻烦!

下面就是回调中的做法:

Android开发之利用注解简单实现动态权限申请_第6张图片

处理类中的方法就不贴出来了,大致逻辑就是那样,文中有不足的地方,希望大家指正!

你可能感兴趣的:(Android)