android 框架_AOP编程_Android优雅权限框架(2)Demo完全解析

享学课堂特邀作者:老顾
转载请声明出处!

上篇文章:AOP编程_Android优雅权限框架(1)概念基础

5. AOP优雅权限框架详解

Demo地址:

https://github.com/18598925736/GracefulPermissionFramework/tree/dev_aspectJ

gradle配置

  • 在project的build.gradle 添加aspectJ gradle插件
c817ae594e6442b5b7d95095dc680c9e
  • permission model 的build.gradle 引入 aspect类库
280bc820c7aa49e7952b65bc95a40898
  • app module 的build.gradle中启用aspectJ插件,并且引入permission module
c28ac11ab803462d9522ac47ac5b8b60

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

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

类结构图

b03649b2a980467fbe21ba0cd1f701d7

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类

@Aspectpublic 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 ,这个不一样,这个是在切入点的方法 被调用时,也就是说,当侦测到该方法被外界调用的时候,而非方法自己执行。这两者有细微差别。至于其他的类型,暂且按下不详述。

c73d96fcea614b778fc8b64dec631e7f

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

具体的签名的语法规则为:

00d434b81cce476eaad803ca24b75102
87698f3bd5a14b18a3c6fd8075b697f4

看不懂? 看不懂就对了,举个例子:

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)` 去使用。

有点绕!一张图说清楚:

29deb66ffc804c2f836e68f18f028910

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

  • 使用 @Around 注解来修饰 方法 doPermission(),被它修饰的方法会被认为是一个 切入策略。Around注解的参数 为: "requestPermission(permissionNeed)", 也就是pointcut修饰的方法名(形参名)在我们已经定义好切入点 requestPermission(PermissionNeed permissionNeed)的前提下,如果程序已经执行到了切入点,那么我是选择怎么样的策略, 目前所选择的策略是 Around ,也就是,完全替代切入点的方法,但是依然保留了 执行原方法逻辑的可能性joinPoint.proceed();除了@Around策略之外,还有以下:
2ffc01bfba684f6c985a7310c8e654c3

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的清单文件中注册

2a5e0e659f6043f289920d255b04d626

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

d99440b96b58442fa16529ad43e36120

Gif图效果演示:

f12c254df80b45a68706138434a6c13a

6. AOP思想以及常用AOP框架

所谓AOP(ApsectOrientedProgramming) 面向切面编程。

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

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

画图表示:

41176b32382e4596b62c0472ff9a94ba

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

4f86cfb077ff4cd3a44a9e4f99d5823c

所采取的方案为:

在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是如何通过@注解的方式来插入逻辑的。


喜欢本文的话可以关注我们的官方账号,第一时间获取资讯。
你的关注是对我们更新最大的动力哦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
简明、完整、全面的安卓开发demo集合,包含如下示例 1、LinearLayout Button、RadioGroup、 CheckBox 2、TableLayout 3、FrameLayout 霓虹灯效果 4、RelativeLayout 梅花效果 5、自定义view跟着触点走的小球 6、 ListView 列表视图 7、WebView web视图 8、ToggleButton 动态布局效果 9、AnalogClock 、 DigitalClock and Chronometer 时钟和数字日期 10、AutoCompleteTextView 根据输入自动补充可能的全部 11、Spinner View 选择框(弹出框形式选择) 12、DatePicker TimePicker View 日期时间选择器 13、ProgressBar View 普通进度条、显示在标题栏上的进度条 14、RatingBar View 评级 15、 SeekBar 拖动条,音量调节效果 16、ScrollView 、HorizontalScrollView 垂直和水平滚动条 17、ScrollView 、HorizontalScrollView 垂直和水平滚动条 18、ExpandableListView 分组可展开收缩的ListView 19、Notification 状态栏通知 20、GridView、ImageSwitcher 21、SmsManager 消息管理器,发短信(这里是模拟器只能给其它模拟器发短信) 22、Intent Action、Category属性 测试 23、系统 Action、Category属性 24、ClipDrawable 徐徐展开的风景 25、AnimationDrawable 会动的图片 26、Menu、SubMenu、ContextMenu xml配置menu 27、Attribute 自定义view的duration属性 控制图片的透明度 28、Bitmap、BitmapFactory 图形与图像处理 29、Canvas 绘制自定义图形 30、Canvas 采用双缓存实现画图板 31、SharedPreference 简单的key-value数据存取 32、SQLiteDatabase 安卓客户端的嵌入式数据库 33、GestureDetector + ViewFlipper实现翻页效果 34、GestureLiberay 自定义手势 35、GestureLiberay 通过自定义的手势实现用户操作 36、TextToSpeech 语音朗读 37、ContentProvider、ContentResolver 应用之间共享数据 38、 Service 相当于没有界面的activity 39、Activity与Service运行中通信 40、Service 相当于没有界面的activity 41、AIDL Service android中的跨进程调用 客户端,服务端见AidlService 42、BroadcastReceiver 接收广播消息 43、非UI线程中不能操作UI线程中的View测试 44、ImageSwitcher animation gesture实现可以滑动的跑马灯 45、下载状态栏显示下载进度 46、Gallery3d效果 47、ListView 上拉加载更多效果 48、异步加载图片的二级缓存技术 49、QQ的好友列表展示效果 50、Fragment + ViewPager实现tab滑动切换 51、能够显示在桌面前面的的歌词效果 52、activity切换特效
optimize_aop是HALCON中的一个函数,用于优化AOP(automatic operator parallelization)模型。它可以根据线程号优化AOP,并检查给定硬件的并行处理能力。optimize_aop会检查每个运算符,并通过在tuple元组、channel通道或domain level域级别上的自动并行化来加快操作速度。它会执行多次运算符,并根据输入参数的变化来评估并行处理的效率。对于正确的优化,需要确保在计算机上没有同时运行其他计算密集型应用程序,以避免影响硬件检查的时间测量。如果程序员不想使用AOP,而是自己实现并行化,那么需要使用多线程技术,将图像进行拆分处理,最后再合并。这需要更多的专业知识,可以参考HALCON的官方例程simulate_aop.hdev和官方说明书parallel_programming.pdf。\[2\]\[3\] #### 引用[.reference_title] - *1* [optimize_aop.hdev对sobel边缘检测算子 AOP的对不同大小图像并行加速效果 相关例程学习](https://blog.csdn.net/u013404374/article/details/48996877)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [关于实现Halcon算法加速的基础知识(CPU多核并行/GPU)](https://blog.csdn.net/libaineu2004/article/details/104202063)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值