文章目录

  • 背景
  • 目的
  • 实现
    • 使用方法
    • 原理
  • 其他

背景

我们都知道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

更多相关文章

  1. 使用Android系统隐藏api读取*.apk程序安装包信息
  2. Android(安卓)GLSurfaceView.Renderer 类 onDrawFrame 方法的刷
  3. ListView属性及方法android
  4. android 更新界面视图 Handler和runOnUiThread
  5. 【Android学习笔记】Android中Intent的应用方法探索
  6. [Android]_[ACtivity生命周期]
  7. Android中notifyDataSetInvalidated()和notifyDataSetChanged()
  8. Android系统模拟位置的使用方法
  9. Android(安卓)MVP模式介绍和讲解

随机推荐

  1. android之layout_weight使用
  2. Android(安卓)Animation(API Guides翻译)
  3. android 重力感应初步认识
  4. android中使用afinal一行代码显示网络图
  5. Android(安卓)7.1.2(Android(安卓)N) And
  6. 2.4.11 网格视图和图像切换器
  7. android编译时添加库第三方库文件
  8. android编译时添加库第三方库文件
  9. Camera Flash的获得权限
  10. 2.4.10 可展开的列表组件