关于AOP思想和AspectJX框架你们都耳熟能详,AspectJ为开发者提供了实现AOP的基础能力,能够经过它来实现符合各自业务需求的功能。java
这里借助AspectJX框架来实现效能提高相关的一些有意思的功能,AspectJX框架的配置和使用在README中有详细步骤,也能够参考官方demo。android
AspectJ中的语法说明详见: github.com/hiphonezhu/… github.com/HujiangTech…git
平常开发中,常常会在某个关键方法中打印Log输出一段字符串和参数变量的值来进行分析调试,或者在方法执行先后打印Log来查看方法执行的耗时。github
若是须要在业务主流程中的多个关键方法中增长日志,查看方法执行的输入参数和返回结果是否正确,只能繁琐的在每一个方法开头添加Log调用打印输出每一个参数。若该方法有返回值,则在return前再添加Log打印输出返回值。若该方法中有多个if分支进行return,还得在每一个分支return前打印Log。正则表达式
统计方法耗时须要在方法开头记录时间,在每一个return前计算时间并打印Log。不只繁琐,还容易遗漏。网络
能够经过给想要打印日志的方法上标记一个注解,在编译时给标记注解的方法织入代码,自动打印这个方法运行时的输入输出信息和耗时信息。架构
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoLog {
/** logcat筛选tag */
String tag();
/** 打印日志级别(默认VERBOSE) */
LogLevel level() default LogLevel.VERBOSE;
}
复制代码
AutoLog.javaapp
自定义一个注解AutoLog,用于给想要打印日志的方法作标记。框架
@Aspect
public class LogAspect {
/** * 切入点,添加了AutoLog注解的全部方法体内 */
@Pointcut("execution (@com.cdh.aop.toys.annotation.AutoLog * *(..))")
public void logMethodExecute() {
}
// Advice ···
}
复制代码
建立一个日志切面LogAspect,在其中定义一个切入点,对全部添加了AutoLog注解的方法进行代码织入。dom
切入点中的execution表示在该方法体内进行代码织入,@com.cdh.aop.toys.annotation.AutoLog表示添加了该注解的方法,第一个星表示不限return type,第二个星表示匹配任意方法名称,(..)表示不限方法入参。
@Aspect
public class LogAspect {
// Pointcut ···
/** * 对上面定义的切入点的方法进行织入,Around的做用是替代原方法体内代码 */
@Around("logMethodExecute()")
public Object autoLog(ProceedingJoinPoint joinPoint) {
try {
// 获取被织入方法的签名信息,MethodSignature包含方法的详细信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取方法上添加的AutoLog注解
AutoLog log = methodSignature.getMethod().getAnnotation(AutoLog.class);
if (log != null) {
// 用于拼接日志详细信息
StringBuilder sb = new StringBuilder();
// 拼接方法名称
String methodName = methodSignature.getMethod().getName();
sb.append(methodName);
// 拼接每一个参数的值
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
sb.append("(");
for (int i=0; i<args.length; i++) {
sb.append(args[i]);
if (i != args.length-1) {
sb.append(",");
}
}
sb.append(")");
}
// 记录开始执行时的时间
long beginTime = System.currentTimeMillis();
// 执行原方法代码,并得到返回值
Object result = joinPoint.proceed();
// 计算方法执行耗时
long costTime = System.currentTimeMillis() - beginTime;
if (methodSignature.getReturnType() != void.class) {
// 若该方法返回类型不是void,则拼接返回值
sb.append(" => ").append(result);
}
// 拼接耗时
sb.append(" | ").append("cost=").append(costTime);
// 拼接方法所在类名和行号
String className = methodSignature.getDeclaringType().getSimpleName();
int srcLine = joinPoint.getSourceLocation().getLine();
sb.append(" | [").append(className).append(":").append(srcLine).append("]");
// 打印日志,使用AutoLog注解设置的tag和级别调用Log类的对应方法
LogUtils.log(log.level(), log.tag(), sb.toString());
return result;
}
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
}
复制代码
使用Around能够替换原方法中的逻辑,也能够经过ProceedingJoinPoint.proceed继续执行原方法逻辑。这里在执行原方法逻辑以外,还进行了方法参数信息的拼接和耗时计算,最后打印日志输出。
到这里完成了一个基本的日志切面织入功能,接下来在想要自动打印日志的方法上添加注解便可。
随意写几个方法调用,在这几个方法上添加AutoLog注解。
public class AddOpWithLog extends BaseOp {
public AddOpWithLog(BaseOp next) {
super(next);
}
@Override
@AutoLog(tag=TAG, level=LogLevel.DEBUG)
protected int onOperate(int value) {
return value + new Random().nextInt(10);
}
}
复制代码
public class SubOpWithLog extends BaseOp {
public SubOpWithLog(BaseOp next) {
super(next);
}
@Override
@AutoLog(tag=TAG, level=LogLevel.WARN)
protected int onOperate(int value) {
return value - new Random().nextInt(10);
}
}
复制代码
public class MulOpWithLog extends BaseOp {
public MulOpWithLog(BaseOp next) {
super(next);
}
@Override
@AutoLog(tag=TAG, level=LogLevel.WARN)
protected int onOperate(int value) {
return value * new Random().nextInt(10);
}
}
复制代码
public class DivOpWithLog extends BaseOp {
public DivOpWithLog(BaseOp next) {
super(next);
}
@Override
@AutoLog(tag=TAG, level=LogLevel.DEBUG)
protected int onOperate(int value) {
return value / (new Random().nextInt(10)+1);
}
}
复制代码
@AutoLog(tag = BaseOp.TAG, level = LogLevel.DEBUG)
public void doWithLog(View view) {
BaseOp div = new DivOpWithLog(null);
BaseOp mul = new MulOpWithLog(div);
BaseOp sub = new SubOpWithLog(mul);
BaseOp add = new AddOpWithLog(sub);
int result = add.operate(100);
Toast.makeText(this, result+"", Toast.LENGTH_SHORT).show();
}
复制代码
运行doWithLog方法,查看logcat输出日志:
效果如图所示,打印方法名称以及每一个入参的值和直接结果返回值(如果void则不打印返回值),还有该方法的执行耗时(单位ms)。
平常开发中常常会涉及线程切换操做,例如网络请求、文件IO和其余耗时操做须要放在自线程中执行,UI操做须要切回主线程执行。
每次切换线程时须要建立Runnable,在它的run方法中执行业务逻辑,或者利用AsyncTask和Executor(切回主线程还须要利用Handler),须要在方法调用处或方法体内部增长这些代码来切换线程运行。
若是能经过给方法加个标记,就能自动让该方法在主或子线程执行,就可让方法调用过程变得清晰和极大的减小代码量。
一样能够利用注解给方法标记,在编译器织入线程调用的代码,自动进行线程切换。 注意:这里的实现方案较为鸡肋,仅提供一个思路和演示。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoThread {
/** * 指定方法运行在主/子线程 * 可选枚举值: MAIN(指望运行在主线程) BACKGROUND(指望运行在子线程) */
ThreadScene scene();
/** * 设置是否阻塞等待该方法执行完成才返回(默认true) */
boolean waitUntilDone() default true;
}
复制代码
AutoThread.java 自定义注解AutoThread,用于标记想要自动切换线程运行的方法。
@Aspect
public class ThreadAspect {
@Pointcut("execution (@com.cdh.aop.toys.annotation.AutoThread * *(..))")
public void threadSceneTransition() {
}
// Advice ···
}
复制代码
ThreadAspect.java 这里定义了一个切面ThreadAspect和切入点threadSceneTransition。
切入点中的execution表示在该方法体内进行代码织入,@com.cdh.aop.toys.annotation.AutoThread表示添加了该注解的方法,第一个星表示不限return type,第二个星表示匹配任意方法名称,(..)表示不限方法入参。
@Aspect
public class ThreadAspect {
// Pointcut ···
@Around("threadSceneTransition()")
public Object executeInThread(final ProceedingJoinPoint joinPoint) {
// result用于保存原方法执行结果
final Object[] result = {null};
try {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取咱们添加的方法注解AutoThread
AutoThread thread = methodSignature.getMethod().getAnnotation(AutoThread.class);
if (thread != null) {
// 获取注解中设置的ThreadScene值,
ThreadScene threadScene = thread.scene();
if (threadScene == ThreadScene.MAIN && !ThreadUtils.isMainThread()) {
// 若指望运行在主线程,但当前不在主线程
// 切换到主线程执行
ThreadUtils.runOnMainThread(new Runnable() {
@Override
public void run() {
try {
// 执行原方法,并保存结果
result[0] = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}, thread.waitUntilDone());
} else if (threadScene == ThreadScene.BACKGROUND && ThreadUtils.isMainThread()) {
// 若指望运行在子线程,但当前在主线程
// 切换到子线程执行
ThreadUtils.run(new Runnable() {
@Override
public void run() {
try {
// 执行原方法,并保存结果
result[0] = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}, thread.waitUntilDone());
} else {
// 直接在当前线程运行
result[0] = joinPoint.proceed();
}
}
} catch (Throwable t) {
t.printStackTrace();
}
// 返回原方法返回值
return result[0];
}
}
复制代码
这里使用Around替换原方法逻辑,在执行原方法以前,先进行线程判断,而后切换到对应线程再执行原方法。
上面看到,当须要切换主线程时,调用ThreadUtils.runOnMainThread来执行原方法,看看这个方法的内部实现:
/** * 主线程执行 * * @param runnable 待执行任务 * @param block 是否等待执行完成 */
public static void runOnMainThread(Runnable runnable, boolean block) {
if (isMainThread()) {
runnable.run();
return;
}
// 利用CountDownLatch来阻塞当前线程
CountDownLatch latch = null;
if (block) {
latch = new CountDownLatch(1);
}
// 利用Pair保存Runnable和CountDownLatch
Pair<Runnable, CountDownLatch> pair = new Pair<>(runnable, latch);
// 将Pair参数发送到主线程处理
getMainHandler().obtainMessage(WHAT_RUN_ON_MAIN, pair).sendToTarget();
if (block) {
try {
// 等待CountDownLatch降为0
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class MainHandler extends Handler {
MainHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
if (msg.what == WHAT_RUN_ON_MAIN) {
// 取出Pair参数
Pair<Runnable, CountDownLatch> pair = (Pair<Runnable, CountDownLatch>) msg.obj;
try {
// 取出Runnable参数运行
pair.first.run();
} finally {
if (pair.second != null) {
// 使CountDownLatch降1,这里会降为0,唤醒前面的阻塞等待
pair.second.countDown();
}
}
}
}
}
复制代码
ThreadUtils.java 切换到主线程的方式仍是利用主线程Handler。若设置等待结果返回,则会建立CountDownLatch,阻塞当前调用线程,等待主线程中执行完任务后才返回。
接下来看看切换子线程执行的方法ThreadUtils.run:
/** * 子线程执行 * * @param runnable 待执行任务 * @param block 是否等待执行完成 */
public static void run(final Runnable runnable, final boolean block) {
Future future = getExecutorService().submit(new Runnable() {
@Override
public void run() {
// 经过线程池运行在子线程
runnable.run();
}
});
if (block) {
try {
// 等待执行结果
future.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
切换到子线程就是经过线程池提交任务执行。
一样写几个方法,而后加上AutoThread注解
public class AddOpInThread extends BaseOp {
public AddOpInThread(BaseOp next) {
super(next);
}
@Override
@AutoThread(scene = ThreadScene.BACKGROUND)
protected int onOperate(int value) {
// 打印该方法运行时所在线程
Log.w(BaseOp.TAG, "AddOpInThread onOperate: " + java.lang.Thread.currentThread());
return value + new Random().nextInt(10);
}
}
复制代码
AddOpInThread.java 方法注解指定运行在子线程。
public class SubOpInThread extends BaseOp {
public SubOpInThread(BaseOp next) {
super(next);
}
@Override
@AutoThread(scene = ThreadScene.MAIN)
protected int onOperate(int value) {
// 打印该方法运行时所在线程
Log.w(BaseOp.TAG, "SubOpInThread onOperate: " + java.lang.Thread.currentThread());
return value - new Random().nextInt(10);
}
}
复制代码
SubOpInThread.java 指定运行在主线程。
public class MulOpInThread extends BaseOp {
public MulOpInThread(BaseOp next) {
super(next);
}
@Override
@AutoThread(scene = ThreadScene.MAIN)
protected int onOperate(int value) {
// 打印该方法运行时所在线程
Log.w(BaseOp.TAG, "MulOpInThread onOperate: " + java.lang.Thread.currentThread());
return value * new Random().nextInt(10);
}
}
复制代码
MulOpInThread.java 指定运行在主线程。
public class DivOpInThread extends BaseOp {
public DivOpInThread(BaseOp next) {
super(next);
}
@Override
@AutoThread(scene = ThreadScene.BACKGROUND)
protected int onOperate(int value) {
// 打印该方法运行时所在线程
Log.w(BaseOp.TAG, "DivOpInThread onOperate: " + java.lang.Thread.currentThread());
return value / (new Random().nextInt(10)+1);
}
}
复制代码
DivOpInThread.java 指定运行在子线程。
接下来调用方法:
public void doWithThread(View view) {
BaseOp div = new DivOpInThread(null);
BaseOp mul = new MulOpInThread(div);
BaseOp sub = new SubOpInThread(mul);
BaseOp add = new AddOpInThread(sub);
int result = add.operate(100);
Toast.makeText(this, result+"", Toast.LENGTH_SHORT).show();
}
复制代码
运行doWithThread方法,查看logcat输出日志:
能够看到第一个方法已经切换到子线程中运行,第2、三个方法又运行在主线程中,第四个方法又运行在子线程中。
一般咱们在建立使用Thread时,须要给它设置一个名称,便于分析和定位该Thread所属业务模块。
开发过程当中出现疏漏或者引入的第三方库中不规范使用线程,例如直接建立线程运行,或者匿名线程等。当想要分析线程时,就会看到不少Thread-一、二、3的线程,若是有一个清晰的名称就容易一眼看出该线程所属的业务。
能够经过拦截全部的Thread.start调用时机,在start以前检测线程名称。如果默认名称,则进行警告,而且自动修改线程名称。
这里把线程相关织入操做都放在一个切面ThreadAspect中:
@Aspect
public class ThreadAspect {
private static final String TAG = "ThreadAspect";
@Before("call (* java.lang.Thread.start(..))")
public void callThreadStart(JoinPoint joinPoint) {
try {
// 获取joinPoint所在对象,即执行start方法的那个Thread实例
Thread thread = (Thread) joinPoint.getTarget();
// 经过正则检测线程名称
if (ThreadUtils.isDefaultThreadName(thread)) {
// 打印警告信息(线程对象和该方法调用的位置)
LogUtils.e(TAG, "发现启动线程[" + thread + "]未自定义线程名称! [" + joinPoint.getSourceLocation() + "]");
// 设置线程名称,名称拼接该方法调用处上下文this对象
thread.setName(thread.getName() + "-" + joinPoint.getThis());
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
复制代码
Before表示在切入点前织入,call表示在该方法的调用处,第一个星表示不限return type,java.lang.Thread.start表示彻底匹配Thread类的start方法,(..)表示不限方法参数。
该切入点会在全部调用thread.start的地方前面织入名称检测和设置名称的代码。
若thread未设置名称,则会使用默认名称,能够看Thread的构造方法。
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public Thread() {
// 第三个参数即默认名称
init(null, null, "Thread-" + nextThreadNum(), 0);
}
复制代码
Thread在建立时会设置一个默认名称,Thread-数字递增,因此能够经过匹配这个名称来判断Thread是否设置了自定义名称。
看ThreadUtils.isDefaultThreadName方法:
public static boolean isDefaultThreadName(Thread thread) {
String name = thread.getName();
String pattern = "^Thread-[1-9]\\d*$";
return Pattern.matches(pattern, name);
}
复制代码
经过正则表达式来判断,若彻底匹配则表示当前是默认名称。
建立几个Thread,分别设置名称和不设置名称,而后启动运行。
public void renameThreadName(View view) {
// 未设置名称
new Thread(new PrintNameRunnable()).start();
// 设置名称
Thread t = new Thread(new PrintNameRunnable());
t.setName("myname-thread-test");
t.start();
}
private static class PrintNameRunnable implements Runnable {
@Override
public void run() {
// 打印线程名称
Log.d(TAG, "thread name: " + Thread.currentThread().getName());
}
}
复制代码
运行后查看logcat输出日志:
工信部发文要求APP在用户未赞成隐私协议以前,不得收集用户、设备相关信息,例如imei、device id、设备已安装应用列表、通信录等可以惟一标识用户和用户设备隐私相关的信息。
注意,这里的用户赞成隐私协议不一样于APP权限申请,是属于业务层面上的隐私协议。若用户未赞成隐私协议,即便在系统应用设置中打开该APP的全部权限,业务代码中也不能获取相关信息。
如图,必须用户赞成后,业务代码中才能获取须要的信息。
要对代码中全部涉及隐私信息获取的地方作检查,容易疏漏。万一出现遗漏,将面临工信部的下架整改处罚。并且部分三方SDK中没有严格按照工信部要求,会私自进行用户、设备相关信息的获取。
能够在全部调用隐私信息API的地方前面织入检查代码,一举涵盖自身业务代码和三方SDK代码进行拦截。
注意,经过动态加载的代码中的调用行为和native层中的行为没法完全拦截。
@Aspect
public class PrivacyAspect {
// 拦截获取手机安装应用列表信息的调用
private static final String POINT_CUT_GET_INSTALLED_APPLICATION = "call (* android.content.pm.PackageManager.getInstalledApplications(..))";
private static final String POINT_CUT_GET_INSTALLED_PACKAGES = "call (* android.content.pm.PackageManager.getInstalledPackages(..))";
// 拦截获取imei、device id的调用
private static final String POINT_CUT_GET_IMEI = "call (* android.telephony.TelephonyManager.getImei(..))";
private static final String POINT_CUT_GET_DEVICE_ID = "call(* android.telephony.TelephonyManager.getDeviceId(..))";
// 拦截getLine1Number方法的调用
private static final String POINT_CUT_GET_LINE_NUMBER = "call (* android.telephony.TelephonyManager.getLine1Number(..))";
// 拦截定位的调用
private static final String POINT_CUT_GET_LAST_KNOWN_LOCATION = "call (* android.location.LocationManager.getLastKnownLocation(..))";
private static final String POINT_CUT_REQUEST_LOCATION_UPDATES = "call (* android.location.LocationManager.requestLocationUpdates(..))";
private static final String POINT_CUT_REQUEST_LOCATION_SINGLE = "call (* android.location.LocationManager.requestSingleUpdate(..))";
// ···
@Around(POINT_CUT_GET_INSTALLED_APPLICATION)
public Object callGetInstalledApplications(ProceedingJoinPoint joinPoint) {
return handleProceedingJoinPoint(joinPoint, new ArrayList<ApplicationInfo>());
}
@Around(POINT_CUT_GET_INSTALLED_PACKAGES)
public Object callGetInstalledPackages(ProceedingJoinPoint joinPoint) {
return handleProceedingJoinPoint(joinPoint, new ArrayList<PackageInfo>());
}
@Around(POINT_CUT_GET_IMEI)
public Object callGetImei(ProceedingJoinPoint joinPoint) {
return handleProceedingJoinPoint(joinPoint, "");
}
@Around(POINT_CUT_GET_DEVICE_ID)
public Object callGetDeviceId(ProceedingJoinPoint joinPoint) {
return handleProceedingJoinPoint(joinPoint, "");
}
@Around(POINT_CUT_GET_LINE_NUMBER)
public Object callGetLine1Number(ProceedingJoinPoint joinPoint) {
return handleProceedingJoinPoint(joinPoint, "");
}
@Around(POINT_CUT_GET_LAST_KNOWN_LOCATION)
public Object callGetLastKnownLocation(ProceedingJoinPoint joinPoint) {
return handleProceedingJoinPoint(joinPoint, null);
}
@Around(POINT_CUT_REQUEST_LOCATION_UPDATES)
public void callRequestLocationUpdates(ProceedingJoinPoint joinPoint) {
handleProceedingJoinPoint(joinPoint, null);
}
@Around(POINT_CUT_REQUEST_LOCATION_SINGLE)
public void callRequestSingleUpdate(ProceedingJoinPoint joinPoint) {
handleProceedingJoinPoint(joinPoint, null);
}
// ···
}
复制代码
定义一个切面PrivacyAspect,和须要检查调用的方法的切入点。其中使用Around替换对敏感API的调用的代码,调用handleProceedingJoinPoint处理,第一个参数是链接点ProceedingJoinPoint,第二个参数是默认返回值(若原方法有返回值,则会返回结果)。
接着进入handleProceedingJoinPoint方法:
private Object handleProceedingJoinPoint(ProceedingJoinPoint joinPoint, Object fakeResult) {
if (!PrivacyController.isUserAllowed()) {
// 若用户未赞成
StringBuilder sb = new StringBuilder();
// 打印调用的方法和该调用所在位置
sb.append("用户未赞成时执行了").append(joinPoint.getSignature().toShortString())
.append(" [").append(joinPoint.getSourceLocation()).append("]");
LogUtils.e(TAG, sb.toString());
// 返回一个空的默认值
return fakeResult;
}
try {
// 执行原方法,返回原结果
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return fakeResult;
}
复制代码
该方法中判断用户是否赞成。若未赞成,则返回空的返回值。不然放行,调用原方法。
部分三方SDK中会经过反射调用敏感API,而且对方法名称字符串作加密处理,以绕过静态检查,所以也须要对反射调用进行拦截。
@Aspect
public class PrivacyAspect {
// 拦截反射的调用
private static final String POINT_CUT_METHOD_INVOKE = "call (* java.lang.reflect.Method.invoke(..))";
// 反射方法黑名单
private static final List<String> REFLECT_METHOD_BLACKLIST = Arrays.asList(
"getInstalledApplications",
"getInstalledPackages",
"getImei",
"getDeviceId",
"getLine1Number",
"getLastKnownLocation",
"loadClass"
);
@Around(POINT_CUT_METHOD_INVOKE)
public Object callReflectInvoke(ProceedingJoinPoint joinPoint) {
// 获取该链接点调用的方法名称
String methodName = ((Method) joinPoint.getTarget()).getName();
if (REFLECT_METHOD_BLACKLIST.contains(methodName)) {
// 如果黑名单中的方法,则进行检查
return handleProceedingJoinPoint(joinPoint, null);
}
try {
// 执行原方法,返回原结果
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
复制代码
经过拦截Method.invoke的调用,判断反射调用的方法是否是黑名单中的方法。
@Aspect
public class PrivacyAspect {
// 拦截加载类的调用
private static final String POINT_CUT_DEX_FIND_CLASS = "call (* java.lang.ClassLoader.loadClass(..))";
@Around(POINT_CUT_DEX_FIND_CLASS)
public Object callLoadClass(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
// 打印该链接点的相关信息
StringBuilder sb = new StringBuilder();
sb.append(joinPoint.getThis()).append("中动态加载");
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
sb.append("\"").append(args[0]).append("\"");
}
sb.append("获得").append(result);
sb.append(" ").append(joinPoint.getSourceLocation());
LogUtils.w(TAG, sb.toString());
return result;
}
}
复制代码
拦截到loadClass后,打印日志输出调用处的位置。
public void interceptPrivacy(View view) {
Log.d(TAG, "用户赞成: " + PrivacyController.isUserAllowed());
// 获取手机安装应用信息
List<ApplicationInfo> applicationInfos = DeviceUtils.getInstalledApplications(this);
if (applicationInfos != null && applicationInfos.size() > 5) {
applicationInfos = applicationInfos.subList(0, 5);
}
Log.d(TAG, "getInstalledApplications: " + applicationInfos);
// 获取手机安装应用信息
List<PackageInfo> packageInfos = DeviceUtils.getInstalledPackages(this);
if (packageInfos != null && packageInfos.size() > 5) {
packageInfos = packageInfos.subList(0, 5);
}
Log.d(TAG, "getInstalledPackages: " + packageInfos);
// 获取imei
Log.d(TAG, "getImei: " + DeviceUtils.getImeiValue(this));
// 获取电话号码
Log.d(TAG, "getLine1Number: " + DeviceUtils.getLine1Number(this));
// 获取定位信息
Log.d(TAG, "getLastKnownLocation: " + DeviceUtils.getLastKnownLocation(this));
try {
// 加载一个类
Log.d(TAG, "loadClass: " + getClassLoader().loadClass("com.cdh.aop.sample.op.BaseOp"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
// 经过反射获取手机安装应用信息
PackageManager pm = getPackageManager();
Method method = PackageManager.class.getDeclaredMethod("getInstalledApplications", int.class);
List<ApplicationInfo> list = (List<ApplicationInfo>) method.invoke(pm, 0);
if (list != null && list.size() > 5) {
list = list.subList(0, 5);
}
Log.d(TAG, "reflect getInstalledApplications: " + list);
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
运行后查看logcat输出日志:
在集成AspectJX框架打包apk后可能会遇到ClassNotFoundException,反编译apk发现不少类没有打进去,甚至包括Application。绝大部分缘由是由于依赖的三方库中使用了AspectJ框架致使的冲突,或者是本身写的切入点的语法有错误,或织入代码有问题,例如方法返回值没有对应上,或者对同一个切入点定义了有冲突的通知。若发生错误,会在build中显示错误信息。
若是不用AOP思想和AspectJ框架实现上面的需求,会有不少繁琐的工做量。这里经过几个简单场景的应用,能够发现若能深刻理解AOP思想和掌握AspectJ使用,会对架构设计和开发效率有很大的提高和帮助。
文中示例完整源码见Efficiency-Toys