通过AOP的思想 打造万能动态权限申请框架Demo完全解析

AOP优雅权限框架详解(以及更多面试题)

https://github.com/xiangjiana/Android-MS

gradle配置

  • 在project的 build.gradle 添加 aspectJ gradle插件

    }
    dependencise {
         classpath ‘com.android.tools.build:gradle:3.5.0‘
    
        //1_1.grade-android-plugin-aspectjx
        classpath ‘com.hujiang.aspectjx:gradle-android-plugun-aspectjx:2.0.5‘
    
       //2_1.android-maven-gradle-plugin
       classpath ‘com.github.dcendents:android-maven-gradler-plugin:2.1‘//ADD
       //NOTE: Do not place your application dependencise here; they belong
      //in the individual module build.gradle files
    }
  • permission model 的 build.gradle 引入 aspect类库
  • app module 的build.gradle中启用aspectJ插件,并且引入 permissionmodule

Java代码

这里有个坑:@PermissionDenied@PermissionDeniedForever 修饰的方法,必须有且仅有一个int类型参数, 返回值随意.

  • zpermission module
    这里包含了框架的核心代码,现在一步一步讲解

类结构图

3个注解@PermissionDenied @PermissionDeniedForever @PermissionNeed

 /**
 * 被此注解修饰的方法,会在方法执行之前去申请相应的权限,只有用户授予权限,被修饰的方法体才会执行
 */
  @Target(ElementType.METHOD)//此注解用于修饰方法
  @Retention(RetentionPolicy.RUNTIME)//注解保留到运行时,因为可能需要反射执行方法(上面说了修饰的是方法!)
  public @interface PermissionNeed {

    String[] permissions();//需要申请的权限,支持多个,需要传入String数组

    int requestCode()default 0;//此次申请权限之后的返回码
  }  
  /**
  * 被此注解修饰的方法,会在权限申请失败时被调用
  */
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface PermissionDenied {
  }
  /**
  * 被此注解修饰的方法,会在用户永久禁止权限之后被调用
  */
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface PermissionDeniedForever {
  }

处理权限回调结果的接口 IPermissionCallback

  /**
 * 权限申请结果接口
 */
  public interface IPermissionCallback {

  /**
  * 授予权限
  */
  void granted(int requestCode);

  /**
  * 这次拒绝,但是并没有勾选"以后不再提示"
  */
  void denied(int requestCode);

  /**
  * 勾选"以后不再提示",并且拒绝
  */
  void deniedForever(int requestCode);
}

以上都是事先要预备好的东西,接下来进入核心

PermissionAspect

  @Aspect
  public class permissinbAspect {
     private static final String TAG = "PermissionAspectTag";
     private final String pointcutExpression="execution(@com.zhou.zpermission.annotation.PermissionNeed * *(..)) && @annotation(permissionNeed)";

     @Pointcut(value = pointcutExpression)
     public void requestPermission(PermissionNeed permissionNeed) {
        Log.d(TAG,"pointCut 定义切入点");
     }
     @Around("requestPermission(permissionNeed)")
        Log.d(TAG,"pointCut 定义切入点");
   ....
 }

此段代码解读如下:

  • 使用 @Aspect注解来修饰类 , @Aspect是来自 AspectJ框架的注解,被它修饰的类,在编译时会被认为是一个切面类
  • 使用 @Pointcut 注解来修饰方法 requestPermission(),被它修饰的方法会被认为是一个切入点.所谓切入点,就是 面向切面编程时,我们无侵入式地插入新的逻辑,总要找到一个确切的位置,我们要知道程序执行到哪一行的时候,轮到我们出场了!切入点,一定是方法, 不能是随意哪一段代码!

切入点可以是以下类型,不同的类型有不同的语法,我目前使用的是 method execution ,也就是 函数执行时。这意味着,当切入点的方法即将开始执行的时候,我们插入的逻辑将会被执行。与之类似的有一个 method call ,这个不一样,这个是在切入点的方法 被调用时,也就是说,当侦测到该方法被外界调用的时候,而非方法自己执行。这两者有细微差别。至于其他的类型,暂且按下不详述。

除了类型之外,这里还有一个重点,那就是 MethodSignature的概念,这个类似于 jni里的方法签名,是为了标记一个或者一类方法, AspectJ框架通过这个方法签名,来确定 JVM的所有class对象中,有哪些方法需要被插入 新的逻辑。
具体的签名的语法规则为:


看不懂? 看不懂就对了,举个例子:
execution(@com.zhou.zpermission.annotation.PermissionNeed**(..))&&@annotation(permissionNeed)

这是Demo中我这么写的,现在逐步解析:

  • execution 表示方法执行时作为切入点
  • @com.zhou.zpermission.annotation.PermissionNeed 表示 切入点的方法必须有这个注解修饰
  • **(..)) 这个比较诡异,我们知道,一个方法写完整一点可能是这个样子 private void test(int a)

但是如果我们不计较 访问权限,不计较返回值类型,也不计较 函数名,甚至不计较参数列表的话,就可以写成这个样子 **(..)) . 表示任意方法

除此之外,还有后半截 &&@annotation(permission),它的含义为:

切入点方法需要接收来自 注解的参数。
即 切入点 @Pointcut 规定切入点的时候,只识别被 @com.zhou.zpermission.annotation.PermissionNeed 标记的方法,但是这个 @com.zhou.zpermission.annotation.PermissionNeed注解,是有自己的参数值的,所以,必须传入这个值给到切入方法 requestPermission(PermissionNeedpermissionNeed) 去使用。
有点绕!一张图说清楚:

图中3个字符串必须一摸一样,不然编译就会报错,而且报错原因还不明确。

使用 @Around 注解来修饰 方法 doPermission(),被它修饰的方法会被认为是一个 切入策略。

Around注解的参数为:
"requestPermission(permissionNeed)", 也就是 pointcut修饰的方法名(形参名)

在我们已经定义好切入点

requestPermission(PermissionNeedpermissionNeed)的前提下,如果程序已经执行到了切入点,那么我是选择怎么样的策略, 目前所选择的策略是 Around ,也就是,完全替代切入点的方法,但是依然保留了 执行原方法逻辑的可能性 joinPoint.proceed();

除了@Around策略之外,还有以下:

PermissionAspect类的作用是: 定义切入点和切入策略,那么现在我们确定切入点是 被注解 @PermissionNeed修饰的方法,切入策略是 @Around,那么,切入之后我们做了哪些事呢?

接下往下看...

PermissionAspectActivity

  public class permissionAspectActivity extends AppcompatActivity {
     private final static String permissionsTag = "permissions";
     private final static String requestCodeTag = "requestCode";
     private static IPermissionCallback mCallback;

    /**
     * 启动当前这个Activity
     */
   public static void startActivity(Context context, String[] permissions,int requestCode,IPermissionCallback callback) {
      Log.d("PermissionAspectTag","context is : "+ context.getClass().getSimpleName());
     if (context  == null) return;
     mCallback = callback;
     //启动当前这个Activiyt并且取消切换动画
    Intent intent = new Intent(context,PermissionAspectActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_CLEAR_TOP);
    //开启新的任务栈并且清除栈顶...为何要清除栈顶
    intent.putExtra(permissionsTag, permissions);
    intent.putExtra(requestCodeTag, requestCode);

    context.startActivity(intent);
    //利用context启动activity
    if (context instanceof Activity) {
    //并且,如果是activity启动的,那么还要屏蔽掉activity切换动画
        ((Activity) context).overridePendingTransition(0, 0);
    }

  }

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     Intent intent = getIntent();
     String[] permissions = intent.getStringArrayExtra (permissionsTag);
     int requestCode = intent.getIntExtra(requestCodeTag,0);

     if (PermissionUtil.hasSelfPermissions(this, permissions)) {
         mCallback.granted(requestCode);
         finish();
         overridePendingTransition(0,0);
     } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
         requestPermissions(permissions, requestCode);
    }

  }

   @Override
   public void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions, @NonNull int[] grantResults) {
     //现在拿到了权限的申请结果,那么如何处理,我这个Activity只是为了申请,然后把结果告诉外界,所以结果的处理只能是外界传进来
     boolean granted = PermissionUtil.verifyPermissions(grantResults);
     if (granted) {
     //如果用户给了权限
          mCallback.granted(requestCode);
     } else {
          if (PermissionUtil.shouldShowRequestPermissionRationale(this ,permissions)) {
             mCallback.denied(requestCode);
          } else {
             mCallback.deniedForever(requestCode);
         }

     }
     finish();
     overridePendingTransition(0,0);
   }

  }

解读:

1.提供一个静态方法 publicstaticvoidstartActivity(Contextcontext,String[]permissions,intrequestCode,IPermissionCallbackcallback),
用于启动自己 PermissionAspectActivity,接收的参数分别为: context,需要的权限数组,权限返回码,权限结果回调接口

  1. onCreate方法中,检查是否已经有想要申请的权限,如果有,直接调用 mCallback.granted(requestCode); 并且结束自身,并且要注意隐藏Activity的切换动画。如果没有,那么,就去requestPermissions(permissions,requestCode);申请权限。
  2. 处理权限申请的回调,并且分情况调用 mCallback的回调方法,然后结束自身

需要注意:
PermissionAspectActivity必须在module的清单文件中注册

并且 要定义它的 theme使得Activity完全透明

Gif图效果演示:

AOP思想以及常用AOP框架

所谓AOPApsectOrientedProgramming) 面向切面编程。

此概念是基于OOPObjectOrientiedProgramming)面向对象编程。在OOP中,我们可以把不同的业务功能都分成一个一个的模块,然后每一个模块有自己的专一职责,从而优化编程过程,降低编程犯错几率。但是随着OOP类的数量的增加,我们会发现,在某一些业务类中,经常有一些相同的代码在重复编写,但是无可奈何,比如日志打印/动态权限申请/埋点数据上报/用户登录状态检查 /服务器端口连通性检查 等等。这些代码,我们虽然可以他们抽离出来整理到一个个专一的模块中,但是调用的时候,还是到处分散的,并且这些调用还***了本来不直接相关的业务代码,让我们阅读业务代码十分费劲。

而AOP的出现,就是基于OOP的这种缺陷而出现的优化方案。利用AOP,我们可以对业务逻辑的各个部分进行隔离,使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率,减少犯错概率。

画图表示:

如上图,OOP中,同样的一段过程,我们把登录检查,权限检查,埋点上报的调用代码写了3遍,然而都是雷同代码,只是参数不同而已。而,换成AOP的思想来编码。则是如下:

所采取的方案为:

在class A , B, C中 找到切入点,然后在切入点插入共同的逻辑,而不是多次编写雷同的代码。

本文的Demo中,插入相同的逻辑,使用的是 Java自定义注解[email protected]切面类[email protected]切入点[email protected]切入策略 的方式。这只是AOP方案的一种,叫做 AspectJ

除此之外,Android开发中常用的AOP方案还有:

(Java注解存在3个阶段,一个是源码期,一个是编译期,一个运行期)

APT

Java的注解解析技术(AnnotationProcessingTool), Apt的作用时期,是 通过 自定义注解解析类(extends AbastractProcessor),对自定义注解进行解析,然后通过JavaPoet这种java类生成工具,来生成编译期才会有的.java(源码中并没有),然而我们源码中却可以使用这个类。

ASM

Asm是Java的字节码操作框架,它可以动态生成类或者增强既有类的功能。理论上,它可以对class文件做任何他想做的事。包括,改变class文件的内容,或者生成新的class。严格来说AspectJ底层就是ASM,只不过AspectJ帮我们做了ASM框架做起来很麻烦,容易出错的事情,让我们可以简单的通过 @Aspect @PointCut @Around 这样的注解,就能完成AOP面向切面编程。但是,ASM作为AspectJ的祖宗,某些时候依然可以完成AspectJ所无法触及到的功能, 就像是c/c++作为Java的祖宗, 现在依然有自己不可替代的作用。

AspectJ AOP框架的深入原理研究

本来想写成一篇,但是发现篇幅太长,留个尾巴,下一篇,解析AspectJ是如何通过@注解的方式来插入逻辑的。

文章太长了,顺手留下GitHub链接,需要获取相关内容的可以自己去找
https://github.com/xiangjiana/Android-MS

原文地址:https://blog.51cto.com/14541311/2458759

时间: 2024-10-02 07:32:30

通过AOP的思想 打造万能动态权限申请框架Demo完全解析的相关文章

Android6.0动态权限申请

goggle在Android6.0要求部分权限需要动态申请,直接下载AndroidManifest.xml中无效 6.0权限的基本知识,以下是需要单独申请的权限,共分为9组, 每组只要有一个权限申请成功了,就默认整组权限都可以使用了. group:android.permission-group.CONTACTS permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS perm

Android6.0动态权限申请步骤以及需要注意的一些坑

因为工作需要,简单研究了一下Android6.0权限申请,在Google提供的sample的基础上,写了一个简单的demo.算是自己的笔记吧,可能会比较混乱,主要是方便以后查看.后期有别的问题,随时更新~ 本demo github下载地址!!! Google提供的demo的下载地址 6.0权限的基本知识,以下是需要单独申请的权限,共分为9组,每组只要有一个权限申请成功了,就默认整组权限都可以使用了. group:android.permission-group.CONTACTS permissi

Android 6.0 动态权限申请注意事项

<span style="font-size:24px;color:#ffff00;background-color: rgb(0, 0, 153);"><strong>Android 6.0 权限区分</strong></span> Android 6.0 为了保护用户隐私,将一些权限的申请放在了应用运行的时候去申请, 比如以往的开发中,开发人员只需要将需要的权限在清单文件中配置即可,安装后用户可以在设置中的应用信息中看到:XX应用以获

Android动态权限申请

从Android 6.0开始,权限不再是在manifest文件中粘贴一下即可,这时候权限也正式走进大家的视野.项目的6.0适配就是我做的,当时没有仔细总结,最近在另一个项目添加权限的时候发现,同一个功能都没有添加申请权限的代码,一个会挂一个不会,花了几个小时在这个小问题上.因此多花点时间总结一下权限问题. Android系统权限的概念 Android是一个权限分隔的操作系统,每个应用都有独特的系统标识.一般情况下,应用没有权限执行对其它应用.系统.用户可能有不利影响的操作.每个应用都在应用沙盒中

Android权限之动态权限

安卓系统的权限管理机制从API 23 (也就是Android 6.0 又叫做 Android M,)之后发生了比较大的改变,在一些比较危险的权限上要求必须申请动态权限,即使你在AndroidMainfest.xml文件中申请也没有任何用,或者你可以将编译的目标版本设定这API 22,这样就可以了.但这并不是长久之计,不是吗?所以因此在这里学习一下. 动态权限需求原因 Android 6.0之前,权限在应用安装过程中只询问一次,以列表的形式展现给用户,然而大多数用户并不会注意到这些,直接就下一步了

安卓蓝牙动态权限

安卓7.0及以下版本 在 IDE 的 Project - Options 菜单弹出来的窗口里面,找到左边树结构的: Application -- Uses Permissions 项目,则右边会出来一堆权限让你打勾.这里要勾选上 Bluetooth 和 Bluetooth admin 两项. 老的安卓版本,勾选这两项就可以了. 安卓8.0及以上版本,还需要运行期用代码动态申请权限. 代码里面: 首先要实现一个函数: procedure RequestPermissionsResult(Sende

android打造万能的适配器

荒废了两天,今天与大家分享一个ListView的适配器 前段时间在学习慕课网的视频,觉得这种实现方式较好,便记录了下来,最近的项目中也使用了多次,节省了大量的代码,特此拿来与大家分享一下. 还是先看图片,这里我模仿博客园App的列表样式做了一个静态的数据列表    这里用到的类比较多,不过核心的只有两个,其余均为演示所用,先来看核心的两个类 ViewHolderM.java package landptf.tools; import android.content.Context; import

android打造万能的适配器(转)

荒废了两天,今天与大家分享一个ListView的适配器 前段时间在学习慕课网的视频,觉得这种实现方式较好,便记录了下来,最近的项目中也使用了多次,节省了大量的代码,特此拿来与大家分享一下. 还是先看图片,这里我模仿博客园App的列表样式做了一个静态的数据列表     这里用到的类比较多,不过核心的只有两个,其余均为演示所用,先来看核心的两个类 ViewHolderM.java package landptf.tools; import android.content.Context; impor

安卓开发笔记——打造万能适配器(Adapter)

为什么要打造万能适配器? 在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需要,因此我们时常要去继承BaseAdapter类去实现一个自定义的适配器来满足我们的场景需要. 如果你是开发一个简单点的APP还好,可能ListView和GridView的数量不会太多,我们只要去写几个BaseAdapter实现类就可以了. 但如果有一天,你需要开发一个APP里面具有几十个ListV