前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android无引用类查找插件

Android无引用类查找插件

原创
作者头像
Jeffery
发布2020-06-07 18:49:22
1.6K0
发布2020-06-07 18:49:22
举报

在排查项目中的代码垃圾时,处理无引用类是最简单直接的,因为没有其他代码引用到它,直接删除也不会影响到项目。但靠人肉去检索项目中所有的类是否有引用又显得是重复低效的,所以在这里提供一个方案,做成gradle插件供大家参考。

原理

Gradle编译过程

App在编译时会经历多个步骤,但关键的会有:1、将所有Module的代码编译成.class文件,并存放在build目录里;2、将所有.class文件(包括项目工程、外部依赖、SDK)合并并制成.dex文件。其中,Android gradle为了让开发者可以对class做动态操作,提供了接口让开发者在dex之前自定义TransForm对class文件进行修改。当然,查找无引用类并不需要修改class,只是需要在这个时机上获取到所有Module编译后生成的.class文件。

代码语言:txt
复制
class UnusedClassPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.extensions.create(UnusedExtension.NAME, UnusedExtension)

        def android = project.extensions.getByType(AppExtension)
        def checker = new UnusedClassChecker(project)
        project.afterEvaluate {
            // 收集所有Module的build目录
            List<String> paths = new ArrayList<>()
            project.rootProject.subprojects { Project subProject ->
                paths.add(subProject.buildDir.absolutePath)
            }
            checker.setPaths(paths)
        }
        // 注册transform
        android.registerTransform(checker)
    }
}
解析class文件

有了所有Module编译后的.class文件后,得到allClasses集合,同时开始对每一个.class文件进行分析。分析.class文件时,可以使用一个非常好用的分析class文件的工具库javassist。引用后,只要将所有Module的编译目录加入到classpath后,通过类名即可以得到解析.class文件抽象后的CtClass对象,如下:

代码语言:txt
复制
ClassPool classPool = ClassPool.getDefault()
// 加入classpath
mProjectsBuildPath.each { buildPath ->
	classPool.appendClassPath(buildPath)
}
// 根据类型获取CtClass对象
// CtClass ctClass = classPool.get(className)
分析依赖

有了.class文件的CtClass对象后,就可以获取到该CtClass所依赖的所有class(class文件会记载),并将所有依赖的class信息记录在集合dependentClasses中。主要从class文件中的常量池、父类、实现接口、Field、Method中获取依赖类。

代码语言:txt
复制
CtClass ctClass = classPool.get(className)
ClassFile classFile = ctClass.getClassFile()
for (String dependent : classFile.getConstPool().getClassNames()) {
    if (dependent.startsWith('[') && dependent.endsWith(';')) {
        dependent = dependent.replaceAll("\\[", "")
        dependent = dependent.substring(1, dependent.length()-1)
    }
    // 排除自身
    if (!dependent.equals(replaceClassName)) {
        putIntoDependent(dependent, "importClass")
    }
}
// 找出同包名依赖
// 找出父类
String superClass = classFile.getSuperclass()
if (!"".equals(superClass) && superClass != null) {
    superClass = superClass.replace('.', File.separator)
    putIntoDependent(superClass, "superClass")
}
// 找出接口
String[] interfaces = classFile.getInterfaces()
if (interfaces != null) {
    for (String face : interfaces) {
        face = face.replace('.', File.separator)
        putIntoDependent(face, "interface")
    }
}
// 找出字段
List<FieldInfo> fieldInfoList = classFile.getFields()
if (fieldInfoList != null) {
    for (FieldInfo fieldInfo : fieldInfoList) {
        try {
            String descriptor = fieldInfo.getDescriptor()
            descriptor = descriptor.replaceAll("\\[", "")
            if (descriptor.length() > 3) {
                descriptor = descriptor.substring(1, descriptor.length()-1)
                descriptor = descriptor.replace('.', File.separator)
                putIntoDependent(descriptor, "field")
            }
        } catch(Throwable t) {
            t.printStackTrace()
        }
    }
}
// 找出方法声明
List<MethodInfo> methodInfoList = classFile.getMethods();
if (methodInfoList != null) {
    for (MethodInfo methodInfo : methodInfoList) {
        String descriptor = methodInfo.getDescriptor()
        String reg = "(L.+?;)"
        Pattern pattern = Pattern.compile(reg)
        Matcher matcher = pattern.matcher(descriptor)
        while (matcher.find()) {
            String methodClassMember = matcher.group()
            methodClassMember = methodClassMember.substring(1, methodClassMember.length() - 1)
            methodClassMember = methodClassMember.replace('.', File.separator)
            putIntoDependent(methodClassMember, "method")
        }
    }
}

// 加入依赖集合
private boolean putIntoDependent(String className, String tag) {
	// 
    if (!className.contains("\$") && !dependentClasses.contains(className)) {
        dependentClasses.add((className))
    }
}
找出无引用类

经过上述步骤后,得到两个集合allClasses所有类、dependentClasses所有有被依赖的类。此时,只需要遍历一下allClasses,若某些类不在dependentClasses上则说明该类有可能是无引用的,所以在得到扫描结果后,需要检查下类是否真的无引用。为什么是可能呢?因为:

  1. 某些类可能只有在xml里有引用(如AndroidManifest、layout资源等),只通过class分析没有找出xml的引用;
  2. 只用作基本类型常量使用的类,编译时不会把class给import进去。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原理
    • Gradle编译过程
      • 解析class文件
        • 分析依赖
          • 找出无引用类
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档