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