美文网首页
Android AOP 总结

Android AOP 总结

作者: 编程小猪 | 来源:发表于2017-03-23 20:34 被阅读0次

    AOP简介

    1.1 什么是AOP

    AOP,Aspect Oriented Programming 面向切面编程
    OOP,Object-oriented programming面向对象编程
    AOP和OOP是不同的编程思想。OOP强调的是高内聚,低耦合,封装。
    提倡的是将功能模块化,对象化。
    可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。

    Paste_Image.png

    1.2 AOP用途

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

    1.3 AOP方式对比选择

    First Header Hook时机 Android中应用场景 优点 缺点
    Dexposed 运行时动态hook 滑动流畅度监控,事件执行监控,热修复 可以动态监控和系统通信的各种方法。 不支持5.0以上手机
    Xposed 运行时动态hook 同Dexposed 可以动态监控和系统通信的各种方法。 不支持5.0以上手机,必须root
    Java Proxy 运行时动态hook hook和系统通信接口例如:插件sdk Java 原生API,没有兼容性问题 只能hook 有Interface的类
    AspactJ 编译时修改代码 统计方法执行时长,方法前后注入逻辑 Spring开源的AOP框架,功能强大。注解很多。基本包括所有的编译时注入方式 需要引入118K的jar
    ASM 编译时修改代码 同AspactJ 字节码操作库 需要自己写注解和编译脚本。字节码插入编写比较费劲
    Javassit 编译时修改代码 同AspactJ 基于java反射的字节码操作类库。对比ASM,编写简单 对比ASM,修改类时,执行时间长

    Dexposed,Xposed: 原理,应用场景,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

    Paste_Image.png
    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
    03-10 16:44:00.735 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13884, coastTime:2
    

    用途:性能监控,监控滑动流畅,view绘制时间等。
    aspactJ,原理,应用场景,demo
    spring 提供的AOP框架,功能强大。缺点,需要集成118KB的jar到APK中。
    也是编译时拦截。在编译的时候,修改class字节码。重新写class文件。
    用法:
    引入classPath

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

    增加jar
    compile 'org.aspectj:aspectjrt:1.8.9'
    增加gradle 插件
    apply plugin: 'android-aspectjx'

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

    @Aspect
    public class AspectTest {
        private static final String TAG = "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是这样的。

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

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

    Paste_Image.png

    高亮部分是自动生成的代码。
    用途:编译是切入。日志记录,打点统计等工作。

    java的动态代理机制

    纯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
    

    运行时hook。但是只能hook接口。在Android中的用途,可以hook Android Framework层的接口。
    详解见:另写一遍博文。JDK中的proxy动态代理原理剖析

    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很像。
    原方法:

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

    反编译后的方法:

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

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

    ASM原理,应用场景

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

    26个class文件,通过Javassist, ASM进行AOP处理的执行时长

    AOP方式 时长
    Javassist 712ms
    ASM 120ms

    所以ASM在执行时间,性能上占有。在编译时修改class文件,我也推荐使用ASM方式。

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

    以上所有代码链接

    相关文章

      网友评论

          本文标题:Android AOP 总结

          本文链接:https://www.haomeiwen.com/subject/utwtottx.html