php插桩 xdebug,Android编译期插桩,让程序自己写代码(二)

在上篇文章 Android编译期插桩,让程序自己写代码(一) 的前言部分我放了一张图,用来说明编译期插桩的位置和相应的技术。这里,我还打算这张图来开篇。

5ab7cf19acbb43de63ff14587c7f1c35.png

AspectJ

在上图中,我们可以清楚的看到AspectJ的插桩位置是.java与.class之间。这很容易使人联想到编译器。事实上,AspectJ就是一种编译器,它在 Java 编译器的基础上增加了关键字识别和编译方法。因此,AspectJ可以编译Java代码。它还提供了Aspect程序。在编译期间,将开发者编写的Aspect程序织入到目标程序中,扩展目标程序的功能。

AspectJ可以应用于 Android 和后端开发中。在后端,AspectJ 应用更为广泛一些,著名的Spring框架就对AspectJ提供了支持。不过,近些年,AspectJ技术在Android领域也开始崭露头角,比较知名的有JakeWharton的 hugo 。另外,一些企业也开始探索AspectJ在埋点、权限管理等方面的应用。

关于AspectJ更为详细的介绍,请大家移步邓平凡大神的博客深入理解Android之AOP。这篇文章对于初次接触AspectJ的人来说十分友好,笔者最初就是通过它进入AspectJ殿堂的。珠玉在前,本文就不再介绍AspectJ的基础知识了。那本文要说些什么呢?

一个简单的Hugo框架。

从字节码分析AspectJ。

Hugo

Hugo是JakeWharton基于AspectJ开源的一个调试框架,其功能是通过注解的方式可以打印出方法的运行时间,方便开发者性能调优。今天,我们就来看一下它的庐山真面目。

配置AspectJ

Hugo是基于AspectJ的,那首先我们就要支持AspectJ。这里向大家推荐沪江的 AspectJX ,它不仅使用简单,而且还支持过滤一些aar或jar包。

首先我们需要在根build.gradle中依赖 AspectJX

dependencies {

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

}

复制代码

在app项目的build.gradle里应用插件,并添加aspectj的依赖库

apply plugin: 'android-aspectjx'

api 'org.aspectj:aspectjrt:1.8.9'

复制代码

这样就配置完成了,是不是很简单啊。

注意:笔者这里采用的gradle版本是3.0.1,如果没有编译通过,检查一下gradle版本。

定义DebugLog注解

十分简单,直接上代码

@Target({METHOD, CONSTRUCTOR})

@Retention(CLASS)

public @interface DebugLog {

}

复制代码

编写Aspect

@Aspect

public class Hugo {

@Pointcut("execution(@com.hugo.example.lib.DebugLog * *(..))")

public void method() {}

@Pointcut("execution(@com.hugo.example.lib.DebugLog *.new(..))")

public void constructor() {}

@Around("method() || constructor()")

public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {

CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

Class> cls = codeSignature.getDeclaringType();

String methodName = codeSignature.getName();

long startNanos = System.nanoTime();

Object result = joinPoint.proceed();

long stopNanos = System.nanoTime();

long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);

StringBuilder builder = new StringBuilder();

builder.append("methodName:")

.append(methodName)

.append(" ---- executeTime:")

.append(lengthMillis);

Log.e(asTag(cls), builder.toString());

return result;

}

private static String asTag(Class> cls) {

if (cls.isAnonymousClass()) {

return asTag(cls.getEnclosingClass());

}

return cls.getSimpleName();

}

}

复制代码

如果你学习了深入理解Android之AOP,那么这段代码应该很容易能看懂。这里我简单解释一下。

选取注解了DebugLog的method和constructor作为pointcut。

在原方法执行前织入开始时间,在原方法执行后织入结束时间,并计算出运行时间。通过Log.v打印出来。

到此,这个简化版的Hugo基本就介绍完了。你可以做个Demo试一下了。

笔者DebugLog的包名是com.hugo.example.lib。因此在method()和constractor()中的注解内容是com.hugo.example.lib.DebugLog。

其实,真正的Hugo框架核心也只有一个类。它只是在日志打印时,输出了更多的方法信息。本文为了方便读者理解作了简化。

测试

我们定义一个Test类,然后在Activity启动的时候调用myThread()方法。Test类如下:

public class Test {

@DebugLog

public void myMethod1() throws Exception{

Thread.sleep(1000);

}

}

复制代码

我们看一下日志,方法的运行时间被完美的打印出来了。

6fee5c61a9f706491ecade392aab6e3c.png

从字节码分析AspectJ

我们仍然以Test为例,看一下Test反编之后的字节码。

c51d3b64a8d6b128152a186bdfd14828.png

反编译的内容看起来不太方便,我在这里把它转换成了如下代码:

c4fb4df89fc0612b05a338690c8e2e5a.png

为了观看方便,上图将代码分为4部分。

我们先看第一部分,这是一个静态代码块,也就是说在类加载的时候,程序会AspectJ提供的Factory类,创建一个类型为JoinPoint.StaticPart静态实例STATIC_PART。深入理解Android之AOP中对JoinPoint.StaticPart介绍如下:

thisJoinPointStaticPart对象:在advice代码中可直接使用,代表JPoint中那些不变的东西。比如这个JPoint的类型,JPoint所处的代码位置等。这里thisJoinPointStaticPart就是代码中的JoinPoint.StaticPart。

第二部分是我们之前定义的myThread方法,它在编译期间被替换了。在运行时,它首先通过Factory的静态方法makeJP创建一个JoinPoint对象。makeJp是一个重载方法,我们看一下。

public static JoinPoint makeJP(JoinPoint.StaticPart staticPart, Object _this, Object target) {

return new JoinPointImpl(staticPart, _this, target, NO_ARGS);

}

public static JoinPoint makeJP(JoinPoint.StaticPart staticPart, Object _this, Object target, Object[] args) {

return new JoinPointImpl(staticPart, _this, target, args);

}

复制代码

通过我列出来了两个,可以看到JoinPoint除了包含了我们第一步中提到了STATIC_PART对象,还包括了this,target对象,以及方法参数。这和深入理解Android之AOP中对thisJoinpoint描述也是一致的:

thisJoinpoint对象:在advice代码中可直接使用。代表JPoint每次被触发时的一些动态信息,比如参数啊之类的。

创建完JoinPoint对象后,随后调用了第三部分中的advice()方法。advice()方法大部分都是我们在Hugo中编写的织入代码,这里只有一个不同,那就是joinPoint.proceed()不见了,替换成了源代码中具体的处理逻辑。

总结

通过上述分析,我们可以清楚的感知到AspectJ提供了非常强大的功能。但同时,由于其为每个切入点生成一个JoinPoint.StaticPar静态实例和在运行过程中生成的JoinPoint以及一些其它的封装,这必然会导致程序在内存和处理速度等方面受影响。因此,在小范围内使用AspectJ是可以的,但是如果涉及范围较大就要慎重考虑了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值