您的位置:首页 > 教程 > Android开发 > Android开发AsmClassVisitorFactory使用详解

Android开发AsmClassVisitorFactory使用详解

2022-06-21 19:47:23 来源:易采站长站 作者:

Android开发AsmClassVisitorFactory使用详解

目录
前言AsmClassVisitorFactory新的Extension实战ClassVisitor实际代码分析个人观点

EHS站长之家-易采站长站-Easck.Com

前言

之前就和大家介绍过AGP(Android>版本之后Transform 已经过期即将废弃的事情。而且也简单的介绍了替换的方式是Transform Action,经过我这一阵子的学习和调研,发现只能说答对了一半吧。下面介绍个新东西AsmClassVisitorFactoryEHS站长之家-易采站长站-Easck.Com

com.android.build.api.instrumentation.AsmClassVisitorFactoryEHS站长之家-易采站长站-Easck.Com

A factory to create class visitor objects to instrument classes.EHS站长之家-易采站长站-Easck.Com

The implementation of this interface must be an abstract class where the parameters and instrumentationContext are left unimplemented. The class must have an empty constructor which will be used to construct the factory object.EHS站长之家-易采站长站-Easck.Com

当前官方推荐使用的应该是这个类,这个类的底层实现就是基于gradle原生的Transform Action,这次的学习过程其实走了一点点弯路,一开始尝试的是Transform Action,但是貌似弯弯绕绕的,最后也没有成功,而且Transform Action的输入产物都是单一文件,修改也是针对单一文件的,所以貌似也不完全是一个很好的替换方案,之前文章介绍的那种复杂的asm操作则无法负荷了。EHS站长之家-易采站长站-Easck.Com

AsmClassVisitorFactory根据官方说法,编译速度会有提升,大概18%左右,这个下面我们会在使用阶段对其进行介绍的。EHS站长之家-易采站长站-Easck.Com

EHS站长之家-易采站长站-Easck.Com

我们先从AsmClassVisitorFactory这个抽象接口开始介绍起吧。EHS站长之家-易采站长站-Easck.Com

EHS站长之家-易采站长站-Easck.Com

AsmClassVisitorFactory

@Incubating
interface AsmClassVisitorFactory<ParametersT : InstrumentationParameters> : Serializable {
    /**
     * The parameters that will be instantiated, configured using the given config when registering
     * the visitor, and injected on instantiation.
     *
     * This field must be left unimplemented.
     */
    @get:Nested
    val parameters: Property<ParametersT>
    /**
     * Contains parameters to help instantiate the visitor objects.
     *
     * This field must be left unimplemented.
     */
    @get:Nested
    val instrumentationContext: InstrumentationContext
    /**
     * Creates a class visitor object that will visit a class with the given [classContext]. The
     * returned class visitor must delegate its calls to [nextClassVisitor].
     *
     * The given [classContext] contains static information about the classes before starting the
     * instrumentation process. Any changes in interfaces or superclasses for the class with the
     * given [classContext] or for any other class in its classpath by a previous visitor will
     * not be reflected in the [classContext] object.
     *
     * [classContext] can also be used to get the data for classes that are in the runtime classpath
     * of the class being visited.
     *
     * This method must handle asynchronous calls.
     *
     * @param classContext contains information about the class that will be instrumented by the
     *                     returned class visitor.
     * @param nextClassVisitor the [ClassVisitor] to which the created [ClassVisitor] must delegate
     *                         method calls.
     */
    fun createClassVisitor(
        classContext: ClassContext,
        nextClassVisitor: ClassVisitor
    ): ClassVisitor
    /**
     * Whether or not the factory wants to instrument the class with the given [classData].
     *
     * If returned true, [createClassVisitor] will be called and the returned class visitor will
     * visit the class.
     *
     * This method must handle asynchronous calls.
     */
    fun isInstrumentable(classData: ClassData): Boolean
}

简单的分析下这个接口,我们要做的就是在createClassVisitor这个方法中返回一个ClassVisitor,正常我们在构造ClassVisitor实例的时候是需要传入下一个ClassVisitor实例的,所以我们之后在new的时候传入nextClassVisitor就行了。EHS站长之家-易采站长站-Easck.Com

另外就是isInstrumentable,这个方法是判断当前类是否要进行扫描,因为如果所有类都要通过ClassVisitor进行扫描还是太耗时了,我们可以通过这个方法过滤掉很多我们不需要扫描的类。EHS站长之家-易采站长站-Easck.Com

@Incubating
interface ClassData {
    /**
     * Fully qualified name of the class.
     */
    val className: String
    /**
     * List of the annotations the class has.
     */
    val classAnnotations: List<String>
    /**
     * List of all the interfaces that this class or a superclass of this class implements.
     */
    val interfaces: List<String>
    /**
     * List of all the super classes that this class or a super class of this class extends.
     */
    val superClasses: List<String>
}

ClassData并不是asm的api,所以其中包含的内容相对来说比较少,但是应该也勉强够用了。这部分大家简单看看就行了,就不多做介绍了呢。EHS站长之家-易采站长站-Easck.Com

EHS站长之家-易采站长站-Easck.Com

新的Extension

AGP版本升级之后,应该是为了区分新旧版的Extension,所以在AppExtension的基础上,新增了一个AndroidComponentsExtension出来。EHS站长之家-易采站长站-Easck.Com

我们的transformClassesWith就需要注册在这个上面。这个需要考虑到变种,和之前的Transform还是有比较大的区别的,这样我们就可以基于不同的变种增加对应的适配工作了。EHS站长之家-易采站长站-Easck.Com

        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.onVariants { variant ->
            variant.transformClassesWith(PrivacyClassVisitorFactory::class.java,
                    InstrumentationScope.ALL) {}
            variant.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
        }

EHS站长之家-易采站长站-Easck.Com

实战

这次还是在之前的敏感权限api替换的字节码替换工具的基础上进行测试开发。EHS站长之家-易采站长站-Easck.Com

EHS站长之家-易采站长站-Easck.Com

ClassVisitor

看看我们正常是如何写一个简单的ClassVisitor的。EHS站长之家-易采站长站-Easck.Com

ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor methodFilterCV = new ClassFilterVisitor(classWriter);
ClassReader cr = new ClassReader(srcClass);
cr.accept(methodFilterCV, ClassReader.SKIP_DEBUG);
return classWriter.toByteArray();

首先我们会构造好一个空的ClassWriter,接着会构造一个ClassVisitor实例,然后传入这个ClassWriter。然后我们构造一个ClassReader实例,然后将byte数组传入,之后调用classReader.accept方法,之后我们就能在visitor中逐个访问数据了。EHS站长之家-易采站长站-Easck.Com

那么其实我们的类信息,方法啥的都是通过ClassReader读入的,然后由当前的ClassVisitor访问完之后交给我们最后一个ClassWriterEHS站长之家-易采站长站-Easck.Com

其中ClassWriter也是一个ClassVisitor对象,他复杂重新将修改过的类转化成byte数据。可以看得出来ClassVisitor就有一个非常简单的链表结构,之后逐层向下访问。EHS站长之家-易采站长站-Easck.Com

介绍完了这个哦,我们做个大胆的假设,如果我们这个ClassVisitor链表前插入几个不同的ClassVisitor,那么我们是不是就可以让asm修改逐个生效,然后也不需要多余的io操作了呢。这就是新的asm api 的设计思路了,也是我们这边大佬的字节码框架大佬的设计。另外bytex内的设计思路也是如此。EHS站长之家-易采站长站-Easck.Com

tips ClassNode 因为是先生成的语法树,所以和一般的ClassVisitor有点小区别,需要在visitEnd方法内调用accept(next)EHS站长之家-易采站长站-Easck.Com

EHS站长之家-易采站长站-Easck.Com

实际代码分析

接下来我们上实战咯。我将之前的代码套用到这次的逻辑上来。EHS站长之家-易采站长站-Easck.Com

demo地址EHS站长之家-易采站长站-Easck.Com

abstract class PrivacyClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {
    override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor {
        return PrivacyClassNode(nextClassVisitor)
    }
    override fun isInstrumentable(classData: ClassData): Boolean {
        return true
    }
}

我在isInstrumentable都返回的是true,其实我可以将扫描规则限定在特定包名内,这样就可以加快构建速度了。EHS站长之家-易采站长站-Easck.Com

class PrivacyClassNode(private val nextVisitor: ClassVisitor) : ClassNode(Opcodes.ASM5) {
    override fun visitEnd() {
        super.visitEnd()
        PrivacyHelper.whiteList.let {
            val result = it.firstOrNull { whiteName ->
                name.contains(whiteName, true)
            }
            result
        }.apply {
            if (this == null) {
                //   println("filter: $name")
            }
        }
        PrivacyHelper.whiteList.firstOrNull {
            name.contains(it, true)
        }?.apply {
            val iterator: Iterator<MethodNode> = methods.iterator()
            while (iterator.hasNext()) {
                val method = iterator.next()
                method.instructions?.iterator()?.forEach {
                    if (it is MethodInsnNode) {
                        it.isPrivacy()?.apply {
                            println("privacy transform classNodeName: ${name@this}")
                            it.opcode = code
                            it.owner = owner
                            it.name = name
                            it.desc = desc
                        }
                    }
                }
            }
        }
        accept(nextVisitor)
    }
}
private fun MethodInsnNode.isPrivacy(): PrivacyAsmEntity? {
    val pair = PrivacyHelper.privacyList.firstOrNull {
        val first = it.first
        first.owner == owner && first.code == opcode && first.name == name && first.desc == desc
    }
    return pair?.second
}

这部分比较简单,把逻辑抽象定义在类ClassNode内,然后在visitEnd方法的时候调用我之前说的accept(nextVisitor)方法。EHS站长之家-易采站长站-Easck.Com

另外就是注册逻辑了,和我前面介绍的内容基本都是一样的。EHS站长之家-易采站长站-Easck.Com

EHS站长之家-易采站长站-Easck.Com

个人观点

AsmClassVisitorFactory相比较于之前的Transform确实简化了非常非常多,我们不需要关心之前的增量更新等等逻辑,只要专注于asm>

其次就是因为减少了io操作,所以其速度自然也就比之前有所提升。同时因为基于的是Transform Action,所以整体性能还是非常ok的,那部分增量可以说是更简单了。EHS站长之家-易采站长站-Easck.Com

另外我也和我的同事大佬交流过哦,复杂的这种类似上篇文章介绍的,最好还是使用Gradle Task的形式进行修改。EHS站长之家-易采站长站-Easck.Com

以上就是Android开发AsmClassVisitorFactory使用详解的详细内容,更多关于Android开发AsmClassVisitorFactory的资料请关注易采站长站其它相关文章!EHS站长之家-易采站长站-Easck.Com

如有侵权,请联系QQ:279390809 电话:15144810328

相关文章

  • Android中RecyclerView实现多级折叠列表效果(TreeRecyclerView)

    Android中RecyclerView实现多级折叠列表效果(TreeRecyclerView)

    前言 首先不得不吐槽一下产品,尼玛为啥要搞这样的功能....搞个两级的不就好了嘛...自带控件,多好。三级,四级,听说还有六级的....这样丧心病狂的设计,后台也不好给数据吧。 先看
    2019-12-15
  • Android Viewpager实现无限循环轮播图

    Android Viewpager实现无限循环轮播图

    在网上找了很多viewpager实现图片轮播的,但是大多数通过以下方式在PagerAdapter的getCount()返回一个无限大的数,来实现 伪无限 @Override public int getCount() { return Integer.MAX_VALUE;//返回一个无限
    2019-12-22
  • AndroidStudio4.0 New Class的坑(小结)

    AndroidStudio4.0 New Class的坑(小结)

    AndroidStudio升级后, 新建类变成了下面这个样子, 并且默认还没有修饰符, 一堆大写的英文看的我一脸懵逼, 后来在 stackoverflow 上看到了这几个参数的含义: IMPORT BLOCK 说白了就是导包(谁家导
    2020-07-01
  • Android自定义View实现通讯录字母索引(仿微信通讯录)

    Android自定义View实现通讯录字母索引(仿微信通讯录)

    一、效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等。这里我截了一张美团选择城市的图片来看看; 我们今天就来实现
    2019-12-16
  • Android个人手机通讯录开发详解

    Android个人手机通讯录开发详解

    一、Android 个人手机通讯录开发 数据存储:SQLite 数据库 开发工具:Android Studio 二、Phone Module 简介 1. 界面展示 2. 文件结构简单分析 三、个人手机通讯录代码实现 1. 清单文件 (AndroidMa
    2019-12-24
  • Android10.0实现本地音乐播放(附源码下载)

    Android10.0实现本地音乐播放(附源码下载)

    1.概述 本篇文章仅是Android小白在写一个小程序,内容仅供参考,有很多不足之处希望各位大神指出,文章末尾有整个项目的下载,不需要币,只求帮你们解决到问题的同时收获到一颗小
    2020-06-23
  • Android使用VideoView播放本地视频和网络视频的方法

    Android使用VideoView播放本地视频和网络视频的方法

    1、效果展示 2、布局文件 ?xml version=1.0 encoding=utf-8?RelativeLayout xmlns:android=http://www.easck.com/apk/res/android xmlns:tools=http://www.easck.com/tools android:layout_width=match_parent android:layout_height=match_parent
    2019-12-13
  • Android实现简易计步器功能隔天步数清零查看历史运动纪录

    Android实现简易计步器功能隔天步数清零查看历史运动纪录

    最近需要用到计步功能,这可难坏我了,iOS端倒好,有自带的计步功能,让我惊讶的是连已爬楼层都给做好了,只需要调接口便可获得数据,我有一句MMP,我很想讲。 但是抱怨归抱怨,
    2019-12-21