IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> asm实现注解打印log -> 正文阅读

[移动开发]asm实现注解打印log

前言

在Android开发中有时候调试问题要给方法加很多的log,很麻烦,所以结合asm用注解的方式来自动在方法中插入log,这样方便开发时候调试。当然通过asm插入的log应该需要包含方法的参数,方法的返回值,有时候也需要获取对象里面的变量值等。

hanno

_    _
| |  | |                        
| |__| | __ _ _ __  _ __   ___  
|  __  |/ _` | '_ \| '_ \ / _ \
| |  | | (_| | | | | | | | (_) |
|_|  |_|\__,_|_| |_|_| |_|\___/

通过字节码插件实现注解打印log,注解可以加在类上面,也可以加在方法上面,当加在类上面时会打印全部方法的log,当加在方法上面时打印当前方法的log

使用方法

1、类中全部方法打印log

@HannoLog
class MainActivity : AppCompatActivity() {
   // ...
}

只要在类上面加上@HannoLog注解就可以在编译的时候给这个类中所有的方法插入log,运行时输出log。

2、给类中的某些方法加log

class MainActivity : AppCompatActivity() {
    @HannoLog(level = Log.INFO, enableTime = false,watchField=true)
    private fun test(a: Int = 3, b: String = "good"): Int {
        return a + 1
    }
}

通过在方法上面添加注解可以在当前方法中插入log。
3、打印的log

//D/MainActivity: ┌───────────────────────────────────------───────────────────────────────────------
//D/MainActivity: │ method: onCreate(android.os.Bundle)
//D/MainActivity: │ params: [{name='savedInstanceState', value=null}]
//D/MainActivity: │ time: 22ms
//D/MainActivity: │ fields: {name='a', value=3}{name='b', value=false}{name='c', value=ccc}
//D/MainActivity: │ thread: main
//D/MainActivity: └───────────────────────────────────------───────────────────────────────────------

其中method是当前方法名,params是方法的参数名和值,time方法的执行时间,fields是当前对象的fields值,thread当前方法执行的线程。

HannoLog参数解释

可以通过level来设置log的级别,level的设置可以调用Log里面的INFO,DEBUG,ERROR等。enableTime用来设置是否打印方法执行的时间,默认是false,如果要打印设置enableTime=true.
tagName用于设置log的名称,默认是当前类名,也可以通过这个方法进行设置。

1、level控制log打印的等级,默认是log.d,可以通过@HannoLog(level = Log.INFO)来设置等级,支持Log.DEBUG,Log.ERROR等。

2、enableTime控制是否输出方法的执行时间,默认是false,如果要打印可以通过@HannoLog(enableTime=true)来设置。

3、tagName设置tag的名称,默认是当前类名,也可以通过 @HannoLog(tagName = “test”)来设置。

4、watchField用于观察对象中的field值,通过@HannoLog(watchField = true)设置,由于静态方法中不能调用非静态的field所以这个参数在静态方法上统一不生效。

重要的类

1、HannoLog
HannoLog是注解类,里面提供了控制参数。对应上面的HannoLog参数解释

/**
* 
* 
* 
* create by 胡汉君
* date 2021/11/10 17:38
* 定义一个注解,用于标注当前方法需要打印log
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
public @interface HannoLog {
   //定义一下log的级别,默认是3,debug级别
   int level() default Log.DEBUG;
   /**
    * @return 打印方法的运行时间
    */
   boolean enableTime() default false;

   /**
    * @return tag的名称,默认是类名,也可以设置
    */
   String tagName() default "";

   /**
    * @return 是否观察field的值,如果观察就会就拿到对象里面全部的field值
    */
   boolean watchField() default false;
}

2、HannoExtension

public class HannoExtension {
//控制是否使用Hanno
boolean enable;
//控制是否打印log
boolean openLog = true;

   public boolean isEnableModule() {
       return enableModule;
   }

   public void setEnableModule(boolean enableModule) {
       this.enableModule = enableModule;
   }

   //设置这个值为true可以给整个module的方法增加log
   boolean enableModule = false;

   public boolean isEnable() {
       return enable;
   }

   public boolean isOpenLog() {
       return openLog;
   }

   public void setOpenLog(boolean openLog) {
       this.openLog = openLog;
   }

   public void setEnable(boolean enable) {
       this.enable = enable;
   }
}

HannoExtension提供gradle.build文件是否开启plugin 和打印执行plugin的log
默认情况下添加HannoLog之后会进行asm插装,也可以通过在module的build.gradle文件中添加以下配置使在编译时不执行字节码插装提高编译速度

apply plugin: 'com.hanking.hanno'
hannoExtension{
 enable=false
 openLog=false
}

实现原理

hanno是通过asm字节码插桩方式来实现的。Android项目的编译过程如下图:
在这里插入图片描述
java编译器会将.java类编译生成.class类,asm可以用来修改.class类,通过对.class类的修改就可以达到往已有的类中加入代码的目的。一个.java文件经过Java编译器(javac)编译之后会生成一个.class文件。 在.class文件中,存储的是字节码(ByteCode)数据,如下图所示。
在这里插入图片描述
ASM所的操作对象是是字节码(ByteCode)的类库。ASM处理字节码(ByteCode)数据的流程是这样的:

第一步,将.class文件拆分成多个部分;

第二步,对某一个部分的信息进行修改;

第三步,将多个部分重新组织成一个新的.class文件。

ClassFile

.class文件中,存储的是ByteCode数据。但是,这些ByteCode数据并不是杂乱无章的,而是遵循一定的数据结构。

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

字节码的类库和ClassFile之间关系
在这里插入图片描述

asm的组成

从组成结构上来说,ASM分成两部分,一部分为Core API,另一部分为Tree API。

  • 其中,Core API包括asm.jar、asm-util.jar和asm-commons.jar;
  • 其中,Tree API包括asm-tree.jar和asm-analysis.jar。
    在这里插入图片描述

asm中重要的类

  • ClassReader类,负责读取.class文件里的内容,然后拆分成各个不同的部分。
  • ClassVisitor类,负责对.class文件中某一部分里的信息进行修改。
  • ClassWriter类,负责将各个不同的部分重新组合成一个完整的.class文件。
    在这里插入图片描述
.class文件 --> ClassReader --> byte[] --> 经过各种转换 --> ClassWriter --> byte[] --> .class文件

ClassVisitor类

ClassVisitor是一个抽象类,实现类有ClassWriter类(Core API)和ClassNode类(Tree API)。

public abstract class ClassVisitor {
    protected final int api;
    protected ClassVisitor cv;
}
  • api字段:int类型的数据,指出了当前使用的ASM API版本。
  • cv字段:ClassVisitor类型的数据,它的作用是将多个ClassVisitor串连起来
    在这里插入图片描述

classVisitor的方法

visit()、visitField()、visitMethod()和visitEnd()。

visitXxx()方法与ClassFile
ClassVisitor的visitXxx()方法与ClassFile之间存在对应关系。在ClassVisitor中定义的visitXxx()方法,并不是凭空产生的,这些方法存在的目的就是为了生成一个合法的.class文件,而这个.class文件要符合ClassFile的结构,所以这些visitXxx()方法与ClassFile的结构密切相关。
1、visit()方法
用于生成类或者接口的定义,如下生成一个为printField的类,因为如果类默认继承的父类是Object类,所以superName是” java/lang/Object “。

cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/hank/test/PrintField", null, "java/lang/Object", null);

2、visitField()方法
对应classFile中的field_info,用于生成对象里面的属性值。通过visitField生成一个属性,如下:

FieldVisitor fv;
{
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "a", "I", null, new Integer(2));
fv.visitEnd();
}

3、visitMethod()方法
用于生成一个方法,对应classFile中的method_info

ClassWriter类

ClassWriter的父类是ClassVisitor,因此ClassWriter类继承了visit()、visitField()、visitMethod()和visitEnd()等方法。
toByteArray方法
在ClassWriter类当中,提供了一个toByteArray()方法。这个方法的作用是将对visitXxx()的调用转换成byte[],而这些byte[]的内容就遵循ClassFile结构。
在toByteArray()方法的代码当中,通过三个步骤来得到byte[]:

  • 第一步,计算size大小。这个size就是表示byte[]的最终的长度是多少。
  • 第二步,将数据填充到byte[]当中。
  • 第三步,将byte[]数据返回。
    3、使用ClassWriter类
    使用ClassWriter生成一个Class文件,可以大致分成三个步骤:
  • 第一步,创建ClassWriter对象。
  • 第二步,调用ClassWriter对象的visitXxx()方法。
  • 第三步,调用ClassWriter对象的toByteArray()方法。
import org.objectweb.asm.ClassWriter;

import static org.objectweb.asm.Opcodes.*;

public class GenerateCore {
    public static byte[] dump () throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        // (2) 调用visitXxx()方法
        cw.visit();
        cw.visitField();
        cw.visitMethod();
        cw.visitEnd();       // 注意,最后要调用visitEnd()方法

        // (3) 调用toByteArray()方法
        byte[] bytes = cw.toByteArray();
        return bytes;
    }
}

Hanno源码分析

上面已经先回顾一下asm相关的基础知识,下面对hanno源码进行分析。主要针对三个方面:

1、如何在方法中插入Log语句。

2、如何获取对象中的field值。

3、如何获取到方法的参数名和参数值,在使用classvisitor的时候visitLocalVariable之后是无法插入代码的,如何解决这个问题?来使得获取的方法的变量名能够插入到Log中。
//未完待续… …

源码地址

https://github.com/hankinghu/hanno

后续优化

1、可以针对某个模块进行配置,对模块配置后会自动给模块中所有的类的全部方法插入log。
2、对编译速度进行优化,现在编译过程中两次遍历class,会比较耗时。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-04 13:33:24  更:2021-12-04 13:35:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:25:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码