##介紹ASM
ASM是一款基于java字節(jié)碼層面的代碼分析和修改工具横侦。無需提供源代碼即可對應(yīng)用嵌入所需debug代碼顷锰,用于應(yīng)用API性能分析柬赐。ASM可以直接產(chǎn)生二進制class文件,也可以在類被加入JVM之前動態(tài)修改類行為官紫。
##ASM庫結(jié)構(gòu)
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-3fa870a12487ace4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
* Core?為其他包提供基礎(chǔ)的讀肛宋、寫、轉(zhuǎn)化Java字節(jié)碼和定義的API束世,并且可以生成Java字節(jié)碼和實現(xiàn)大部分字節(jié)碼的轉(zhuǎn)換
* Tree提供了Java字節(jié)碼在內(nèi)存中的表現(xiàn)
* Analysis為存儲在tree包結(jié)構(gòu)中的java方法字節(jié)碼提供基本的數(shù)據(jù)流統(tǒng)計和類型檢查算法
* Commons提供一些常用的簡化字節(jié)碼生成轉(zhuǎn)化和適配器
* Util包含一些幫助類和簡單的字節(jié)碼修改酝陈,有利于在開發(fā)或者測試中使用
* XML提供一個適配器將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方法等知識
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等),名稱严嗜,父類粱檀,實現(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)部類型對應(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)部聲明的對應(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)者消費者模式
在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é)構(gòu)來表示復(fù)雜的字節(jié)碼結(jié)構(gòu)钾虐,并利用Push模型來對樹進行遍歷噪窘。
* ASM中提供一個ClassReader類磁奖,調(diào)用accept方法殷蛇,接受一個實現(xiàn)了抽象類ClassVisitor的對象實例作為參數(shù),然后依次調(diào)用ClassVisitor的各個方法史辙。字節(jié)碼空間上的偏移被轉(zhuǎn)成各種visitXXX方法菌仁。使用者只需要在對應(yīng)的的方法上進行需求操作即可浩习,無需考慮字節(jié)偏移。
* 這個過程中ClassReader可以看作是一個事件生產(chǎn)者济丘,ClassWriter繼承自ClassVisitor抽象類谱秽,負責(zé)將對象化的class文件內(nèi)容重構(gòu)成一個二進制格式的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é)束前铁材,插入字節(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文件,反編譯后村砂,結(jié)果如下:
```
@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帘瞭。