Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
880634 opened this issue Dec 18, 2022 · 15 comments
Closed

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

880634 opened this issue Dec 18, 2022 · 15 comments
Labels
wontfix This will not be worked on

Comments

@880634
Copy link

880634 commented Dec 18, 2022

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

  • 当前使用的插件版本: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 就报这个错了,劳烦大大有空看一下

@wurensen
Copy link
Owner

@880634 我先复现看看

@wurensen
Copy link
Owner

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

@880634
Copy link
Author

880634 commented Dec 20, 2022

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

@880634
Copy link
Author

880634 commented Dec 20, 2022

@880634 我先复现看看

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

@wurensen
Copy link
Owner

wurensen commented Dec 20, 2022

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

@wurensen
Copy link
Owner

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

@wurensen
Copy link
Owner

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

@wurensen
Copy link
Owner

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

@wurensen wurensen changed the title AOP 注解出现失效 AOP 多注解出现失效 Dec 20, 2022
@wurensen wurensen added the wontfix This will not be worked on label Dec 20, 2022
@wurensen wurensen pinned this issue Dec 20, 2022
@wurensen wurensen changed the title AOP 多注解出现失效 AOP 多注解出现失效或发生异常(EmptyStackException) Dec 20, 2022
@880634
Copy link
Author

880634 commented Dec 25, 2022

@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 版本的有一定出入,劳烦大大核实一下。

@wurensen
Copy link
Owner

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

@880634
Copy link
Author

880634 commented Jan 12, 2023

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

@wurensen
Copy link
Owner

wurensen commented Jan 13, 2023

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

@880634
Copy link
Author

880634 commented Jan 15, 2023

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

@880634
Copy link
Author

880634 commented Jan 15, 2023

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

@wurensen
Copy link
Owner

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

2 participants