android字节码框架——AspectJ

最常用的字节码处理框架有 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 实战

    classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'

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

    apply plugin: 'android-aspectjx'
    implement 'org.aspectj:aspectjrt:1.8.+'

简单的 AspectJ 示例

    @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:代表方法执行的位置,插入在函数体内部。

扫描二维码关注公众号,回复: 12569662 查看本文章

统计 Application 中所有方法的耗时

    @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 函数插桩

    @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 的每一次网络请求。

    @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 类中这两个方法的实现,代码如下所示:

    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 方法的处理,代码如下所示:

    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 方法,代码如下所示:

    // 是否是第一次启动
    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 的每一次网络请求

    /**
    * 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();
        }
    }

    @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

猜你喜欢

转载自blog.csdn.net/u012124438/article/details/113817273