wurensen / gradle_plugin_android_aspectjx

A Android gradle plugin that effects AspectJ on Android project and can hook methods in Kotlin, aar and jar file.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AOP 多注解出现失效或发生异常(EmptyStackException)

880634 opened this issue · comments

请提供构建环境相关信息:

  • 当前使用的插件版本:3.3.1

  • AGP(Android Gradle Plugin)版本:7.1.0

  • Gradle版本:7.2-all.zip

发送构建错误时,请先确定是构建错误还是aspectj织入错误:

已自查,并没有找到对应的记录

  • 如果是aspectj织入发生异常,会在对应module下的build/tmp/transformClassesWithAjxForXXX/logs目录下产生ajcore为前缀的日志文件,请提供该日志文件以便查找问题

非编译时的问题,而是运行时的问题

  • 如果是其它错误,请尽量提供完整的堆栈信息
W/System.err: java.util.EmptyStackException
W/System.err:     at java.util.Stack.peek(Stack.java:102)
W/System.err:     at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:170)
W/System.err:     at com.hjq.demo.aop.PermissionsAspect$1.onGranted(PermissionsAspect.java:78)
W/System.err:     at com.hjq.demo.other.PermissionInterceptor.grantedPermissionRequest(PermissionInterceptor.java:123)
W/System.err:     at com.hjq.permissions.PermissionFragment.onRequestPermissionsResult(PermissionFragment.java:394)
W/System.err:     at android.app.Activity.dispatchRequestPermissionsResultToFragment(Activity.java:7626)
W/System.err:     at android.app.Activity.dispatchActivityResult(Activity.java:7470)
W/System.err:     at android.app.ActivityThread.deliverResults(ActivityThread.java:4391)
W/System.err:     at android.app.ActivityThread.handleSendResult(ActivityThread.java:4440)
W/System.err:     at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
W/System.err:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
W/System.err:     at android.os.Looper.loop(Looper.java:193)
W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6718)
W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
  • 其他相关代码
@Aspect
public class PermissionsAspect {

    /**
     * 方法切入点
     */
    @Pointcut("execution(@com.hjq.demo.aop.Permissions * *(..))")
    public void method() {}

    /**
     * 在连接点进行方法替换
     */
    @Around("method() && @annotation(permissions)")
    public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Permissions permissions) {
        Activity activity = null;

        // 方法参数值集合
        Object[] parameterValues = joinPoint.getArgs();
        for (Object arg : parameterValues) {
            if (!(arg instanceof Activity)) {
                continue;
            }
            activity = (Activity) arg;
            break;
        }

        if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
            activity = ActivityManager.getInstance().getTopActivity();
        }

        if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
            Timber.e("The activity has been destroyed and permission requests cannot be made");
            return;
        }

        requestPermissions(joinPoint, activity, permissions.value());
    }

    private void requestPermissions(ProceedingJoinPoint joinPoint, Activity activity, String[] permissions) {
        XXPermissions.with(activity)
                .permission(permissions)
                .interceptor(new PermissionInterceptor())
                .request(new OnPermissionCallback() {

                    @Override
                    public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
                        if (allGranted) {
                            try {
                                // 获得权限,执行原方法
                                joinPoint.proceed();
                            } catch (Throwable e) {
                                e.printStackTrace();
                                CrashReport.postCatchedException(e);
                            }
                        }
                    }
                });
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permissions {

    /**
     * 需要申请权限的集合
     */
    String[] value();
}
@Aspect
public class LogAspect {

    /**
     * 构造方法切入点
     */
    @Pointcut("execution(@com.hjq.demo.aop.Log *.new(..))")
    public void constructor() {}

    /**
     * 方法切入点
     */
    @Pointcut("execution(@com.hjq.demo.aop.Log * *(..))")
    public void method() {}

    /**
     * 在连接点进行方法替换
     */
    @Around("(method() || constructor()) && @annotation(log)")
    public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        enterMethod(joinPoint, log);

        long startNanos = System.nanoTime();
        Object result = joinPoint.proceed();
        long stopNanos = System.nanoTime();

        exitMethod(joinPoint, log, result, TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos));

        return result;
    }

    /**
     * 方法执行前切入
     */
    private void enterMethod(ProceedingJoinPoint joinPoint, Log log) {
        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

        // 方法所在类
        String className = codeSignature.getDeclaringType().getName();
        // 方法名
        String methodName = codeSignature.getName();
        // 方法参数名集合
        String[] parameterNames = codeSignature.getParameterNames();
        // 方法参数值集合
        Object[] parameterValues = joinPoint.getArgs();

        //记录并打印方法的信息
        StringBuilder builder = getMethodLogInfo(className, methodName, parameterNames, parameterValues);

        log(log.value(), builder.toString());

        final String section = builder.substring(2);
        Trace.beginSection(section);
    }

    /**
     * 获取方法的日志信息
     *
     * @param className         类名
     * @param methodName        方法名
     * @param parameterNames    方法参数名集合
     * @param parameterValues   方法参数值集合
     */
    @NonNull
    private StringBuilder getMethodLogInfo(String className, String methodName, String[] parameterNames, Object[] parameterValues) {
        StringBuilder builder = new StringBuilder("\u21E2 ");
        builder.append(className)
                .append(".")
                .append(methodName)
                .append('(');
        for (int i = 0; i < parameterValues.length; i++) {
            if (i > 0) {
                builder.append(", ");
            }
            builder.append(parameterNames[i]).append('=');
            builder.append(parameterValues[i]);
        }
        builder.append(')');

        if (Looper.myLooper() != Looper.getMainLooper()) {
            builder.append(" [Thread:\"").append(Thread.currentThread().getName()).append("\"]");
        }
        return builder;
    }

    /**
     * 方法执行完毕,切出
     *
     * @param result            方法执行后的结果
     * @param lengthMillis      执行方法所需要的时间
     */
    private void exitMethod(ProceedingJoinPoint joinPoint, Log log, Object result, long lengthMillis) {
        Trace.endSection();

        Signature signature = joinPoint.getSignature();

        String className = signature.getDeclaringType().getName();
        String methodName = signature.getName();

        StringBuilder builder = new StringBuilder("\u21E0 ")
                .append(className)
                .append(".")
                .append(methodName)
                .append(" [")
                .append(lengthMillis)
                .append("ms]");

        //  判断方法是否有返回值
        if (signature instanceof MethodSignature && ((MethodSignature) signature).getReturnType() != void.class) {
            builder.append(" = ");
            builder.append(result.toString());
        }

        log(log.value(), builder.toString());
    }

    private void log(String tag, String msg) {
        Timber.tag(tag);
        Timber.d(msg);
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface Log {

    String value() default "AOPLog";
}
public final class ImageSelectActivity extends AppActivity {

    .....

    @Log
    @Permissions({Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE})
    public static void start(BaseActivity activity, int maxSelect, OnPhotoSelectListener listener) {
        .....
    }
    .....
}
  • 问题原因

我在某个方法上面加了两个 AOP 注解,一个是打印方法名及参数的 Log 的 AOP 注解,另外一个是请求权限的 AOP 注解,现在出现了一种情况,权限回调中有正常调用 joinPoint.proceed(),但是会出现 java.util.EmptyStackException 异常,最终导致切面无法往下执行。

  • 问题排查

我如果删掉 Log AOP 注解,则就没有异常了。又或者权限是授予状态,也是没有问题的,因为调用 XXPermissions 申请权限的时候,它会先判断授权有没有授予,有的话会直接回调授予的方法(同步操作),如果没有授予权限的话,则会开启一个透明的 Fragment 来发起申请权限(异步操作),可能是因为这个原因不行的,但是权限框架这样做是没有问题的。

之前使用 HujiangTechnology/gradle_plugin_android_aspectjx 是没有问题的,现在换成了 wurensen/gradle_plugin_android_aspectjx 就报这个错了,劳烦大大有空看一下

@880634 我先复现看看

@880634 我反编译看了,当前版本的aspectj设计上,不支持异步调用。可以看下相关问题:eclipse-aspectj/aspectj#128

@wurensen 大大,但是我如果删除 Log AOP 注解就没有问题了,但是一加上就不行了,好像是不能同时存在有两个不同的 AOP 注解,一个同步,一个异步的那种。

@880634 我先复现看看

大大,你这边能复现么?如果不行的话我这边可以发一份我的工程给你

@880634 能复现。上面说的异步不够准确,有延期调用也不行。比如先中断,申请权限,等到有结果后,再恢复调用。因为它动态修改的代码会封装成闭包、退栈,延期调用就会出现这个异常。
不是说不能不同的aop注解,而是不能出现延期调用。
你可以先取巧,多封装出一个函数,注解分别给不同的函数,绕过去这个问题试试。

image
可以看下反编译大概的执行流程,就知道为什么会出现该问题了,正常你多个注解连续处理,是没有问题的,但是类似“中断-恢复”这样的方式,就有异常了。

旧版本可以,估计是因为你用的aspectjrt版本在1.9.3以下,从1.9.3开始,aspectjrt修复了多次调用的问题,但同时也造成了当前的问题,可以看看当时修复多次调用的bug记录:https://bugs.eclipse.org/bugs/show_bug.cgi?id=333274

这个问题估计要自己取巧来解决,避免同一个切入点多个注解进行织入处理。
或者反馈给aspectj,让修改同一个切入点多注解织入的机制,但是不大可能,因为这种延期使用就不符合它目前的设计。

@wurensen 大大,这个好像跟 aspectjrt 的版本没太大关系,我之前用的是 HujiangTechnology/gradle_plugin_android_aspectjx 最后一个版本,classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10',这个版本对应的 aspectjrt 的版本是 1.9.5,就没有这个问题,大佬你可以下载 getActivity/AndroidProject 这个工程试一下,确实和你说 1.9.3 版本的有一定出入,劳烦大大核实一下。

@880634 我上面解释得很清楚了,它目前的机制设计上就不支持这种“延期调用”,我说的1.9.3也是根据官方那边的issue内容摘抄过来的,具体是哪个版本引入这个机制我没有去细究,因为这个不重要。至少从我引入版本开始,都是用的新的机制。
解决你这个问题,不可能去回退aspectjrt版本,因为会引发其它问题,具体问题我上面贴的官方修复这个bug的原因里有。
所以,建议你通过取巧方式来解决,多套一层包装函数,两个注解使用到不同函数上。

@wurensen 大大,你说的不支持延期调用,但是我如果只写 @Permissions 注解就可以了,我试过了没有问题,你看看是不是弄错了?

@880634 我们讨论的前提是多注解啊,你只有一个注解的时候是没有问题的。所以我才建议你写包装函数,各一个注解,然后切入实现代码那边就不用改了。

@wurensen 大大,我不想多写几个重载函数,我更想要是直接当个小白用户,直接用就可以了,不需要考虑那么多,大大你有研究过有什么兼容这个问题的方案吗?

大大,我这个需求也不奇葩,并且合情合理,权限请求肯定是异步了,还有多加一个日志打印,这么简单需求难道还要开发者做兼容么?总感觉这样的处理方式不太好,我用 AOP 的初心有两个:1. 代码简洁美观;2. 减少重复代码,但是这个问题又得让我回归最原始的写法。

@880634 问题不是我不解决,而是这个插件只是对aspectj的使用包装,这个问题是aspectj生成代码的设计机制就是这样。