使用ASM對生命周期打點

##介紹ASM

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

##ASM庫結構

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-3fa870a12487ace4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

* Core?為其他包提供基礎的讀、寫、轉(zhuǎn)化Java字節(jié)碼和定義的API,并且可以生成Java字節(jié)碼和實現(xiàn)大部分字節(jié)碼的轉(zhuǎn)換

* Tree提供了Java字節(jié)碼在內(nèi)存中的表現(xiàn)

* Analysis為存儲在tree包結構中的java方法字節(jié)碼提供基本的數(shù)據(jù)流統(tǒng)計和類型檢查算法

* Commons提供一些常用的簡化字節(jié)碼生成轉(zhuǎn)化和適配器

* Util包含一些幫助類和簡單的字節(jié)碼修改,有利于在開發(fā)或者測試中使用

* XML提供一個適配器將XML和SAX-comliant轉(zhuǎn)化成字節(jié)碼結構,可以允許使用XSLT去定義字節(jié)碼轉(zhuǎn)化。

### class文件結構

ASM 是基于java字節(jié)碼層面的代碼分析和修改工具。所以學習ASM之前,還得不下class文件結構,java類型,java方法等知識

Class文件結構如下:

| 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等),名稱,父類,實現(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 編譯的代碼 |

* 每個類、字段、方法和方法代碼的屬性有屬于自己的名稱記錄在類文件格式的JVM規(guī)范的部分,這些屬性展示了字節(jié)碼多方面的信息,例如源文件名、內(nèi)部類、簽名、代碼行數(shù)、本地變量表和注釋。JVM規(guī)范允許定義自定義屬性,這些屬性會被標準的VM(虛擬機)忽略,但是可以包含附件信息。

* 方法代碼表包含一系列對java虛擬機的指令。有些指令在代碼中使用偏移量,當指令從方法代碼被插入或者移除時,全部偏移量的值可能需要調(diào)整。

###原java類型與class文件內(nèi)部類型對應關系

| 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)部聲明的對應關系

| 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)者消費者模式

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

* 字節(jié)碼生產(chǎn)者,例如ClassReader

* 字節(jié)碼消費者,例如writers(ClassWriter, FieldWriter, MethodWriter和AnnotationWriter),adapters(ClassAdapter和MethodAdapter)

下圖是生產(chǎn)者和消費者交互的時序圖:

官網(wǎng)提供的時序圖:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-4ba0865b79cc47f7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

網(wǎng)友畫的時序圖:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-325e8dddd5882892.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

通過時序圖可以看出ASM在處理class文件的整個過程。ASM通過樹這種數(shù)據(jù)結構來表示復雜的字節(jié)碼結構,并利用Push模型來對樹進行遍歷。

* ASM中提供一個ClassReader類,調(diào)用accept方法,接受一個實現(xiàn)了抽象類ClassVisitor的對象實例作為參數(shù),然后依次調(diào)用ClassVisitor的各個方法。字節(jié)碼空間上的偏移被轉(zhuǎn)成各種visitXXX方法。使用者只需要在對應的的方法上進行需求操作即可,無需考慮字節(jié)偏移。

* 這個過程中ClassReader可以看作是一個事件生產(chǎn)者,ClassWriter繼承自ClassVisitor抽象類,負責將對象化的class文件內(nèi)容重構成一個二進制格式的class字節(jié)碼文件,ClassWriter可以看作是一個事件的消費者。

##示例:攔截Android中 Activity生命周期方法,執(zhí)行的時長。

首先定義一個ActivityTimeManager記錄方法的使用時長,以onCreate方法為例。

``` java

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編譯的時候,在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個方法后,增加的字節(jié)碼。

所以我們怎么使用ASM,對class文件進行修改。把紅色部分的字節(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消費。

自定義ChangeVisitor 來處理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);

// 如果文件的注解不為空,說明文件要進行修改。則創(chuàng)建RedefineAdvice,修改方法

if (fileAnnotationVisitor!= null) {

return new RedefineAdvice(mv, access, owner, name, desc);

}

return mv;

}

}

```

ChangeVisitor,繼承ClassVisitor,class文件的訪問,可以重寫

visitAnnotation(), 獲取或者修改注解

visitMethod(),獲取或者修改方法

visitField(),獲取或者修改成員變量

這段代碼的邏輯是:先判斷這個class文件是否有注解。如果有注解,則先解析注解。如果注解不為空,則說明有方法需要修改則創(chuàng)建RedefineAdvice,訪問和修改方法。

看下ActivityAnnotationVisitor,對注解的訪問和解析。

```

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,對方法的修改

```

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使用注解寫入

mv.visitMethodInsn(INVOKESTATIC, "com/test/aop/tools/ActivityTimeManger",

activityAnnotationVisitor.value+"Start",

"(Landroid/app/Activity;)V");

}

//在方法執(zhí)行結束前,插入字節(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使用注解寫入

mv.visitMethodInsn(INVOKESTATIC, "com/test/aop/tools/ActivityTimeManger",?

activityAnnotationVisitor.value+"End",

"(Landroid/app/Activity;)V");

}

}

```

整段代碼邏輯是:

先查找方法上的注解,如果方法上有注解,則獲取注解的value。在方法執(zhí)行前后,插入字節(jié)碼。通過重寫onMethodEnter和onMethodExit方法。

所以在原來的TestActivity上,增加注解class注解和方法注解,然后通過processClass()處理,就能在記錄Activity方法執(zhí)行的時間。

```

@FileAnnotation("TestActivity")

public class TestActivity extends Activity{

@ActivityAnnotation("onCreate")

public void onCreate() {

System.out.println("onCreate");

}

```

編譯后的class文件,反編譯后,結果如下:

```

@FileAnnotation

public class TestActivity extends Activity {

? ? @ActivityAnnotation

? ? public void onCreate() {

? ? ? ? ActivityTimeManger.onCreateStart(this);

? ? ? ? System.out.println("onCreate");

? ? ? ? ActivityTimeManger.onCreateEnd(this);

? ? }

}

```

備注:Android App目前大部分都是通過gradle編譯。所以以上字節(jié)碼處理代碼,都需要寫在自定義的gradle插件中,自定義一個Transform處理。

怎么自定義gradle插件和Transform ,可以百度,或者google。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容