Android AOP 总结

一、AOP简介

1.1 什么是AOP

AOP,AspectOriented Programming 面向切面编程

OOP,Object-orientedprogramming面向对象编程

AOP和OOP是不同的编程思想。OOP强调的是高内聚,低耦合,封装。

提倡的是将功能模块化,对象化。

可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。

     

1.2      AOP用途

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

 

1.3      AOP方式对比选择

 

Hook时机

Android中应用场景

优点

缺点

Dexposed

运行时动态hook

滑动流畅度监控

事件执行监控

热修复

可以动态监控和系统通信的各种方法。

不支持5.0以上手机

Xposed

运行时动态hook

同Dexposed

可以动态监控和系统通信的各种方法。

不支持5.0以上手机

必现root

Java Proxy

运行时动态hook

hook和系统通信接口

例如:插件sdk

Java 原生API,没有兼容性问题

只能hook 有Interface的类

AspactJ

编译时修改代码

统计方法执行时长

方法前后注入逻辑

Sprint开源的AOP框架,功能强大。注解很多。基本包括所有的编译时注入方式

需要引入118K的jar

ASM

编译时修改代码

同AspactJ

字节码操作库,

需要自己写注解和编译脚本。

字节码插入编写比较费劲

Javassit

编译时修改代码

同AspactJ

基于java反射的字节码操作类库。

对比ASM,编写简单

对比ASM,修改类时,执行时间长

 

DexposedXposed原理,景,demo

 

Dexposed是基于Xposed开发的hook自己app的库。淘宝开源的。

原理:http://www.zhaoxiaodan.com/android/Android-Hook(1)-dexposed%E5%8E%9F%E7%90%86.html

http://blog.csdn.net/yueqian_scut/article/details/50939034

通过把原java方法的类型改为native来把对java函数的调用转到native层,在native层用dvm的各种函数来操作Method的指针和对象来控制函数流程。

Git地址:https://github.com/alibaba/dexposed                  

Runtime

Android Version

Support

Dalvik

2.2

Not Test

Dalvik

2.3

Yes

Dalvik

3.0

No

Dalvik

4.0-4.4

Yes

ART

5.0

Testing

ART

5.1

No

ART

M

No

 

 

最近貌似没有维护了。Github上代码没有更新过

使用方法:

compile'com.taobao.android:dexposed:0.1.7@aar'
 

代码示例:

/**
 * doFrame 方法的hook
 */
  public static class FrameMethodHook extends XC_MethodHook {
  
    private long startTime = 0;
    private int frameNo = -1;
  
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        startTime = SystemClock.elapsedRealtime();
        frameNo = (Integer) param.args[1];
    }
  
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        long coastTime = SystemClock.elapsedRealtime() - startTime;
        Log.i(TAG, "frameNo:" + frameNo + ", coastTime:" + coastTime);
    }
}
  
  
  private XC_MethodHook.Unhook frameMethodunHook = null;
  
public void hookDoFrame() {
// 找到doFrame方法,插入MethodHook
    FrameMethodHook frameMethodHook = new FrameMethodHook();
    frameMethodunHook = DexposedBridge.findAndHookMethod(Choreographer.class, "doFrame", long.class, int.class, frameMethodHook);
  }
  
  public void unHookDoFrame() {
    if (frameMethodunHook != null) {
        frameMethodunHook.unhook();
    }
}

运行后log:

03-10 16:43:59.440 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13831, coastTime:153
03-10 16:43:59.520 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13832, coastTime:79
03-10 16:43:59.595 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13846, coastTime:60
03-10 16:44:00.540 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13870, coastTime:38
03-10 16:44:00.580 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13872, coastTime:38
03-10 16:44:00.585 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13875, coastTime:3
03-10 16:44:00.605 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13876, coastTime:4
03-10 16:44:00.620 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13877, coastTime:3
03-10 16:44:00.635 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13878, coastTime:2
03-10 16:44:00.655 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13879, coastTime:2
03-10 16:44:00.670 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13880, coastTime:3
03-10 16:44:00.685 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13881, coastTime:2
03-10 16:44:00.705 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13882, coastTime:3
03-10 16:44:00.720 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13883, coastTime:2

用途:性能监控,监控滑动流畅,view绘制时间等。

aspactJ原理,景,demo

sprint 提供的AOP框架,功能强大。缺点,需要集成118KBjarAPK中。

也是编译时拦截。在编译的时候,修改class字节码。重新写class文件。

用法:

引入classPath

classpath'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
增加jar

compile'org.aspectj:aspectjrt:1.8.9'

增加gradle插件

applyplugin: 'android-aspectjx'

增加完这3句话就可以使用aspectj

新建一个AspectTest文件。在类最开始增加@Aspect注解。编译器在编译的时候,就会自动去解析,并不需要主动去调用AspectJ类里面的代码。

@Aspect
public class AspectTest {
    private static final StringTAG ="AspectTest";

    @Before("execution(*android.app.Activity.on**(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint)throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.d(TAG,"onActivityMethodBefore: " + key);
    }
}

这段测试代码的意思是,在所有继承Activity的类中,执行到onXXX方法前,增加拦截。插入这段代码。

例如Activity是这样的。

publicclass AnimationScaleActivityextendsFragmentActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.an_scale);
      ImageView imgv=(ImageView)findViewById(R.id.img);
      AnimationalphaAnimation=AnimationUtils.loadAnimation(this,R.anim.scale);
      imgv.startAnimation(alphaAnimation);
       Intent intent = newIntent();
        intent.putExtra("rs","success");
        setResult(2,intent);
   }
}

编译完后,再反编译,是这样的。

高亮部分是自动生成的代码。

用途:编译是切入。日志记录,打点统计等工作。

java动态代理机制 demo

纯java API。主要API类是:

Proxy.newProxyInstance

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:
     Proxy.getProxyClass(loader, interfaces).
         getConstructor(new Class[] { InvocationHandler.class }).
         newInstance(new Object[] { handler });


Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。

参数:

loader - 定义代理类的类加载器

interfaces - 代理类要实现的接口列表

h - 指派方法调用的调用处理程序

返回:

一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

抛出:

IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制

NullPointerException - 如果 interfaces 数组参数或其任何元素为 null,或如果调用处理程序 h 为 null

详解见:另写一遍博文。JDK中的proxy动态代理原理剖析

运行时hook。但是只能hook接口。在Android中的用途,可以hook Android Framework层的接口。

javassit原理,景,demo

Javassist是一个开源的分析、编辑和创建Java字节码的类库。

使用方法如下:

引入依赖库

compile 'javassist:javassist:3.12.0.GA'

 

示例:记录方法的执行时长。并打印出来。

声明要注入的代码:

private static String injectStr = "System.out.println(\"Test Javassist Inject \" ); ";

private static String fieldT1Str = "private int t1;";
private static String fieldT2Str = "private int t2;";
private static String injectTimeBefore = "t1 = System.currentTimeMillis();";
private static String injectTimeAfter = "t2 = System.currentTimeMillis();\n" +
        "        long t = t2-t1;\n" +
        "        System.out.println(\"ActivityTime\"+ this.toString()+\", oncreate \" + t);";

获取默认的ClassPool

ClassPool pool = ClassPool.getDefault()
pool.appendClassPath(path); // path是class文件地址
public static void injectClass(String topPath, File file, String packageName, String methodName) {
        try {
            String filePath = file.getAbsolutePath();
            System.out.println("filePath:" + filePath);
            //确保当前文件是class文件,并且不是系统自动生成的class文件,且不是内部类
            if (filePath.endsWith(".class")
                    && !filePath.contains("R.class")
                    && !filePath.contains("BuildConfig.class")
                    && !filePath.contains("$")) {
                int index = filePath.indexOf(packageName);
                boolean isPackageClass = index != -1; // 是这个包里面的class
                if (isPackageClass) {
                    int end = filePath.length() - 6;// .class = 6
                    String className = filePath.substring(index, end)
                            .replace('\\', '.').replace('/', '.');
                    System.out.println("className:" + className);

                    // 开始修改class文件
                    CtClass c = pool.getCtClass(className);
                    if (c.isFrozen()) {
                        c.defrost();
                    }
                    CtMethod ctMethod = c.getDeclaredMethod(methodName);
                    if (ctMethod != null) {
                        c.addField(CtField.make(fieldT1Str, c));
                        c.addField(CtField.make(fieldT2Str, c));
                        System.out.println("injectActivity");
                        ctMethod.insertBefore(injectTimeBefore);
                        ctMethod.insertAfter(injectTimeAfter);
                    }

                    c.writeFile(topPath);
                    c.detach();

                }
            }
        } catch (Exception e) {
            e.printStackTrace();

        }
    }

写法很简单。API也比较易懂。和java 反射的API很像。

执行时间:aop time: 712

原方法:

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.an_scale);
  }

反编译后的方法:

protected void onCreate(BundlesavedInstanceState) {
       this.t1 = (int) System.currentTimeMillis();
       super.onCreate(savedInstanceState);
       this.t2 = (int) System.currentTimeMillis();
       System.out.println(newStringBuffer().append("ActivityTime").append(toString()).append(",oncreate ").append((long) (this.t2 - this.t1)).toString());
}

优点:使用简单。APK中不需要引入第三方jar。引入的jar是在编译时,gradle插件使用的。

ASM原理,景,demo

ASM是一款基于java字节码层面的代码分析和修改工具。无需提供源代码即可对应用嵌入所需debug代码,用于应用API性能分析。ASM可以直接产生二进制class文件,也可以在类被加入JVM之前动态修改类行为。

详解见。Android 中使用ASM,对Activity生命周期打点统计。

阅读更多

没有更多推荐了,返回首页