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中声明过的敏感权限。

具体的敏感权限如下:

<!-- CALENDAR 日历组 -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- CAMERA 相机拍照组 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- CONTACTS 联系人组 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- LOCATION 定位组 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- MICROPHONE 麦克风组 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- PHONE 组 -->
<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" />
<!-- SENSORS 传感器组 -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- SMS 组 -->
<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" />
<!-- STORAGE 存储组 -->
<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<String>类型参数用来获取被拒绝的权限
@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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android是一个开放的移动操作系统,爱好者和开发者可以根据自己的需求进行定制和开发。在Android开发中,JavaPoet是一个非常有用的库,可以动态生成Java代码,简化一些重复的工作。而AOP(面向切面编程)则是一种编程范式,可以将横切关注点与业务逻辑分离,提高代码的可复用性和可维护性。 动态权限申请是Android开发中经常遇到的一个问题。在Android系统中,一些敏感的操作和资源访问需要动态申请权限,以确保用户的隐私和安全。传统的权限申请方式是在每个需要权限的地方都进行判断和申请,这样会导致代码的冗余和可读性的下降。使用AOP结合JavaPoet可以实现动态权限申请的解决方案。 首先,我们可以通过AOP在需要权限的方法周围添加一个切面,用于检查和申请权限。通过AspectJ等AOP框架,我们可以定义一个切面,在方法执行之前和之后执行相应的逻辑。 然后,利用JavaPoet动态生成申请权限的代码。我们可以定义一个注解,用于标识需要权限的方法。在AOP切面中,当检测到方法上有该注解时,生成相应的权限申请代码。 最后,在代码编译阶段,通过JavaPoet生成的代码会自动插入到原始代码中。这样,我们就可以在运行时动态地进行权限的申请了。 通过以上的实践,我们可以实现动态权限申请的功能,同时可以减少重复的代码,并提高代码的可维护性。使用JavaPoet和AOP相结合的方式,可以使我们的开发变得更加高效和便捷。它们为Android开发带来了更多的灵活性和扩展性,帮助我们更好地应对权限申请的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值