AspectJ 在 Android 中的完整使用指南

发布于:2025-06-10 ⋅ 阅读:(45) ⋅ 点赞:(0)

一、环境配置(Gradle 7.0+ 适配)

1. 项目级 build.gradle
// 注意:沪江插件已停更,推荐官方兼容方案
buildscript {
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.9.9.1' // AspectJ 工具
    }
}

2. 模块级 build.gradle

plugins {
    id 'com.android.application'
    id 'io.freefair.aspectj' // 官方推荐插件(支持 AGP 7.0+)
}

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.9.1' // 运行时库
}

兼容性说明:若项目含 Kotlin,添加 id 'io.freefair.aspectj.post-compile-weaving' 插件

二、核心语法详解

1. 切点表达式(Pointcut)
表达式示例 含义
execution(public * *(..)) 所有 public 方法
execution(* com.util.*.*(..)) com.util 包下所有方法
@annotation(com.LogTrack) 被 @LogTrack 注解的方法
within(com.ui..*) com.ui 包及其子包所有类的方法
call(* android.util.Log.d(..)) 拦截 Log.d() 的调用(非执行)
2. 通知类型(Advice)
@Before("pointcut()")          // 方法执行前
@After("pointcut()")           // 方法执行后(无论成败)
@AfterReturning(pointcut="", returning="result") // 方法返回后
@AfterThrowing(pointcut="", throwing="ex") // 抛出异常后
@Around("pointcut()")          // 完全控制方法执行(最常用)

三、实战案例

案例 1:自动日志追踪
@Aspect
public class DebugLogAspect {

    // 切点:标记 @DebugLog 的方法
    @Pointcut("execution(@com.example.DebugLog * *(..))")
    public void debugLogPointcut() {}

    @Around("debugLogPointcut()")
    public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        long start = System.currentTimeMillis();
        
        Log.d("AspectJ", "▶️ " + methodName + " 开始 | 参数: " + Arrays.toString(joinPoint.getArgs()));
        
        Object result = joinPoint.proceed(); // 执行原方法
        
        long duration = System.currentTimeMillis() - start;
        Log.d("AspectJ", "◀️ " + methodName + " 结束 | 耗时: " + duration + "ms | 结果: " + result);
        
        return result;
    }
}
案例 2:权限申请自动化
// 定义权限注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequirePermission {
    String[] value(); // 需要申请的权限
}

// 切面实现
@Aspect
public class PermissionAspect {
    
    @Around("execution(@RequirePermission * *(..)) && @annotation(permission)")
    public Object checkPermission(ProceedingJoinPoint pjp, RequirePermission permission) throws Throwable {
        Activity activity = (Activity) pjp.getTarget(); // 获取宿主Activity
        String[] perms = permission.value();
        
        if (hasPermissions(activity, perms)) {
            return pjp.proceed(); // 已有权限
        } else {
            // 发起权限申请(需配合 ActivityResultLauncher)
            PermissionRequester.request(activity, perms, () -> {
                try { pjp.proceed(); } catch (Throwable ignored) {} 
            });
            return null; // 阻断原方法执行
        }
    }
    
    private boolean hasPermissions(Context ctx, String... perms) {
        for (String perm : perms) {
            if (ContextCompat.checkSelfPermission(ctx, perm) != PERMISSION_GRANTED) 
                return false;
        }
        return true;
    }
}

四、高频问题解决方案

问题 1:织入(Weaving)失效

排查步骤

  1. 检查是否应用了 AGP 7.0+ 的兼容插件

  2. 确认 aspectjrt 版本一致性

  3. 使用命令检查织入结果:

./gradlew clean assembleDebug --debug | grep "ajc"
问题 2:Lambda 表达式支持

在 build.gradle 中添加:

aspectj {
    excludeJar "com.android.support" // 排除冲突库
    weaveInfo = true
    addSerialVoid = true // 修复 Lambda 支持
}

五、性能优化建议

  1. 避免在切面中执行耗时操作
    (如:IO 读写、网络请求)

  2. 精确限定切点范围

// 劣质写法:扫描全包
@Pointcut("execution(* com.app..*.*(..))")

// 优化写法:精确到类
@Pointcut("within(com.ui.HomeActivity) || within(com.service.*Impl)")

        编译时排除第三方库

aspectj {
    exclude "androidx.*", "com.google.*" 
}

六、AspectJ vs 其他方案对比

方案 集成难度 性能 功能完整性 适用场景
AspectJ ★★★☆ ⚡⚡⚡ ✅ 100% 需要完整 AOP 支持
ASM ★★★★☆ ⚡⚡⚡⚡ ⚠️ 手动实现 高性能字节码操作
动态代理 ★★☆ ❌ 仅限接口 简单运行时拦截
注解处理器 ★★★☆ ⚡⚡ ⚠️ 无执行期 生成代码替代运行时逻辑

💡 结论:AspectJ 仍是 Android 平台功能最完备的 AOP 方案,适合复杂横切逻辑。

七、高级技巧:编译时代码注入

在 @Around 中动态修改参数:

@Around("execution(* com.payment.process(..))")
public Object encryptPayment(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();
    if (args.length > 0 && args[0] instanceof PaymentRequest) {
        PaymentRequest request = (PaymentRequest) args[0];
        request.setCardToken(AES.encrypt(request.getCardNumber())); // 自动加密
        args[0] = request; // 替换参数
    }
    return pjp.proceed(args); // 传入新参数
}

通过本指南,你可快速掌握 AspectJ 在 Android 中的工程化应用。建议从 日志监控权限管理 等场景入手,逐步深入字节码层优化。


网站公告

今日签到

点亮在社区的每一天
去签到