博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AspectJ 学习笔记
阅读量:6269 次
发布时间:2019-06-22

本文共 11145 字,大约阅读时间需要 37 分钟。

AOP中用到的术语

面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

Aspect :切面,一个关注点的模块化,这个关注点可能会横切多个对象。

Join Point :连接点,程序中可切入的点,例如方法调用时、读取某个变量时。

Pointcut :切入点,代码注入的位置。

Advice :在切入点注入的代码,一般有 before、after、around 三种类型。

Target Object :被一个或多个 aspect 横切拦截操作的目标对象。

Weaving : 把 Advice 代码织入到目标对象的过程。

Inter-type declarations : 用来给一个类型声明额外的方法或属性。

AspectJ 是在静态织入代码,即在编译期注入代码的。

一 AspectJ之热补丁技术

利用java的 AOP技术,对字节码进行处理。

1.构建时利用aop技术对每个方法进行插桩的操作

gradle进行构建的时候,在Java源码编译完成之后,生成dex文件之前,调用AspectJ的编译器进行插桩。插桩的目的是给每一个方法注入一段寻找patch方法的逻辑。 简单来说就是每个方法执行时先去根据自己方法的签名寻找是否有自己对应的patch方法,如果有执行patch方法,没有执行自己原有的逻辑。

怎么进行插桩

相关的AspectJ需要去了解。

@Aspectpublic class PatchAspect {    private static final String TAG = "PatchAspect";    @Around("execution(* *..*()) && !withincode(* com.zpw.patch..*(..))")    public void weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {        try {            String e = joinPoint.toLongString(); //获取当前方法的签名            System.out.println("AspectJ拦截到的方法->" + e);            if(PatchUtils.hasMethodPatch(e)) {  //查询是否有自己方法的patch方法                Object target = joinPoint.getTarget(); //获取当前的运行对象,方便设置给patch方法                Object[] params = joinPoint.getArgs(); //获取运行参数                //在Application中获取patch包中需要打补丁的类                List
classNameList = ZApplication.classNameList; if (classNameList != null && classNameList.size() > 0) { for (String className : classNameList) { Class patch = (Class) ZApplication.cl.loadClass(className); Object patchObj = patch.newInstance(); Method[] methods = patchObj.getClass().getMethods(); for (Method method : methods) { PatchAnnotation annotation = method.getAnnotation(PatchAnnotation.class); if (annotation != null) { System.out.println(); if (e.contains(annotation.value())) { method.setAccessible(true); method.invoke(patchObj); return; } } } } } joinPoint.proceed(); } } catch (Exception var8) { var8.printStackTrace(); } joinPoint.proceed(); }}复制代码
public class PatchUtils {    private static final String TAG = "PatchUtils";    public static boolean hasMethodPatch(String methodSignature)            throws ClassNotFoundException, IllegalAccessException, InstantiationException {        List
classNameList = ZApplication.classNameList; if (classNameList != null && classNameList.size() > 0) { for (String className : classNameList) { Class patch = null; try { patch = ZApplication.cl.loadClass(className); System.out.println("补丁中的类->" + patch.getName()); Object patchObj = patch.newInstance(); Method[] methods = patchObj.getClass().getMethods(); for (Method declaredMethod : methods) { System.out.println("补丁中的方法->" + declaredMethod.getName()); PatchAnnotation annotation = declaredMethod.getAnnotation(PatchAnnotation.class); if (annotation != null) { System.out.println("补丁中的方法的注解->" + annotation.value()); return true; } } } catch (Exception e) { e.printStackTrace(); } } } return false; }}复制代码
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface PatchAnnotation {    String value();    String intercept();}复制代码
public class ZApplication extends Application {    private static final String TAG = "ZApplication";    public static List
classNameList = new ArrayList<>(); public static DexClassLoader cl; @Override public void onCreate() { super.onCreate(); StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); //拷贝补丁 copyData(); // 获取补丁,如果存在就执行注入操作 String dexInternalStoragePath = getDir("dex", Context.MODE_PRIVATE).getAbsolutePath().concat("/patch_dex.jar"); File file = new File(dexInternalStoragePath); String optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(); if (file.exists()) { try { inject(dexInternalStoragePath, optimizedDexOutputPath); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } else { Log.e("ZApplication", dexInternalStoragePath + "不存在"); } cl = new DexClassLoader(dexInternalStoragePath, optimizedDexOutputPath, null, getClassLoader()); } /** * 要注入的dex的路径 * * @param dexInternalStoragePath * @param optimizedDexOutputPath */ private void inject(String dexInternalStoragePath, String optimizedDexOutputPath) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { DexFile dexFile = DexFile.loadDex(dexInternalStoragePath, optimizedDexOutputPath, 0); if (dexFile != null) { Enumeration
entries = dexFile.entries(); if (entries != null) { while (entries.hasMoreElements()) { String className = entries.nextElement(); System.out.println("补丁中的类->" + className); classNameList.add(className); } } else { System.out.println("entries is null!"); } } else { System.out.println("dexFile is null!"); } } void copyData() { InputStream in = null; FileOutputStream out = null; String path = getDir("dex", Context.MODE_PRIVATE).getAbsolutePath().concat("/patch_dex.jar"); // data/data目录 File file = new File(path); if (!file.exists()) { try { in = this.getAssets().open("patch_dex.jar"); // 从assets目录下复制 out = new FileOutputStream(file); int length = -1; byte[] buf = new byte[1024]; while ((length = in.read(buf)) != -1) { out.write(buf, 0, length); } out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (out != null) { try { out.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } }}复制代码

何时插桩

java源码编译完成之后,Dex文件生成之前。

如何基于构建插桩

1.hook java Compiler的Task,在java源码编译完成之后执行AspectJ的编译器,进行字节码插桩操作。

import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainfinal def log = project.loggerfinal def variants = project.android.applicationVariantsvariants.all { variant ->    if (!variant.buildType.isDebuggable()) {        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")        return;    }    JavaCompile javaCompile = variant.javaCompile    javaCompile.doLast {        String[] args = ["-showWeaveInfo",                         "-1.5",                         "-inpath", javaCompile.destinationDir.toString(),                         "-aspectpath", javaCompile.classpath.asPath,                         "-d", javaCompile.destinationDir.toString(),                         "-classpath", javaCompile.classpath.asPath,                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]        log.debug "ajc args: " + Arrays.toString(args)        MessageHandler handler = new MessageHandler(true);        new Main().run(args, handler);        for (IMessage message : handler.getMessages(null, true)) {            switch (message.getKind()) {                case IMessage.ABORT:                case IMessage.ERROR:                case IMessage.FAIL:                    log.error message.message, message.thrown                    break;                case IMessage.WARNING:                    log.warn message.message, message.thrown                    break;                case IMessage.INFO:                    log.info message.message, message.thrown                    break;                case IMessage.DEBUG:                    log.debug message.message, message.thrown                    break;            }        }    }}复制代码
dependencies {    compile 'org.aspectj:aspectjrt:1.8.1'}复制代码

2.gradle Transform API方案

对于上述方法存在一些问题,对于一些jar包,其实已经是字节码了不会走这个过程,因此对一些jar的插桩操作不是很好处理。因此我们采用了Transform API的方案。 transform api是Android gradle plugin 1.5之后新api, 作用就是在生成dex之前,给开发者一个机会能够统一进行修改字节码。 用这个方案写了一个实现了Transform API的gradle 插件,具体对哪些jar包进行插桩我们通过gradle配置的方式实现。这就大大的方便了我们对自己的代码和第三方的代码进行字节码的处理。

2.创建patch补丁包

假设错误类为

public class Animal {    public void hurtThrows(){        int i = 4 / 0;    }}复制代码

出现bug时将需要进行替换的方法放到指定类,然后生成一个只含有此java类字节码的apk包,进行下发

public class AnimalPatch{  private String TAG = "AnimalPatch";    @PatchAnnotation(intercept="after", value="public void com.zpw.hotfix.Animal.hurtThrows()")  public void hurtThrowsFix()  {    System.out.println("hurtThrowsFix");  }}复制代码

3.补丁包的传输和加载

在补丁包加载之前一定要注意安全性校验、安全行校验、安全性校验。加载patch包的方式: 由于最终的patch包的形式是一个apk,因此加载也很简单直接使用android的DexFile.loadDex()将apk加载。加载完成之后遍历每一个Enumeration, 并反射获取所有class的Method,进行缓存起来,方便每个方法在缓存中查找。

二 检测方法耗时

1.注入逻辑

@Aspectpublic class AspectJSpectControler {    @Around(value = "execution(* com.zpw.test..*.*(..)")    public Object weavePatchLogic(ProceedingJoinPoint joinPoint) throws Throwable {        if (BuildConfig.DEBUG) { //debug    状态下计算方法耗时            long startT = System.currentTimeMillis();            Object proceed = joinPoint.proceed();            long consume = System.currentTimeMillis() - startT;            if (consume > 40 && Thread.currentThread().getId() == BaseApplication.getInstance().getMainThreadId()) { // 方法耗时大于40毫秒,并且当前线程是主线程,则直接打印当前方法的签名                NeteaseLog.d(METHOD_TIME_TAG, consume + " ms " + joinPoint.getSignature() + " main thread method");            }            return proceed;        }        return joinPoint.proceed();    }    }复制代码

2.运行app,过滤log查看方法耗时

adb logcat | grep method

三 对第三方sdk的某些方法进行hook修改

同理。

四 处理权限问题

同理。在需要判断权限的地方使用AspectJ进行代码注入。

转载地址:http://igppa.baihongyu.com/

你可能感兴趣的文章
Android程序开发初级教程(一) 开始 Hello Android
查看>>
使用Gradle打RPM包
查看>>
“我意识到”的意义
查看>>
淘宝天猫上新辅助工具-新品填表
查看>>
再学 GDI+[43]: 文本输出 - 获取已安装的字体列表
查看>>
nginx反向代理
查看>>
操作系统真实的虚拟内存是什么样的(一)
查看>>
hadoop、hbase、zookeeper集群搭建
查看>>
python中一切皆对象------类的基础(五)
查看>>
modprobe
查看>>
android中用ExpandableListView实现三级扩展列表
查看>>
%Error opening tftp://255.255.255.255/cisconet.cfg
查看>>
java读取excel、txt 文件内容,传到、显示到另一个页面的文本框里面。
查看>>
《从零开始学Swift》学习笔记(Day 51)——扩展构造函数
查看>>
python多线程队列安全
查看>>
[汇编语言学习笔记][第四章第一个程序的编写]
查看>>
android 打开各种文件(setDataAndType)转:
查看>>
补交:最最原始的第一次作业(当时没有选上课,所以不知道)
查看>>
Vue实例初始化的选项配置对象详解
查看>>
PLM产品技术的发展趋势 来源:e-works 作者:清软英泰 党伟升 罗先海 耿坤瑛
查看>>