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类型参数用来获取被拒绝的权限
@PermissionDenied
public 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注解方法进行代码织入

@Aspect
public 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

你可能感兴趣的:(技术备忘)