AOP编程_Android优雅权限框架(2)Demo完全解析
ccwgpt 2024-10-07 06:50 65 浏览 0 评论
享学课堂特邀作者:老顾
转载请声明出处!
5. AOP优雅权限框架详解
Demo地址:
https://github.com/18598925736/GracefulPermissionFramework/tree/dev_aspectJ
gradle配置
- 在project的build.gradle 添加aspectJ gradle插件
- permission model 的build.gradle 引入 aspect类库
- app module 的build.gradle中启用aspectJ插件,并且引入permission module
Java代码
- app module 是使用框架的地方
上面我说到了,使用框架思想,消除了Activity,Fragment,Service,普通类 在申请权限时的差异性,可以全部以普通类的方式来申请权限并且处理回调。所以这里展示 Activity,Fragment,Service 的动态权限申请写法。
普通类
public class LocationUtil { private String TAG = "LocationUtil"; @PermissionNeed( permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) public void getLocation() { Log.e(TAG, "申请位置权限之后,我要获取经纬度"); } /** * 这里写的要特别注意,denied方法,必须是带有一个int参数的方法,下面的也一样 * @param requestCode */ @PermissionDenied public void denied(int requestCode) { Log.e(TAG, "用户不给啊"); } @PermissionDeniedForever public void deniedForever(int requestCode) { Log.e(TAG, "用户永久拒绝"); } }
Activity
public class MainActivity extends AppCompatActivity { private static final String TAG = "PermissionAspectTag"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_location).setOnClickListener(v -> getLocationPermission()); findViewById(R.id.btn_contact).setOnClickListener(v -> getContactPermission()); } @PermissionNeed( permissions = {Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS,Manifest.permission.GET_ACCOUNTS}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_CONTACT) private void getContactPermission() { Log.d(TAG, "getContactPermission"); } @PermissionNeed( permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) private void getLocationPermission() { Log.d(TAG, "getLocationPermission"); } @PermissionDenied private void permissionDenied(int requestCode) { switch (requestCode) { case PermissionRequestCodeConst.REQUEST_CODE_CONTACT: Log.d(TAG, "联系人权限被拒绝"); break; case PermissionRequestCodeConst.REQUEST_CODE_LOCATION: Log.d(TAG, "位置权限被拒绝"); break; default: break; } } @PermissionDeniedForever private void permissionDeniedForever(int requestCode) { switch (requestCode) { case PermissionRequestCodeConst.REQUEST_CODE_CONTACT: Log.d(TAG, "权限联系人被永久拒绝"); break; case PermissionRequestCodeConst.REQUEST_CODE_LOCATION: Log.d(TAG, "位置联系人被永久拒绝"); break; default: break; } } }
Fragment
public class MyFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { getLocation(); return super.onCreateView(inflater, container, savedInstanceState); } private String TAG = "LocationUtil"; @PermissionNeed( permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) public void getLocation() { Log.e(TAG, "申请位置权限之后,我要获取经纬度"); } /** * 这里写的要特别注意,denied方法,必须是带有一个int参数的方法,下面的也一样 * * @param requestCode */ @PermissionDenied public void denied(int requestCode) { Log.e(TAG, "用户不给啊"); } @PermissionDeniedForever public void deniedForever(int requestCode) { Log.e(TAG, "用户永久拒绝"); } }
Service
public class MyService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { getLocation(); return super.onStartCommand(intent, flags, startId); } private String TAG = "LocationUtil"; @PermissionNeed( permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) public void getLocation() { Log.e(TAG, "申请位置权限之后,我要获取经纬度"); } /** * 这里写的要特别注意,denied方法,必须是带有一个int参数的方法,下面的也一样 * @param requestCode */ @PermissionDenied public void denied(int requestCode) { Log.e(TAG, "用户不给啊"); } @PermissionDeniedForever public void deniedForever(int requestCode) { Log.e(TAG, "用户永久拒绝"); } }
经过观察,Activity,Fragment,Service,和普通类,都是定义了一个或者多个被@PermissionNeed注解修饰的方法, 如果是多个,还要在@PermissionDenied和@PermissionDeniedForever修饰的方法中switch处理requestCode(参考上方Activity),以应对申请多次申请不同权限的结果 。也许除了这4个地方之外,还有别的地方需要申请动态权限,但是既然我们消除了差异性,就可以全部以普通类的方式来申请权限以及处理回调。这才叫从根本上解决问题。
这里有个坑: 被@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 PermissionAspect { 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)") public void doPermission(final ProceedingJoinPoint joinPoint, PermissionNeed permissionNeed) { PermissionAspectActivity.startActivity(getContext(joinPoint), permissionNeed.permissions(), permissionNeed.requestCode(), new IPermissionCallback() { @Override public void granted(int requestCode) { // 如果授予,那么执行joinPoint原方法体 try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } } @Override public void denied(int requestCode) {//这里的getThis也是要给梗 PermissionUtil.invokeAnnotation(joinPoint.getThis(), PermissionDenied.class, requestCode); } @Override public void deniedForever(int requestCode) { PermissionUtil.invokeAnnotation(joinPoint.getThis(), PermissionDeniedForever.class, requestCode); } }); } private Context getContext(final ProceedingJoinPoint joinPoint) { final Object obj = joinPoint.getThis(); if (obj instanceof Context) {// 如果切入点是一个类?那么这个类的对象是不是context? return (Context) obj; } else {// 如果切入点不是Context的子类呢? //jointPoint.getThis,其实是得到切入点所在类的对象 Object[] args = joinPoint.getArgs(); if (args.length > 0) {// if (args[0] instanceof Context) {//看看第一个参数是不是context return (Context) args[0]; } else { return ApplicationUtil.getApplication();//如果不是,那么就只好hook反射了 } } else { return ApplicationUtil.getApplication();//如果不是,那么就只好hook反射了 } } } }
此段代码解读如下:
- 使用@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(PermissionNeed permissionNeed)` 去使用。
有点绕!一张图说清楚:
- 使用 @Around 注解来修饰 方法 doPermission(),被它修饰的方法会被认为是一个 切入策略。Around注解的参数 为: "requestPermission(permissionNeed)", 也就是pointcut修饰的方法名(形参名)在我们已经定义好切入点 requestPermission(PermissionNeed permissionNeed)的前提下,如果程序已经执行到了切入点,那么我是选择怎么样的策略, 目前所选择的策略是 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、提供一个静态方法
public static void startActivity(Context context, String[] permissions, int requestCode, IPermissionCallback callback),
用于启动自己 PermissionAspectActivity,
接收的参数分别为:context,需要的权限数组,权限返回码,权限结果回调接口
2、onCreate方法中,检查是否已经有想要申请的权限,如果有,直接调用mCallback.granted(requestCode); 并且结束自身,并且要注意隐藏Activity的切换动画。如果没有,那么,就去requestPermissions(permissions, requestCode);申请权限。
3、处理权限申请的回调,并且分情况调用mCallback的回调方法,然后结束自身
需要注意:
PermissionAspectActivity必须在module的清单文件中注册
并且 要定义它的theme使得Activity完全透明
Gif图效果演示:
6. AOP思想以及常用AOP框架
所谓AOP(ApsectOrientedProgramming) 面向切面编程。
此概念是基于OOP (ObjectOrientiedProgramming)面向对象编程。在OOP中,我们可以把不同的业务功能都分成一个一个的模块,然后每一个模块有自己的专一职责,从而优化编程过程,降低编程犯错几率。但是随着OOP类的数量的增加,我们会发现,在某一些业务类中,经常有一些相同的代码在重复编写,但是无可奈何,比如日志打印/动态权限申请/埋点数据上报/用户登录状态检查 /服务器端口连通性检查 等等。这些代码,我们虽然可以他们抽离出来整理到一个个专一的模块中,但是调用的时候,还是到处分散的,并且这些调用还入侵了本来不直接相关的业务代码,让我们阅读业务代码十分费劲。
而AOP的出现,就是基于OOP的这种缺陷而出现的优化方案。利用AOP,我们可以对业务逻辑的各个部分进行隔离,使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率,减少犯错概率。
画图表示:
如上图,OOP中,同样的一段过程,我们把登录检查,权限检查,埋点上报的调用代码写了3遍,然而都是雷同代码,只是参数不同而已。而,换成AOP的思想来编码。则是如下:
所采取的方案为:
在class A , B, C中 找到切入点,然后在切入点插入共同的逻辑,而不是多次编写雷同的代码.
本文的Demo中,插入相同的逻辑,使用的是 Java自定义注解+@Aspect切面类+@PointCut切入点+@Around切入策略 的方式。这只是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的祖宗, 现在依然有自己不可替代的作用。
7. AspectJ AOP框架的深入原理研究
…本来想写成一篇,但是发现篇幅太长,留个尾巴,下一篇,解析AspectJ是如何通过@注解的方式来插入逻辑的。
喜欢本文的话可以关注我们的官方账号,第一时间获取资讯。
你的关注是对我们更新最大的动力哦~
相关推荐
- 用Deepseek扩写土木工程毕业论文实操指南
-
用Deepseek扩写毕业论文实操指南一、前期准备整理现有论文初稿/提纲列清楚论文核心框架(背景、现状、意义、方法、数据、结论等)梳理好关键文献,明确核心技术路线二、Deepseek扩写核心思路...
- 985学霸亲授,DeepSeek也能绘6大科研图表,5分钟就出图
-
在实验数据处理中,高效可视化是每个科研人的必修课。传统绘图软件操作复杂、耗时费力,而智能工具DeepSeek的出现彻底改变了这一现状。本文将详解如何用DeepSeek一键生成六大科研常用图表,从思维导...
- AI写论文刷屏?大学生正在丢掉的思考力
-
一、宿舍深夜:当论文变成"Ctrl+C+V"凌晨两点的大学宿舍,小王对着电脑屏幕叹气。本该三天前开始写的近代史论文,此刻还一片空白。他熟练打开某AI写作网站,输入"论五四运动的...
- Grok在辅助论文写作上能不能既“聪明”又“可怕”?!
-
AcademicIdeas-学境思源AI初稿写作随着人工智能技术的飞速发展,论文写作这一学术任务正迎来新的助力。2025年2月18日,美国xAI公司推出了备受瞩目的Grok3模型,其创始人埃隆·...
- 大四论文沟通场景!音频转文字难题听脑AI来化解
-
大四学生都知道,写论文时和导师沟通修改意见,简直是“过关斩将”。电话、语音沟通完,想把导师说的修改方向、重点要求记下来,麻烦事儿可不少。手写记不全,用普通录音转文字工具,转完还得自己慢慢找重点,稍不注...
- 论文写作 | 技术路线图怎么画?(提供经典优秀模板参考)
-
技术路线图是一种图表或文字说明,用于描述研究目标、方法和实施计划。它展示了研究的整体框架和步骤,有助于读者理解研究的逻辑和进展。在课题及论文中,技术路线图是常见的一部分,甚至是一个类似心脏一样的中枢器...
- 25年信息系统项目管理师考试第2批论文题目写作建议思路框架
-
25年信息系统项目管理师考试第2批论文题目写作建议思路框架--马军老师
- 微信购物应尽快纳入法律框架(微信购物管辖)
-
符向军近日,甘肃省工商行政管理局发布《2016年上半年信息分析报告》。报告显示,微信网购纠纷迅猛增长,网络购物投诉呈上升趋势。投诉的主要问题有出售的商品质量不过关、消费者通过微信付款后对方不发货、购买...
- 泛珠三角区域网络媒体与腾讯微信签署《战略合作框架协议》
-
新海南客户端、南海网7月14日消息(记者任桐)7月14日上午,参加第四届泛珠三角区域合作网络媒体论坛的区域网络媒体负责人及嘉宾一行到腾讯微信总部座谈交流,并签署《战略合作框架协议》(以下简称《框架协...
- 离线使用、植入微信-看乐心Mambo手环如何打破框架
-
从2014年开始智能手环就成功进入人们的生活,至今已经演变出数据监测、信息推送、心率监测等诸多五花八门的功能,人们选择智能手环并不指望其能够改变身体健康情况,更多的是通过数据来正视自身运动情况和身体健...
- 华专网络:如何零基础制作一个网站出来?
-
#如何零基础制作一个网站出来?#你是不是觉得网站建设很复杂,觉得自己是小白,需求不明确、流程搞不懂、怕被外包公司坑……这些问题我都懂!今天华专网络就用大白话给你捋清楚建站的全流程,让你轻松get网站制...
- WAIC2024丨明日上午9点,不见不散!共同探讨智能社会与全球治理框架
-
大咖云集,硕果闪耀WAIC2024世界人工智能大会智能社会论坛将于7月5日9:00-12:00与你相约直播间WAIC2024上海杨浦同济大学哔哩哔哩多平台同步直播探讨智能社会与全球治理框架WAIC...
- 约基奇:森林狼换来戈贝尔时大家都在嘲笑 他们的阵容框架很不错
-
直播吧5月4日讯西部季后赛半决赛,掘金将迎战森林狼,约基奇赛前接受采访。约基奇说道:“当蒂姆-康纳利(森林狼总经理、前掘金总经理&曾选中约基奇)做了那笔交易(换来戈贝尔)时,每个人都在嘲笑他...
- 视频号带货为什么一个流量都没有?顶级分析框架送给你
-
视频号带货为什么一个流量都没有?遇到问题,一定是步步来分析内容,视频号带货一个流量都没有,用另外一个意思来讲,就可以说是零播放。为什么视频号带货一个流量都没有?跟你说再多,都不如来个分析框架。1、是否...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)