Android 中使用ASM怎棱,對(duì)Activity生命周期打點(diǎn)統(tǒng)計(jì)

介紹ASM

ASM是一款基于java字節(jié)碼層面的代碼分析和修改工具。無(wú)需提供源代碼即可對(duì)應(yīng)用嵌入所需debug代碼拥诡,用于應(yīng)用API性能分析触趴。ASM可以直接產(chǎn)生二進(jìn)制class文件,也可以在類被加入JVM之前動(dòng)態(tài)修改類行為渴肉。

ASM庫(kù)結(jié)構(gòu)

Paste_Image.png
  • Core 為其他包提供基礎(chǔ)的讀冗懦、寫(xiě)、轉(zhuǎn)化Java字節(jié)碼和定義的API宾娜,并且可以生成Java字節(jié)碼和實(shí)現(xiàn)大部分字節(jié)碼的轉(zhuǎn)換
  • Tree提供了Java字節(jié)碼在內(nèi)存中的表現(xiàn)
  • Analysis為存儲(chǔ)在tree包結(jié)構(gòu)中的java方法字節(jié)碼提供基本的數(shù)據(jù)流統(tǒng)計(jì)和類型檢查算法
  • Commons提供一些常用的簡(jiǎn)化字節(jié)碼生成轉(zhuǎn)化和適配器
  • Util包含一些幫助類和簡(jiǎn)單的字節(jié)碼修改批狐,有利于在開(kāi)發(fā)或者測(cè)試中使用
  • XML提供一個(gè)適配器將XML和SAX-comliant轉(zhuǎn)化成字節(jié)碼結(jié)構(gòu)扇售,可以允許使用XSLT去定義字節(jié)碼轉(zhuǎn)化前塔。

class文件結(jié)構(gòu)

ASM 是基于java字節(jié)碼層面的代碼分析和修改工具。所以學(xué)習(xí)ASM之前承冰,還得不下class文件結(jié)構(gòu)华弓,java類型,java方法等知識(shí)
Class文件結(jié)構(gòu)如下:

| Header|
| --------- ------ |
| Modifiers, name, super class, interfaces |
| Constant pool: numeric, string and type constants |
| Source file name (optional) |
| Enclosing class reference |
| Annotation* |
| Attribute* |

member attribute
Inner class* Name
Field* Modifiers, name, type
Annotation*
Attribute*
Method* Modifiers, name, return and parameter types
Annotation*
Attribute*
Compiled code

翻譯成中文:

| Header|
| --------- ------ |
| Modifiers, name, super class, interfaces 修飾(public/private等)困乒,名稱寂屏,父類,實(shí)現(xiàn)的接口 |
| Constant pool: numeric, string and type constants 常量池,數(shù)字迁霎,字符串吱抚,類型常量(枚舉類型) |
| Source file name (optional) 原文件名稱,(可選) |
| Enclosing class reference 外部類的引用 |
| Annotation* Class的注解 |
| Attribute* Class屬性 |

member attribute
Inner class* 內(nèi)部類 Name 名稱
Field* 成員變量 Modifiers, name, type 修飾符考廉,名稱秘豹,類型
Annotation* 注解
Attribute* 屬性
Method* 方法 Modifiers, name, return and parameter types Modifiers, name, return and parameter types 修飾符,名稱昌粤,返回類型既绕,參數(shù)類型
Annotation* 注解
Attribute* 類型
Compiled code 編譯的代碼
  • 每個(gè)類、字段涮坐、方法和方法代碼的屬性有屬于自己的名稱記錄在類文件格式的JVM規(guī)范的部分凄贩,這些屬性展示了字節(jié)碼多方面的信息,例如源文件名袱讹、內(nèi)部類疲扎、簽名、代碼行數(shù)廓译、本地變量表和注釋评肆。JVM規(guī)范允許定義自定義屬性,這些屬性會(huì)被標(biāo)準(zhǔn)的VM(虛擬機(jī))忽略非区,但是可以包含附件信息瓜挽。
  • 方法代碼表包含一系列對(duì)java虛擬機(jī)的指令。有些指令在代碼中使用偏移量征绸,當(dāng)指令從方法代碼被插入或者移除時(shí)久橙,全部偏移量的值可能需要調(diào)整。

原java類型與class文件內(nèi)部類型對(duì)應(yīng)關(guān)系

Java type Type descriptor
boolean Z
char C
byte B
short S
int I
float F
long J
double D
Object Ljava/lang/Object;
int[] [I
Object[][] [[Ljava/lang/Object;

原java方法聲明與class文件內(nèi)部聲明的對(duì)應(yīng)關(guān)系

Method declaration in source file Method descriptor
void m(int i, float f) (IF)V
int m(Object o) (Ljava/lang/Object;)I
int[] m(int i, String s) (ILjava/lang/String;)[I
Object m(int[] i) ([I]Ljava/lang/Object;

參數(shù)描述在前面管怠,返回值描述在后面

ASM的處理流程淆衷,生產(chǎn)者消費(fèi)者模式

在Core包中邏輯上分為2部分:

  • 字節(jié)碼生產(chǎn)者,例如ClassReader
  • 字節(jié)碼消費(fèi)者渤弛,例如writers(ClassWriter, FieldWriter, MethodWriter和AnnotationWriter)祝拯,adapters(ClassAdapter和MethodAdapter)
    下圖是生產(chǎn)者和消費(fèi)者交互的時(shí)序圖:
    官網(wǎng)提供的時(shí)序圖:
Paste_Image.png

網(wǎng)友畫(huà)的時(shí)序圖:

Paste_Image.png

通過(guò)時(shí)序圖可以看出ASM在處理class文件的整個(gè)過(guò)程。ASM通過(guò)樹(shù)這種數(shù)據(jù)結(jié)構(gòu)來(lái)表示復(fù)雜的字節(jié)碼結(jié)構(gòu)她肯,并利用Push模型來(lái)對(duì)樹(shù)進(jìn)行遍歷佳头。

  • ASM中提供一個(gè)ClassReader類,調(diào)用accept方法晴氨,接受一個(gè)實(shí)現(xiàn)了抽象類ClassVisitor的對(duì)象實(shí)例作為參數(shù)康嘉,然后依次調(diào)用ClassVisitor的各個(gè)方法。字節(jié)碼空間上的偏移被轉(zhuǎn)成各種visitXXX方法籽前。使用者只需要在對(duì)應(yīng)的的方法上進(jìn)行需求操作即可亭珍,無(wú)需考慮字節(jié)偏移敷钾。
  • 這個(gè)過(guò)程中ClassReader可以看作是一個(gè)事件生產(chǎn)者,ClassWriter繼承自ClassVisitor抽象類肄梨,負(fù)責(zé)將對(duì)象化的class文件內(nèi)容重構(gòu)成一個(gè)二進(jìn)制格式的class字節(jié)碼文件阻荒,ClassWriter可以看作是一個(gè)事件的消費(fèi)者。

示例:攔截Android中 Activity生命周期方法众羡,執(zhí)行的時(shí)長(zhǎng)财松。

首先定義一個(gè)ActivityTimeManager記錄方法的使用時(shí)長(zhǎng),以onCreate方法為例纱控。

public class ActivityTimeManger {
    public static HashMap<String, Long> startTimeMap = new HashMap<>();
    public static void onCreateStart(Activity activity) {
        startTimeMap.put(activity.toString(), System.currentTimeMillis());
    }
    public static void onCreateEnd(Activity activity) {
        Long startTime = startTimeMap.get(activity.toString());
        if (startTime == null) {
            return;
        }
        long coastTime = System.currentTimeMillis() - startTime;
        System.out.println(activity.toString() + " onCreate coast Time" + coastTime);
        startTimeMap.remove(activity.toString());
… …
    }

在Activity編譯的時(shí)候辆毡,在onCreate方法中,前后各插入ActivityTimeManger. onCreateStart() 和
ActivityTimeManger. onCreateEnd() 方法
原始的Activity甜害,onCreate方法:

public class TestActivity extends Activity{
    
    public void onCreate() {
        System.out.println("onCreate");
    }
}

使用javap –c 命令 查看class文件的字節(jié)碼舶掖,如下:

public com.test.aop.main.TestActivity();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method android/app/Activity."<init>":()V
       4: return

  public void onCreate();
    Code:
       0: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #21                 // String onCreate
       5: invokevirtual #22                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

加上ActivityTimeManger. onCreateStart(),ActivityTimeManger. onCreateEnd()之后的源碼如下:

public class TestActivity extends Activity{
    public void onCreate() {
        ActivityTimeManger.onCreateStart(this);
        System.out.println("onCreate");
        ActivityTimeManger.onCreateEnd(this);
    }

使用javap –c 命令 查看class文件的字節(jié)碼尔店,如下:

public com.test.aop.main.TestActivity();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method android/app/Activity."<init>":()V
       4: return

  public void onCreate();
    Code:
       0: aload_0
       1: invokestatic  #15                 // Method com/test/aop/tools/ActivityTimeManger.onCreateStart:(Landroid/app/Activity;)V
       4: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #27                 // String onCreate
       9: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: aload_0
      13: invokestatic  #34                 // Method com/test/aop/tools/ActivityTimeManger.onCreateEnd:(Landroid/app/Activity;)V
      16: return

紅色部分是增加ActivityTimeManger. onCreateStart()眨攘,ActivityTimeManger. onCreateEnd()2個(gè)方法后,增加的字節(jié)碼嚣州。
所以我們?cè)趺词褂肁SM鲫售,對(duì)class文件進(jìn)行修改。把紅色部分的字節(jié)碼插入到class文件中呢该肴?
先輸入文件情竹。把class文件重命名為.opt文件,修改完后匀哄,再重命名回去蚕苇。

public static void processClass(File file) {
        System.out.println("start process class " + file.getPath());
        File optClass = new File(file.getParent(), file.getName() + ".opt");
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(file);
            outputStream = new FileOutputStream(optClass);
            byte[] bytes = referHack(inputStream);
            outputStream.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        if (file.exists()) {
            file.delete();
        }
        optClass.renameTo(file);
    }

referHack 方法

    private static byte[] referHack(InputStream inputStream) {
        try {
            ClassReader classReader = new ClassReader(inputStream);
            ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
            ClassVisitor changeVisitor = new ChangeVisitor(classWriter);
            classReader.accept(changeVisitor, ClassReader.EXPAND_FRAMES);
            return classWriter.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        return null;
    }

創(chuàng)建ClassReader咆霜,生產(chǎn)者靖苇,讀出class字節(jié)碼鼓寺,輸出給ClassWriter消費(fèi)。
自定義ChangeVisitor 來(lái)處理class字節(jié)碼法梯。

    public static class ChangeVisitor extends ClassVisitor {
   // 記錄文件名 
    private String owner;
        private ActivityAnnotationVisitor fileAnnotationVisitor = null;
        public ChangeVisitor(ClassVisitor cv) {
            super(Opcodes.ASM5, cv);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName,
                String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);
            this.owner = name;
        }

        @Override
// 處理class文件的注解
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            System.out.println("visitAnnotation: desc=" + desc + " visible=" + visible);
            AnnotationVisitor annotationVisitor = super.visitAnnotation(desc, visible);
            if (desc != null) {
// 如果注解不是空苔货,傳遞給ActivityAnnotationVisitor處理。
                fileAnnotationVisitor = new ActivityAnnotationVisitor(Opcodes.ASM5, annotationVisitor, desc);
                return FileAnnotationVisitor;
            }
            return annotationVisitor;
        }
        
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// 獲取到原始的MethodVisitor
            MethodVisitor mv = this.cv.visitMethod(access, name, desc, signature, exceptions);
// 如果文件的注解不為空立哑,說(shuō)明文件要進(jìn)行修改夜惭。則創(chuàng)建RedefineAdvice,修改方法
            if (fileAnnotationVisitor!= null) {
                return new RedefineAdvice(mv, access, owner, name, desc);
            }
            return mv;
        }
    }

ChangeVisitor刁憋,繼承ClassVisitor滥嘴,class文件的訪問(wèn)木蹬,可以重寫(xiě)
visitAnnotation(), 獲取或者修改注解
visitMethod()至耻,獲取或者修改方法
visitField()若皱,獲取或者修改成員變量
這段代碼的邏輯是:先判斷這個(gè)class文件是否有注解。如果有注解尘颓,則先解析注解走触。如果注解不為空,則說(shuō)明有方法需要修改則創(chuàng)建RedefineAdvice疤苹,訪問(wèn)和修改方法互广。
看下ActivityAnnotationVisitor,對(duì)注解的訪問(wèn)和解析卧土。

public static class ActivityAnnotationVisitor extends AnnotationVisitor {
        public String desc;
        public String name;
        public String value;

        public ActivityAnnotationVisitor(int api, AnnotationVisitor av, String paramDesc) {
            super(api, av);
            this.desc = paramDesc;
        }

        public void visit(String paramName, Object paramValue) {
            this.name = paramName;
            this.value = paramValue.toString();
            System.out.println("visitAnnotation: name=" + name + " value=" + value);
        }

    }

記錄注解的名稱和值惫皱,描述。
RedefineAdvice尤莺,對(duì)方法的修改

public static class RedefineAdvice extends AdviceAdapter {
        String owner = "";
        ActivityAnnotationVisitor activityAnnotationVisitor = null;
        protected RedefineAdvice(MethodVisitor mv, int access, String className, String name, String desc) {
            super(Opcodes.ASM5, mv, access, name, desc);
            owner = className;
        }

        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            System.out.println("visitAnnotation: desc=" + desc + " visible=" + visible);
            AnnotationVisitor annotationVisitor = super.visitAnnotation(desc, visible);
// 先判斷方法上是否有注解旅敷,如果有注解,則使用ActivityAnnotationVisitor解析注解
            if (desc != null) {
                activityAnnotationVisitor = new ActivityAnnotationVisitor(Opcodes.ASM5, annotationVisitor, desc);
                return activityAnnotationVisitor;
            }
            return annotationVisitor;
        }

        @Override
// 修改方法入口颤霎,在方法執(zhí)行前媳谁,插入字節(jié)碼      
protected void onMethodEnter() {
            if (activityAnnotationVisitor == null) {
                return;
            }
            super.onMethodEnter();
//插入字節(jié)碼,ALOAD
            mv.visitVarInsn(ALOAD, 0);
//插入字節(jié)碼INVOKESTATIC友酱,調(diào)用ActivityTimeManger.onCreateStart().
// onCreate使用注解寫(xiě)入
            mv.visitMethodInsn(INVOKESTATIC, "com/test/aop/tools/ActivityTimeManger",
                    activityAnnotationVisitor.value+"Start",
                    "(Landroid/app/Activity;)V");
        }

//在方法執(zhí)行結(jié)束前晴音,插入字節(jié)碼
        @Override
        protected void onMethodExit(int opcode) {
            if (activityAnnotationVisitor == null) {
                return;
            }
            super.onMethodExit(opcode);
//插入字節(jié)碼,ALOAD
            mv.visitVarInsn(ALOAD, 0);
//插入字節(jié)碼INVOKESTATIC缔杉,調(diào)用ActivityTimeManger.onCreateEnd().
// onCreate使用注解寫(xiě)入
            mv.visitMethodInsn(INVOKESTATIC, "com/test/aop/tools/ActivityTimeManger", 
                    activityAnnotationVisitor.value+"End",
                    "(Landroid/app/Activity;)V");
        }
    }

整段代碼邏輯是:
先查找方法上的注解锤躁,如果方法上有注解,則獲取注解的value或详。在方法執(zhí)行前后进苍,插入字節(jié)碼。通過(guò)重寫(xiě)onMethodEnter和onMethodExit方法鸭叙。
所以在原來(lái)的TestActivity上觉啊,增加注解class注解和方法注解,然后通過(guò)processClass()處理沈贝,就能在記錄Activity方法執(zhí)行的時(shí)間杠人。

@FileAnnotation("TestActivity")
public class TestActivity extends Activity{
    
    @ActivityAnnotation("onCreate")
    public void onCreate() {
        System.out.println("onCreate");
    }

編譯后的class文件,反編譯后宋下,結(jié)果如下:

@FileAnnotation
public class TestActivity extends Activity {
    @ActivityAnnotation
    public void onCreate() {
        ActivityTimeManger.onCreateStart(this);
        System.out.println("onCreate");
        ActivityTimeManger.onCreateEnd(this);
    }
}

備注:Android App目前大部分都是通過(guò)gradle編譯嗡善。所以以上字節(jié)碼處理代碼,都需要寫(xiě)在自定義的gradle插件中学歧,自定義一個(gè)Transform處理罩引。
關(guān)于怎么自定義gradle插件和Transform ,可以百度枝笨,或者google袁铐。這里就不在寫(xiě)了揭蜒。
以上所有代碼鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剔桨,隨后出現(xiàn)的幾起案子屉更,更是在濱河造成了極大的恐慌,老刑警劉巖洒缀,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瑰谜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡树绩,警方通過(guò)查閱死者的電腦和手機(jī)萨脑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饺饭,“玉大人砚哗,你說(shuō)我怎么就攤上這事∨檗龋” “怎么了蛛芥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)军援。 經(jīng)常有香客問(wèn)我仅淑,道長(zhǎng),這世上最難降的妖魔是什么胸哥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任涯竟,我火速辦了婚禮,結(jié)果婚禮上空厌,老公的妹妹穿的比我還像新娘庐船。我一直安慰自己,他們只是感情好嘲更,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布筐钟。 她就那樣靜靜地躺著,像睡著了一般赋朦。 火紅的嫁衣襯著肌膚如雪篓冲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天宠哄,我揣著相機(jī)與錄音壹将,去河邊找鬼。 笑死毛嫉,一個(gè)胖子當(dāng)著我的面吹牛诽俯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播承粤,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼暴区,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼闯团!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起颜启,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浪讳,沒(méi)想到半個(gè)月后缰盏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淹遵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年口猜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片透揣。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡济炎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辐真,到底是詐尸還是另有隱情须尚,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布侍咱,位于F島的核電站耐床,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏楔脯。R本人自食惡果不足惜撩轰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望昧廷。 院中可真熱鬧堪嫂,春花似錦、人聲如沸木柬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眉枕。三九已至愚战,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間齐遵,已是汗流浹背寂玲。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梗摇,地道東北人拓哟。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像伶授,于是被迫代替她去往敵國(guó)和親断序。 傳聞我的和親對(duì)象是個(gè)殘疾皇子流纹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容