美文网首页IT@程序员猿媛
lancet 库的工作原理

lancet 库的工作原理

作者: 未见哥哥 | 来源:发表于2019-04-24 20:25 被阅读68次

    Lancet

    lancet 是一个轻量级Android AOP框架。

    dependencies{
        classpath 'me.ele:lancet-plugin:1.0.4'
    }
    
    apply plugin: 'me.ele.lancet'
    
    dependencies {
        provided 'me.ele:lancet-base:1.0.4'
    }
    

    下面使用 Lancet 来 hook Cat 接口的 eat() 方法

    • Cat.java
    package com.example.androidperfermance.aop.lancet;
    
    public class Cat {
        public void eat() {
            System.out.println("猫吃老鼠");
        }
    
        @Override
        public String toString() {
            return "猫";
        }
    }
    
    • 使用 Cat 方法
    private void testLancet() {
        Cat cat = new Cat();
        cat.eat();//输出 猫吃老鼠 
    }
    

    下面使用 Lancet 来 hook 这个 Cat 类的 eat() 方法

    • LancetHooker.java hook 类
    package com.example.androidperfermance.aop.lancet;
    import android.util.Log;
    import java.lang.annotation.Target;
    import me.ele.lancet.base.Origin;
    import me.ele.lancet.base.Scope;
    import me.ele.lancet.base.annotations.ImplementedInterface;
    import me.ele.lancet.base.annotations.Insert;
    import me.ele.lancet.base.annotations.Proxy;
    import me.ele.lancet.base.annotations.TargetClass;
    
    public class LancetHooker {
    
        @Insert(value = "eat", mayCreateSuper = true)
        @TargetClass(value = "com.example.androidperfermance.aop.lancet.Cat", scope = Scope.SELF)
        public void _eat() {
            //这里可以使用 this 访问当前 Cat 类的成员,仅用于Insert 方式的非静态方法的Hook中.(暂时)
            System.out.println(">>>>>>>" + this);
            System.out.println(Log.getStackTraceString(new Throwable()));
            Origin.callVoid();  
        }
    }
    
    输出结果

    通过输出结果可以看出,Lancet 是已经 hook 到对应的方法了。

    分析 Lancet 对目标类做了什么操作

    程序中不需要显示地去调用 LancetHooker 类,Lancet 内部是通过 Gradle Transform 来修改目标类的字节码,Transform 是指在项目在编译成 .class 之后在生成 .dex 之前通过 ASM 去修改 .class 文件的。下面这些类都是在打包编译时生成的,因此上面引入 lancet 库时只需要使用 privoded ,provided 表示在编译时依赖,不需要在运行时依赖。

    下面来分析 lancet 生成的类是如何工作的,Transfrom 生成的 class 文件放在 build/intermediates/transforms/lancet/debug/58/com/example/androidperfermance/aop/lancet/

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.example.androidperfermance.aop.lancet;
    
    import android.util.Log;
    import me.ele.lancet.base.Scope;
    import me.ele.lancet.base.annotations.Insert;
    import me.ele.lancet.base.annotations.TargetClass;
    
    public class Cat {
        public Cat() {
        }
    
        public void eat() {
            Cat._lancet.com_example_androidperfermance_aop_lancet_LancetHooker__eat(this);
        }
        //eat 方法真正的逻辑
        private void eat$___twin___() {
            System.out.println("猫吃老鼠");
        }
    
        public String toString() {
            return "猫";
        }
    
        private static class _lancet {
            private _lancet() {
            }
    
            @Insert(
                value = "eat",
                mayCreateSuper = true
            )
            @TargetClass(
                value = "com.example.androidperfermance.aop.lancet.Cat",
                scope = Scope.SELF
            )
            static void com_example_androidperfermance_aop_lancet_LancetHooker__eat(Cat var0) {
                System.out.println(">>>>>>>" + var0);
                System.out.println(Log.getStackTraceString(new Throwable()));
                var0.eat$___twin___();
            }
        }
    }
    
    • 对比我们自己 Cat 类,看 Lancet 生成 Cat.class 差异在哪里?

    针对目标类 Cat 生成一个内部类 Cat._lancet,当外部调用 Cat 对象 中的 eat()方法时内部会调用 _lancetcom_example_androidperfermance_aop_lancet_LancetHooker_eat 方法。

    static void com_example_androidperfermance_aop_lancet_LancetHooker__eat(Cat var0) {
        System.out.println(">>>>>>>" + var0);
        System.out.println(Log.getStackTraceString(new Throwable()));
        var0.eat$___twin___();
     }
    

    com_example_androidperfermance_aop_lancet_LancetHooker_eat 方法体内容就是在 LancetHooker 类中编写的方法体,内部调用eat$___twin___(),它是 eat() 方法的真正实现。

    private void eat$___twin___() {
        System.out.println("猫吃老鼠");
    }
    

    注意这个方法eat$___twin___()是私有的。 也就是说只能外界只能调用 eat() 方法,通过 eat() 再来调用 eat$___twin___()方法。

    总结

    本文简单地使用 Lancet 实现 hook 了 Cat 类的 eat() 方法,并根据这个 Lancet 生成的类,分析了 Lancet 是如何实现 hook 操作的。Lancet 这个库还有很多使用方式,具体可以参考一下官方文档 lancet

    本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。

    记录于 2019年4月24号

    相关文章

      网友评论

        本文标题:lancet 库的工作原理

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