前言
Android 性能调优中,通常存在需要对方法的执行时间进行统计的需求,这样就可以看出哪些方法耗时多,是系统的瓶颈。最容易想到的方案是在每个方法的开头处获取系统时间,在方法的结尾处再次获取系统时间,前后两个时间戳的差值就是这个方法执行所消耗的总时间;
Hugo项目是一个调试函数调用耗时的工具,通过对方法或者类添加@DebugLog注解,在运行时会将函数的耗时打印在控制台中,通常用于排查函数耗时,或者用于卡顿检测;
hugo 这个框架麻雀虽小但五脏俱全,它使用了很多 Android 开发中流行的技术,例如注解,AOP,AspectJ,Gradle 插件;
一、hugo插件详解
1、hugo使用
hugo 以 gradle 插件的形式供开发者集成和使用,分为两步:
- 在项目全局添加对 hugo 插件的依赖
- 在需要使用 hugo 的 module 中应用 hugo 插件
- buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' // 添加 Hugo 的 Gradle 插件依赖
- }
- }
- apply plugin: 'com.jakewharton.hugo' // 应用 Hugo 插件
2、hugo源码分析
①aspectjrt.jar:aspectJ 运行时的依赖库,想要使用 aspectJ 的功能都需要引入这个库;
hugo-annotations:hugo 的注解库,定义了 DebugLog 这个注解;
- @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
- public @interface DebugLog {
- }
②hugo-runtime:hugo 的运行时库,是实现 hugo 日志功能的核心库;
③hugo-plugin:hugo 的插件库,主要实现了aop的插件;
- class HugoPlugin implements Plugin<Project> {
- @Override void apply(Project project) {
- def hasApp = project.plugins.withType(AppPlugin)
- def hasLib = project.plugins.withType(LibraryPlugin)
- if (!hasApp && !hasLib) {
- throw new IllegalStateException("'android' or 'android-library' plugin required.")
- }
- final def log = project.logger
- final def variants
- if (hasApp) {
- variants = project.android.applicationVariants
- } else {
- variants = project.android.libraryVariants
- }
- project.dependencies {
- debugCompile 'com.jakewharton.hugo:hugo-runtime:1.2.2-SNAPSHOT'
- // TODO this should come transitively
- debugCompile 'org.aspectj:aspectjrt:1.8.6'
- compile 'com.jakewharton.hugo:hugo-annotations:1.2.2-SNAPSHOT'
- }
- project.extensions.create('hugo', HugoExtension)
- variants.all { variant ->
- if (!variant.buildType.isDebuggable()) {
- log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
- return;
- } else if (!project.hugo.enabled) {
- log.debug("Hugo is not disabled.")
- return;
- }
- JavaCompile javaCompile = variant.javaCompile
- javaCompile.doLast {
- String[] args = [
- "-showWeaveInfo",
- "-1.5",
- "-inpath", javaCompile.destinationDir.toString(),
- "-aspectpath", javaCompile.classpath.asPath,
- "-d", javaCompile.destinationDir.toString(),
- "-classpath", javaCompile.classpath.asPath,
- "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
- ]
- log.debug "ajc args: " + Arrays.toString(args)
- MessageHandler handler = new MessageHandler(true);
- new Main().run(args, handler);
- for (IMessage message : handler.getMessages(null, true)) {
- switch (message.getKind()) {
- case IMessage.ABORT:
- case IMessage.ERROR:
- case IMessage.FAIL:
- log.error message.message, message.thrown
- break;
- case IMessage.WARNING:
- log.warn message.message, message.thrown
- break;
- case IMessage.INFO:
- log.info message.message, message.thrown
- break;
- case IMessage.DEBUG:
- log.debug message.message, message.thrown
- break;
- }
- }
- }
- }
- }
- }
3、代码实操
需要进行日志记录的类名或者方法名处使用 @DebugLog 注解标记即可;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- @DebugLog
- private void test(String... tests) {
- for (String arg : tests) {
- Log.i("Args", arg);
- }
- }
二、AOP详解
1、什么是aop
AOP,全称为 Aspect Oriented Programming,即面向切面编程;AOP 是软件开发中的一个编程范式,通过预编译方式或者运行期动态代理等实现程序功能的统一维护的一种技术,它是 OOP(面向对象编程)的延续,利用 AOP 开发者可以实现对业务逻辑中的不同部分进行隔离,从而进一步降低耦合,提高程序的可复用性,进而提高开发的效率;
aop涉及到的关键知识点:
- 横切关注点(Cross-cutting concerns):在面向对象编程中,经常需要在不同的模块代码中添加一些类似的代码,例如在函数入口处打印日志,在 View 的点击处添加点击事件的埋点统计,在 AOP 中把软件系统分成两个部分:核心关注点和横切关注点,核心关注点就是业务逻辑处理的主要流程,而横切关注点就是上面所说的经常发生在核心关注点的多个地方,且基本相似的日志纪录,埋点统计等等;
- 连接点(Joint point):在核心关注点中可能会存在横切关注点的地方,例如方法调用的入口,View 的点击处理等地方,在 AOP 中习惯称为连接点;
- 增强(Advice):特定连接点处所执行的动作,也就是 AOP 织入的代码,目的是对原有代码进行功能的增强;
- before:在目标方法执行之前的动作;
- around:在目标方法之前前后的动作;
- after:在目标方法执行之后的动作;
- 切入点(Pointcut):连接点的集合,这些连接点可以确定什么时机会触发一个通知;
- 切面(Aspect):切入点和通知可以组合成一个切面;
- 织入(Weaving):将通知注入到连接点的过程;
AOP 中代码的织入根据类型的不同,主要可以分为三类:
- 编译时织入:在 Java 类文件编译的时候进行织入,这需要通过特定的编译器来实现,例如使用 AspectJ 的织入编译器;
- 类加载时织入:通过自定义类加载器 ClassLoader 的方式在目标类被加载到虚拟机之前进行类的字节代码的增强;
- 运行时织入:切面在运行的某个时刻被动态织入,基本原理是使用 Java 的动态代理技术;
2、Android中aop实现:用aspectj实现aop
2.1、什么是AspectJ
①AspectJ实际上是对AOP编程思想的一个实践,AOP虽然是一种思想,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP;
基础知识点:
- Aspect 切面:切面是切入点和通知的集合;
- PointCut 切入点:切入点是指那些通过使用一些特定的表达式过滤出来的想要切入Advice的连接点;
- Advice 通知:通知是向切点中注入的代码实现方法;
- Joint Point 连接点:所有的目标方法都是连接点;
- Weaving 编织:主要是在编译期使用AJC将切面的代码注入到目标中, 并生成出代码混合过的.class的过程;
涉及到的注解:
- @Aspect:声明切面,标记类
- @Pointcut(切点表达式):定义切点,标记方法
- @Before(切点表达式):前置通知,切点之前执行
- @Around(切点表达式):环绕通知,切点前后执行
- @After(切点表达式):后置通知,切点之后执行
- @AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
- @AfterThrowing(切点表达式):异常通知,切点抛出异常时执行
2.2、实现一个网络状态检测的AOP
①aspectj配置
项目的gradle中配置build.gradle(project)
- buildscript {
- repositories {
- google()
- mavenCentral()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:4.1.1'
- classpath 'org.aspectj:aspectjtools:1.8.6'
- }
- }
- ......
- ......
主app中build.gradle(app)
- dependencies {
- compile 'org.aspectj:aspectjrt:1.8.6'
- }
- android.libraryVariants.all { variant ->
- JavaCompile javaCompile = variant.javaCompile
- javaCompile.doLast {
- String[] args = [
- "-showWeaveInfo",
- "-1.5",
- "-inpath", javaCompile.destinationDir.toString(),
- "-aspectpath", javaCompile.classpath.asPath,
- "-d", javaCompile.destinationDir.toString(),
- "-classpath", javaCompile.classpath.asPath,
- "-bootclasspath", android.bootClasspath.join(File.pathSeparator)
- ]
- MessageHandler handler = new MessageHandler(true);
- new Main().run(args, handler)
- def log = project.logger
- for (IMessage message : handler.getMessages(null, true)) {
- switch (message.getKind()) {
- case IMessage.ABORT:
- case IMessage.ERROR:
- case IMessage.FAIL:
- log.error message.message, message.thrown
- break;
- case IMessage.WARNING:
- case IMessage.INFO:
- log.info message.message, message.thrown
- break;
- case IMessage.DEBUG:
- log.debug message.message, message.thrown
- break;
- }
- }
- }
- }
②aop实现
定义annotation:
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface CheckNetwork {
- }
代码注解:
- @CheckNetwork()
- private void checkNetwork() {
- LogUtil.i("AnnotationFragment", "检测完毕");
- }
关键处理切入点:
- @Aspect
- public class CheckNetworkAspect {
- private static final String TAG = CheckNetworkAspect.class.getSimpleName();
- /**
- * 找到处理的切点
- * * *(..) “**”表示是任意包名 “..”表示任意类型任意多个参数
- */
- @Pointcut("execution(@la.xiong.androidquick.demo.features.function.annotation.aspect.CheckNetwork * *(..))")
- public void executionCheckNetwork() {
- }
- /**
- * 处理切面
- *
- * @param joinPoint
- * @return
- */
- @Around("executionCheckNetwork()")
- public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
- MethodSignature signature = (MethodSignature) joinPoint.getSignature();
- CheckNetwork annotation = signature.getMethod().getAnnotation(CheckNetwork.class);
- if (annotation != null) {
- Context context = AspectUtils.getContext(joinPoint.getThis());
- if (NetworkUtils.isConnected()) {
- Toast.makeText(context, "当前网络正常", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(context, "此时没有网络连接", Toast.LENGTH_SHORT).show();
- }
- return joinPoint.proceed();
- }
- return null;
- }
- }
总结:
AOP 能够实现将日志纪录,性能统计,埋点统计,安全控制,异常处理等代码从具体的业务逻辑代码中抽取出来,放到统一的地方进行处理;
利用 AOP 开发者可以实现对业务逻辑中的不同部分进行隔离,从而进一步降低耦合,提高程序的可复用性,进而提高开发的效率;
可以自定义属于你的功能比如:日志记录,性能统计,安全控制,事务处理,异常处理等等。
本文转载自微信公众号「Android开发编程」,可以通过以下二维码关注。转载本文请联系Android开发编程公众号。