Android AOP 总结

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生命周期打点统计

以上所有代码链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容