前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >android字节码框架——AspectJ

android字节码框架——AspectJ

作者头像
老马的编程之旅
发布2022-06-22 15:32:52
1K0
发布2022-06-22 15:32:52
举报
文章被收录于专栏:深入理解Android

最常用的字节码处理框架有 AspectJ、ASM 等等,它们的相同之处在于输入输出都是 Class 文件。并且,它们都是 在 Java 文件编译成 .class 文件之后,生成 Dalvik 字节码之前执行。 而 AspectJ 作为 Java 中流行的 AOP(aspect-oriented programming) 编程扩展框架,其内部使用的是 BCEL框架 来完成其功能。

AspectJ 的优势

它的优势有两点:成熟稳定、使用非常简单。

使用非常简单 AspectJ 可以在如下五个位置插入自定义的代码:

1)、在方法(包括构造方法)被调用的位置。 2)、在方法体(包括构造方法)的内部。 3)、在读写变量的位置。 4)、在静态代码块内部。 5)、在异常处理的位置的前后。 此外,它也可以 直接将原位置的代码替换为自定义的代码。

AspectJ 的缺陷

AspectJ缺点: 1.切入点固定 AspectJ 只能在一些固定的切入点来进行操作

2.正则表达式的局限性 AspectJ 的匹配规则采用了类似正则表达式的规则,比如 匹配 Activity 生命周期的 onXXX 方法,如果有自定义的其他以 on 开头的方法也会匹配到,这样匹配的正确性就无法满足。

3.性能较低 AspectJ 在实现时会包装自己一些特定的类,它并不会直接把 Trace 函数直接插入到代码中,导致生成的字节码比较大,影响性能,如果想对 App 中所有的函数都进行插桩,性能影响肯定会比较大。如果你只插桩一小部分函数,那么 AspectJ 带来的性能损耗几乎可以忽略不计。

AspectJX 实战

代码语言:javascript
复制
    classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'

然后,在 app 目录下的 build.gradle 下加入:

代码语言:javascript
复制
    apply plugin: 'android-aspectjx'
    implement 'org.aspectj:aspectjrt:1.8.+'

简单的 AspectJ 示例

代码语言:javascript
复制
    @Before("execution(* android.app.Activity.on**(..))")
    public void onActivityCalled(JoinPoint joinPoint) throws Throwable {
        Log.d(...)
    }

execution 中的是一个匹配规则,第一个 * 代表匹配任意的方法返回值,后面的语法代码匹配所有 Activity 中以 on 开头的方法。这样,我们就可以 在 App 中所有 Activity 中以 on 开头的方法中打印一句 log。

上面的 execution 就是处理 Join Point 的类型,通常有如下两种类型:

1)、call:代表调用方法的位置,插入在函数体外面。 2)、execution:代表方法执行的位置,插入在函数体内部。

统计 Application 中所有方法的耗时

代码语言:javascript
复制
    @Aspect
    public class ApplicationAop {
    
        @Around("call (* com.json.chao.application.BaseApplication.**(..))")
        public void getTime(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.toShortString();
        long time = System.currentTimeMillis();
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        Log.i(TAG, name + " cost" +     (System.currentTimeMillis() - time));
        }
    }

当 Action 为 Before、After 时,方法入参为 JoinPoint。当 Action 为 Around 时,方法入参为 ProceedingPoint。 而 Around 和 Before、After 的最大区别就是 ProceedingPoint 不同于 JoinPoint,其提供了 proceed 方法执行目标方法。

对 App 中所有的方法进行 Systrace 函数插桩

代码语言:javascript
复制
    @Aspect
    public class SystraceTraceAspectj {

        private static final String TAG = "SystraceTraceAspectj";

        @Before("execution(* **(..))")
        public void before(JoinPoint joinPoint) {
            TraceCompat.beginSection(joinPoint.getSignature().toString());
        }
    
        @After("execution(* **(..))")
        public void after() {
            TraceCompat.endSection();
        }
    }

AspectJ 在性能监控框架中应用

奇虎360的 ArgusAPM 性能监控框架来全面分析下 AOP 技术在性能监控方面的应用。主要分为如下 三个部分:

1)、监控应用冷热启动耗时与生命周期耗时。 2)、监控 OKHttp3 的每一次网络请求。 3)、监控 HttpConnection 的每一次网络请求。

代码语言:javascript
复制
    @Aspect
    public class TraceActivity {

        // 1、定义一个切入点方法 baseCondition,用于排除 argusapm 中相应的类。
        @Pointcut("!within(com.argusapm.android.aop.*) && !within(com.argusapm.android.core.job.activity.*)")
        public void baseCondition() {
        }

        // 2、定义一个切入点 applicationOnCreate,用于执行 Application 的 onCreate方法。
        @Pointcut("execution(* android.app.Application.onCreate(android.content.Context)) && args(context)")
        public void applicationOnCreate(Context context) {

        }

        // 3、定义一个后置通知 applicationOnCreateAdvice,用于在 application 的 onCreate 方法执行完之后插入 AH.applicationOnCreate(context) 这行代码。
        @After("applicationOnCreate(context)")
        public void applicationOnCreateAdvice(Context context) {
            AH.applicationOnCreate(context);
        }

        // 4、定义一个切入点,用于执行 Application 的 attachBaseContext 方法。
        @Pointcut("execution(* android.app.Application.attachBaseContext(android.content.Context)) && args(context)")
        public void applicationAttachBaseContext(Context context) {
        }

        // 5、定义一个前置通知,用于在 application 的 onAttachBaseContext 方法之前插入 AH.applicationAttachBaseContext(context) 这行代码。
        @Before("applicationAttachBaseContext(context)")
        public void applicationAttachBaseContextAdvice(Context context) {
            AH.applicationAttachBaseContext(context);
        }

        // 6、定义一个切入点,用于执行所有 Activity 中以 on 开头的方法,后面的 ”&& baseCondition()“ 是为了排除 ArgusAPM 中的类。
        @Pointcut("execution(* android.app.Activity.on**(..)) && baseCondition()")
        public void activityOnXXX() {
        }

        // 7、定义一个环绕通知,用于在所有 Activity 的 on 开头的方法中的开始和结束处插入相应的代码。(排除了 ArgusAPM 中的类)
        @Around("activityOnXXX()")
        public Object activityOnXXXAdvice(ProceedingJoinPoint proceedingJoinPoint) {
            Object result = null;
            try {
                Activity activity = (Activity) proceedingJoinPoint.getTarget();
                //        Log.d("AJAOP", "Aop Info" + activity.getClass().getCanonicalName() +
                //                "\r\nkind : " + thisJoinPoint.getKind() +
                //                "\r\nargs : " + thisJoinPoint.getArgs() +
                //                "\r\nClass : " + thisJoinPoint.getClass() +
                //                "\r\nsign : " + thisJoinPoint.getSignature() +
                //                "\r\nsource : " + thisJoinPoint.getSourceLocation() +
                //                "\r\nthis : " + thisJoinPoint.getThis()
                //        );
                long startTime = System.currentTimeMillis();
                result = proceedingJoinPoint.proceed();
                String activityName = activity.getClass().getCanonicalName();

                Signature signature = proceedingJoinPoint.getSignature();
                String sign = "";
                String methodName = "";
                if (signature != null) {
                    sign = signature.toString();
                    methodName = signature.getName();
                }

                if (!TextUtils.isEmpty(activityName) && !TextUtils.isEmpty(sign) && sign.contains(activityName)) {
                    invoke(activity, startTime, methodName, sign);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return result;
        }

        public void invoke(Activity activity, long startTime, String methodName, String sign) {
            AH.invoke(activity, startTime, methodName, sign);
        }
    }

我们注意到,在注释4、5这两处代码是用于 在 application 的 onAttachBaseContext 方法之前插入 AH.applicationAttachBaseContext(context) 这行代码。此外,注释2、3两处的代码是用于 在 application 的 onCreate 方法执行完之后插入 AH.applicationOnCreate(context) 这行代码。下面,我们再看看 AH 类中这两个方法的实现,代码如下所示:

代码语言:javascript
复制
    public static void applicationAttachBaseContext(Context context) {
        ActivityCore.appAttachTime = System.currentTimeMillis();
        if (Env.DEBUG) {
            LogX.d(Env.TAG, SUB_TAG, "applicationAttachBaseContext time : " + ActivityCore.appAttachTime);
        }
    }

    public static void applicationOnCreate(Context context) {
        if (Env.DEBUG) {
            LogX.d(Env.TAG, SUB_TAG, "applicationOnCreate");
        }

    }

AH 类的 applicationAttachBaseContext 方法中将启动时间 appAttachTime 记录到了 ActivityCore 实例中。而 applicationOnCreate 基本上什么也没有实现。 然后,我们再回到切面文件 TraceActivity 中,看到注释6、7处的代码,这里用于 在所有 Activity 的 on 开头的方法中的开始和结束处插入相应的代码。这里排除了 ArgusAPM 中的类。 下面,我们来分析下 activityOnXXXAdvice 方法中的操作。首先,在目标方法执行前获取了 startTime。然后,调用了 proceedingJoinPoint.proceed() 用于执行目标方法;最后,调用了 AH 类的 invoke 方法。我们看看 invoke 方法的处理,代码如下所示:

代码语言:javascript
复制
    public static void invoke(Activity activity, long startTime, String lifeCycle, Object... extars) {
        // 1
        boolean isRunning = isActivityTaskRunning();
        if (Env.DEBUG) {
            LogX.d(Env.TAG, SUB_TAG, lifeCycle + " isRunning : " + isRunning);
        }
        if (!isRunning) {
            return;
        }

        // 2
        if (TextUtils.equals(lifeCycle, ActivityInfo.TYPE_STR_ONCREATE)) {
            ActivityCore.onCreateInfo(activity, startTime);
        } else {
            // 3
            int lc = ActivityInfo.ofLifeCycleString(lifeCycle);
            if (lc <= ActivityInfo.TYPE_UNKNOWN || lc > ActivityInfo.TYPE_DESTROY) {
                return;
            }
            ActivityCore.saveActivityInfo(activity, ActivityInfo.HOT_START, System.currentTimeMillis() - startTime, lc);
        }
    }

在注释1处,我们会先去查看当前应用的 Activity 耗时统计任务是否打开了。如果打开了,然后就会走到注释2处,这里 会先判断目标方法名称是否是 “onCreate”,如果是 onCreate 方法,就会执行 ActivityCore 的 onCreateInfo 方法,代码如下所示:

代码语言:javascript
复制
    // 是否是第一次启动
    public static boolean isFirst = true;
    public static long appAttachTime = 0;
    // 启动类型
    public static int startType;
    
    public static void onCreateInfo(Activity activity, long startTime) {
        // 1   
        startType = isFirst ? ActivityInfo.COLD_START : ActivityInfo.HOT_START;
        // 2
        activity.getWindow().getDecorView().post(new FirstFrameRunnable(activity, startType, startTime));
        //onCreate 时间
        long curTime = System.currentTimeMillis();
        // 3
        saveActivityInfo(activity, startType, curTime - startTime, ActivityInfo.TYPE_CREATE);
    }

如何监听OKHttp3 的每一次网络请求

代码语言:javascript
复制
    /**
    * OKHTTP3 切面文件
    *
    * @author ArgusAPM Team
    */
    @Aspect
    public class OkHttp3Aspect {

        // 1、定义一个切入点,用于直接调用 OkHttpClient 的 build 方法。
        @Pointcut("call(public okhttp3.OkHttpClient build())")
        public void build() {

        }

        // 2、使用环绕通知在 build 方法执行前添加一个 NetWokrInterceptor。
        @Around("build()")
        public Object aroundBuild(ProceedingJoinPoint joinPoint) throws Throwable {
            Object target = joinPoint.getTarget();

            if (target instanceof OkHttpClient.Builder && Client.isTaskRunning(ApmTask.TASK_NET)) {
                OkHttpClient.Builder builder = (OkHttpClient.Builder) target;
                builder.addInterceptor(new NetWorkInterceptor());
            }

            return joinPoint.proceed();
        }
    }
代码语言:javascript
复制
    @Override
    public Response intercept(Chain chain) throws IOException {
        // 1、获取每一个 OkHttp 请求的开始时间
        long startNs = System.currentTimeMillis();

        mOkHttpData = new OkHttpData();
        mOkHttpData.startTime = startNs;

        if (Env.DEBUG) {
            Log.d(TAG, "okhttp request 开始时间:" + mOkHttpData.startTime);
        }

        Request request = chain.request();
        
        // 2、记录当前请求的请求 url 和请求数据大小
        recordRequest(request);

        Response response;

        try {
            response = chain.proceed(request);
        } catch (IOException e) {
            if (Env.DEBUG) {
                e.printStackTrace();
                Log.e(TAG, "HTTP FAILED: " + e);
            }
            throw e;
        }
        
        // 3、记录这次请求花费的时间
        mOkHttpData.costTime = System.currentTimeMillis() - startNs;

        if (Env.DEBUG) {
            Log.d(TAG, "okhttp chain.proceed 耗时:" + mOkHttpData.costTime);
        }
        
        // 4、记录当前请求返回的响应码和响应数据大小
        recordResponse(response);

        if (Env.DEBUG) {
            Log.d(TAG, "okhttp chain.proceed end.");
        }

        // 5、记录 OkHttp 的请求数据
        DataRecordUtils.recordUrlRequest(mOkHttpData);
        return response;
    }

1.定义切点为OkHttpClient 的 build 方法 2.使用环绕通知在 build 方法执行前添加一个 NetWokrInterceptor。 3.在NetWokrInterceptor中执行chain.proceed(request);前后记录时间差即可,因为责任链模式关系,最后所有Interceptor执行完才回到NetWokrInterceptor

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-02-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AspectJ 的优势
  • AspectJ 的缺陷
  • AspectJX 实战
  • 简单的 AspectJ 示例
  • 统计 Application 中所有方法的耗时
  • 对 App 中所有的方法进行 Systrace 函数插桩
  • AspectJ 在性能监控框架中应用
  • 如何监听OKHttp3 的每一次网络请求
相关产品与服务
应用性能监控
应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档