android技术专栏Android技术知识

AspectJ In Android Studio

2017-07-20  本文已影响0人  Arnold_J

开发工具:Android Studio
参考链接:
1.一个流传广泛到不知道哪个是原版的博客
2.基于上面内容中第二种方式配置的具体说明
3.基于上述方法的测试代码库
4.一个插件 gradle-android-aspectj-plugin
5.另一个插件 gradle_plugin_android_aspectjx
6.Aspect Oriented Programming in Android


做安卓都知道 OOP 面向对象编程。它将一系列事物抽象化,把他们有公共的属性集合到一起,成为能高度概括某一类具体事物的概念。比如交通工具是一个抽象概念,而具体实现则有汽车、自行车等等,相信走在安卓开发路上的我们都很熟悉,但是在面向对象编程的过程中,我们遇到这样的问题:需要对某些事件进行统一的处理,比如统计埋点、权限控制、日志打印。显然,我们可以自定义一个类,然后在不同事件中分别调用指定方法,这样的思想在我们日常的编程中已经有了相当成熟的应用,但是终究还是不够智能和便捷。
于是乎,伟大的 AOP 出现了,解决了许许多多的问题,而今天,我们就来简单了解一下 AOP。

一、什么是AOP ?

AOP是Aspect Oriented Programming的缩写,也就是题目里说的面相切面编程。它通过预编译或者运行期动态代理实现程序功能的统一维护。AOP 是 OOP的延续,也是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑之间的耦合度降低,提高程序的可重用性。
还不是很了解的可以去稍微看一下,有点多,简单看看就好。百度链接

二、运行时AOP框架

Dexposed,这是是阿里巴巴无线事业部第一个重量级 Andorid 开源软件,基于 ROOT 社区著名开源项目Xposed 改造剥离了 ROOT 部分,演化为服务于所在应用自身的 AOP 框架。它支撑了阿里大部分 App 的在线分钟级客户端 bugfix 和线上调试能力。
但是,它在2015年貌似就已经停止更新了
但是,它在2015年貌似就已经停止更新了
但是,它在2015年貌似就已经停止更新了
看看github上的更新数据:

[alibaba](https://github.com/alibaba)/[dexposed](https://github.com/alibaba/dexposed)截图
安卓5.0使用ART虚拟机后,这个库的支持就一直停留在 testing 再也没变过。前段时间,埋点数据和ios严重不符合,才发现了这个问题,为此重新整理一下。

三、预编译AOP框架 -- AspectJ

由于找不到合适的运行时框架,我们决定使用预编译的AOP框架 -- AspectJ。根据网上目前的资料来看,可以使用的主要有两种方法:

这种情况下,为了快速开发,一般当然会选择先考察插件的可使用性。
(如果想看自己配置 Gradle 的方法,可以直接跳到后面看 3.4 ~):-)
根据资料,gradle-android-aspectj-plugin 这个插件是不能支持 data-binding 的,虽然我公司也用不到,但是总希望能找到一个更优的解决方案。于是乎,我在公众号中找到了需要的东西——根据上面的插件改良的插件。并且在原作者的Github上,这个插件也得到了推荐。

gradle-android-aspectj-plugin 首页

那么下面,就了解一下这个插件的使用:

3.1 使用插件的走一波效果

工程- gradle

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
    }
}

app - gradle

apply plugin: 'android-aspectjx'
//更详细的使用作者在readme中已经举例写得很明白了,可以自行参考上面的链接
aspectjx {
    //加入需要织入代码的第三方库
    includeJarFilter '...xxx', '...xxx'
    
    //排除不需要织入代码的第三方库
    excludeJarFilter '...xxx'
}
dependencies{
        ...
        compile 'org.aspectj:aspectjrt:1.8.9'
}

AspectTest

@Aspect
public class AspectTest {

    private static final String TAG = "AspectTest";
    // 复制代码需要注意修改下面的 com.arno.testaspectj 为你的包名
    private static final String ACTI_ONCREATE = "execution(com.arno.testaspectj.MainActivity.onCreate(..))";
    
    @Before(ACTI_ONCREATE )
    public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
        Log.d(TAG, "onActivityCreate: " +  joinPoint.getSignature().toString());
    }
   
}

主界面什么也没动,这样工程就应该可以跑起来了。
If you meet any problem,I recommend strongly to update your gradle first,clean your project and run it.
在进入 MainActivity 后,打开控制台,你就会看到我们对应的日志打印。这是最简单的实现,我们可以看到,在没有更改 MainActivity 中代码的情况下,我们成功截取了 onCreate 方法的调用,并在它调用之前打印了日志。
那么下面让我们看看使用 AspectJ 需要了解哪些元素。

3.2 AspectJ 相关词汇了解
3.3 Advice - Before && After && Around

以上三种切入方式是最常用的,切入方式如英文单词的意思,非常好理解。我们以下面的代码为例,说明调用过程。

@xxx注解("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
    Log.d(TAG, msg);
}
@Before ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
    Log.d(TAG, msg);
}
// -- 执行顺序
Log.d(TAG,msg)
MainActivity.onCreate(.)
@After ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
    Log.d(TAG, msg);
}
// -- 执行顺序
MainActivity.onCreate(.)
Log.d(TAG,msg)
@After ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable {
    Log.d(TAG, msg);
    proceedingJoinPoint.proceed();
    Log.d(TAG,msg2);
}
// -- 执行顺序
Log.d(TAG,msg)
MainActivity.onCreate(.)
Log.d(TAG,msg2)
3.4 自己配置 Gradle 插件,添加脚本

根据博客的说法,我照搬了一套,放到我的 Gradle 文件中去,然后报红,以下文件无法导入。

import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

之后我升级了一下 Gradle 版本,编译成功。但是配置了一些切入点,却并不执行相关内容。于是上网找了些资料,发现不少人和我一样对着这个英文资料在配置 aspectj 。有人说,配置中的 java 版本改一下就可以了。

String[] args = ["-showWeaveInfo",
                         "-1.7", // <-- java 版本
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", plugin.project.android.bootClasspath.join(
                File.pathSeparator)
        ]

然而没有用,这期间,我试了各种方法,包括但不仅限于:clean project,rebuild project,new project... 后来我在 stackoverflow 上找到了一个答案:不靠谱的链接
按照它做了,然而。呵呵 :-(
后面在 stackoverflow 上又有新发现,但是链接已经找不到了,总之就是不能用 = = ,白叨叨了我好久,有点费时间。
所以,乖乖用插件吧。

-------------------------------------- 分割线 ------------------------------------------------

07/26
使用过程中发现如下问题:

  1. 在对 android.app.Activity.onResume() 方法织入代码时,如果其子类中没有重写这个方法,那么是无法织入代码的。(也就是说,即使在onResume中不做任何操作,同样要override)。前文中,我一直默认使用 execution,但是如果使用 call,那么即使你重写了 onResume 也无法织入代码,由于织入代码的位置问题,必须要在代码中调用了这个方法,才能成功织入。
  2. 在对 com.xxx.xx.BaseActivity.onResume() 方法织入代码时,如果 BaseActivity 和其子类都重写了 onResume() 方法,那么织入的代码会被调用两次。
  3. 对组合的自定义 pointcut 进行代码织入的时候,joinPoint.getArgs() 获取的参数是范围较小的方法的参数。
  4. 组合自定义 pointcut 的时候,withincode + execution 无法成功织入代码。
    5.无法切割对象,所有的代码织入都必须在界面的方法中。

如果有新发现,欢迎戳我戳我戳我! 谢谢

上一篇 下一篇

猜你喜欢

热点阅读