Android使用AOP实现动态权限获取
文章目录
- 背景
- 目的
- 实现
- 使用方法
- 原理
- 其他
背景
我们都知道Android项目中包含一项配置,叫做TargetSDKVersion,这里使用不同的版本号,会使用不同Android版本的特性,也需要我们对相应的版本进行兼容。
targetSdkVersion is the main way Android provides forward compatibility
targetSdkVersion 是 Android 系统提供前向兼容的主要手段。这是什么意思呢?随着 Android 系统的升级,某个系统的 API 或者模块的行为可能会发生改变,但是为了保证老 APK 的行为还是和以前兼容。只要 APK 的 targetSdkVersion 不变,即使这个 APK 安装在新 Android 系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的前向兼容性。
最近,部门决定对项目的TargetSDKVersion进行提高,由原来的21提升到27,以应对未来国内市场对TargetSDKVersion的限制。
而当TargetSDKVersion升级到23的时候,Android对权限的进行了调整,我们无法直接使用,在Manifest中声明过的敏感权限。
具体的敏感权限如下:
<uses-permission android:name="android.permission.READ_CALENDAR" /><uses-permission android:name="android.permission.WRITE_CALENDAR" /><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.READ_CONTACTS" /><uses-permission android:name="android.permission.WRITE_CONTACTS" /><uses-permission android:name="android.permission.GET_ACCOUNTS" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.CALL_PHONE" /><uses-permission android:name="android.permission.READ_CALL_LOG" /><uses-permission android:name="android.permission.WRITE_CALL_LOG" /><uses-permission android:name="android.permission.USE_SIP" /><uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /><uses-permission android:name="android.permission.BODY_SENSORS" /><uses-permission android:name="android.permission.SEND_SMS" /><uses-permission android:name="android.permission.RECEIVE_SMS" /><uses-permission android:name="android.permission.READ_SMS" /><uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" /><uses-permission android:name="android.permission.RECEIVE_MMS" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
所以,提升后,需要在运行时进行动态权限获取。
目的
让组内开发人员可以方便的在需要获取权限的地方,快速接入判断功能。
实现
使用方法
在想要加入权限判断的方法上面加入以下注解,具体权限根据实际添加。
@NeedPermission({Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_PHONE_STATE,Manifest.permission.READ_CONTACTS})
用户如果拒绝权限,方法内容将不会执行。
如果想要获取到用户的拒绝操作,在检查方法的同一个类中,加入
//方法名随意,可以没有参数,或者包含一个List类型参数用来获取被拒绝的权限 @PermissionDeniedpublic void denyPermission(List<String> permissions) {for (String p:permissions) {Log.d("AOPMonitor", p);}}
注意:
方法所在类必须是Activity或者Fragment
NeedPermission不要加在Activity的生命周期方法上。
原理
首先,我们需要两个注解。
/** * API大于等于23时,来对方法进行权限判断的 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface NeedPermission { String[] value();}
/** * 用来接收权限被拒绝的通知 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface PermissionDenied {}
然后对拥有NeedPermission注解方法进行代码织入
@Aspectpublic class NeedPermissionAspect { private static final int ANDROID_M = 23;//定位NeedPermission注解,并获取注解对象 @Pointcut("execution(@com.tts.android.aopmonitor.annotation.NeedPermission * *(..)) && @annotation(needPermission)") public void needPermission(NeedPermission needPermission){} @Around("needPermission(needPermission)") public void checkPermission(final ProceedingJoinPoint point, NeedPermission needPermission) { Context context = null; final Object object = point.getThis(); if (object instanceof Context) { context = (Context) object; } else if (object instanceof Fragment) { context = ((Fragment) object).getActivity(); } else if (object instanceof android.support.v4.app.Fragment) { context = ((android.support.v4.app.Fragment) object).getActivity(); } if (context == null || needPermission == null) { LogUtils.loge("the method is not belong to a activity or fragment, " + "or NeedPermission annotation not found"); try { point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return; } /** * 小于23的版本不需要动态权限验证 */ if (DeviceUtils.getSDKVersionCode() < ANDROID_M) { try { point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return; } //开始检查并获取权限,使用AndPermission框架 AndPermission.with(context) .runtime() .permission(needPermission.value()) .onGranted(new Action<List<String>>() { @Override public void onAction(List<String> data) { try { point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } } }) .onDenied(new Action<List<String>>() { @Override public void onAction(List<String> data) { /** * 被拒绝后,使用反射,获取PermissionDenied注解的方法,执行内容 */ Class<?> cls = object.getClass(); Method[] methods = cls.getDeclaredMethods(); if (methods == null || methods.length == 0) { return; } for (Method method : methods) { //过滤不含自定义注解PermissionDenied的方法 boolean isHasAnnotation = method.isAnnotationPresent(PermissionDenied.class); if (isHasAnnotation) { method.setAccessible(true); //获取方法参数类型 Class<?>[] types = method.getParameterTypes(); //获取方法上的注解 PermissionDenied aInfo = method.getAnnotation(PermissionDenied.class); if (aInfo == null) { return; } try { if (null != types && types.length == 1 ) { method.invoke(object, data); } else { method.invoke(object); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } }) .start(); }
由于使用了反射,我们需要对加入注解的方法进行混淆配置。
# 不混淆拥有以下注解的方法-keepclassmembers class * { @com.tts.android.aopmonitor.annotation.NeedPermission <methods>; @com.tts.android.aopmonitor.annotation.PermissionDenied <methods>;}
其他
项目地址
关于AOP技术,项目中使用的是Aspectj,下面这篇介绍的就很不错。
AndroidStudio 配置 AspectJ 环境实现AOP
更多相关文章
- 使用Android系统隐藏api读取*.apk程序安装包信息
- Android(安卓)GLSurfaceView.Renderer 类 onDrawFrame 方法的刷
- ListView属性及方法android
- android 更新界面视图 Handler和runOnUiThread
- 【Android学习笔记】Android中Intent的应用方法探索
- [Android]_[ACtivity生命周期]
- Android中notifyDataSetInvalidated()和notifyDataSetChanged()
- Android系统模拟位置的使用方法
- Android(安卓)MVP模式介绍和讲解