百度APP Android包体积优化实践(四)Dex注解优化
GEEK TALK
01
前言
百度APP Android包体积优化实践系列文章回顾:
百度APP Android包体积优化实践(二)Dex行号优化
GEEK TALK
02
Dex注解类型
2.1 注解的生命周期分类
RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。 RetentionPolicy.CLASS:注解被保留到class文件,但JVM加载class文件时候被遗弃,这是默认的生命周期。 RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,JVM加载class文件之后仍然存在。
2.2 Dex注解的可见性分类
如下图所示,按照注解的可见性,Dex中的注解又可以分为以下3类:
(1)编译时注解
其中 BUILD 对应 Java RetentionPolicy.SOURCE 和 RetentionPolicy.CLASS,表明在源文件中和class文件中存在的注解,在运行时是无效的。
(2)运行时注解
RUNTIME 对应 RetentionPolicy.RUNTIME。
(3)系统注解
SYSTEM表示仅供系统使用,与业务代码无直接关系。
03
Dex注解格式
.annotation [注解属性] <注解类名>[注解字段 = 值].end annotation
如果注解的作用范围是类, .annotation 指令会直接定义在 smali 文件中,如果作用范围是方法或者字段,则会包含在方法或字段定义中。
我们具体反编译apk后,对于在源码中一个方法上的注解@SuppressLint("BanParcelableUsage"),查看smali中注解表现如下:
.annotation build Landroid/annotation/SuppressLint;value = {"BanParcelableUsage"}.end annotation
以上图为例,可以看出 build表明注解类型是编译时注解,Landroid/annotation/SuppressLint 表明注解的类型,而value的内容则表明注解的值是"BanParcelableUsage"。
GEEK TALK
04
优化目标
△可以优化的注解(标黄部分)
4.1 build注解
4.2 system注解-泛型注解
public List<JSONObject> jsonObjectList = new ArrayList<>().field public jsonObjectList:Ljava/util/List;.annotation system Ldalvik/annotation/Signature;value = {"Ljava/util/List<","Lorg/json/JSONObject;",">;"}.end annotation.end field
java/lang/Class.getTypeParametersjava/lang/Class.getGenericSuperclassjava/lang/Class.getGenericInterfacesjava/lang/reflect/Field.getGenericTypejava/lang/reflect/Method.getGenericReturnTypejava/lang/reflect/Method.getTypeParametersjava/lang/reflect/Method.getGenericParameterTypesjava/lang/reflect/Method.getGenericExceptionTypesjava/lang/reflect/Constructor.getTypeParametersjava/lang/reflect/Constructor.getGenericParameterTypejava/lang/reflect/Constructor.getGenericExceptionTypes
4.3 system注解—类关系注解
注解名 | 含义 |
.annotation system Ldalvik/annotation/MemberClasses | 内部类列表 |
.annotation system Ldalvik/annotation/InnerClass | 内部类自身的信息,与EnclosingClass或EnclosingMethod共同存在 |
.annotation system Ldalvik/annotation/EnclosingClass | 声明该内部类的地方为类,与EnclosingMethod互斥 |
.annotation system Ldalvik/annotation/EnclosingMethod | 声明该内部类的地方为方法,与EnclosingMethod互斥 |
例如,有一个如下结构的类OuterClass,包含着一个InnerClass的内部类。
public class OuterClass {public String a;public class InnerClass{public String b;}}
我们查看OuterClass类的smali文件,可以看到有MemberClasses注解标识了内部类InnerClass。
.class public Lcom/baidu/searchbox/OuterClass;.super Ljava/lang/Object;.source "OuterClass.java"# annotations.annotation system Ldalvik/annotation/MemberClasses;value = {Lcom/baidu/searchbox/OuterClass$InnerClass;}.end annotation...
我们查看InnerClass类的smali文件,可以看到有InnerClass注解标识了自身的内部类信息,同时EnclosingClass表明了声明该InnerClass的地方是OuterClass类。
.class public Lcom/baidu/searchbox/OuterClass$InnerClass;.super Ljava/lang/Object;.source "OuterClass.java"# annotations.annotation system Ldalvik/annotation/EnclosingClass;value = Lcom/baidu/searchbox/OuterClass;.end annotation.annotation system Ldalvik/annotation/InnerClass;accessFlags = 0x1name = "InnerClass".end annotation
同时系统也提供了如下接口来获取类关系信息,如果代码中不存在以下接口获取类关系信息,那么类关系注解就可以被优化。
com/google/gson/Gson.fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Objectcom/google/gson/Gson.fromJson(Lcom/google/gson/JsonElement;Ljava/lang/Class;)Ljava/lang/Objectcom/google/gson/Gson.fromJson(Ljava/io/Reader;Ljava/lang/Class;)Ljava/lang/Object
05
优化方案
override fun visitClass(dcn: DexClassNode) {val outDexClassNode = DexClassNode(dcn.type, dcn.accessFlags, dcn.superType, dcn.interfaces)outDexClassPoolNode.addClass(outDexClassNode)MarkedMultiDexSplitter.setDexIdForClassNode(outDexClassNode, dexId)//遍历该Dex下面的所有类dcn.accept(object : DexClassVisitor(outDexClassNode.asVisitor()) {override fun visitAnnotation(annotationInfo: DexAnnotationVisitorInfo):DexAnnotationVisitor? {//检查类注解是否匹配删除规则return if (removeAnnotation(annotationInfo, dcn.type.toTypeDescriptor())) {null} else super.visitAnnotation(annotationInfo)}override fun visitMethod(methodInfo: DexMethodVisitorInfo?): DexMethodVisitor {val superMethodVisitor = super.visitMethod(methodInfo)return object : DexMethodVisitor(superMethodVisitor) {override fun visitAnnotation(annotationInfo: DexAnnotationVisitorInfo):DexAnnotationVisitor? {//检查方法注解是否匹配删除规则return if (removeAnnotation(annotationInfo, dcn.type.toTypeDescriptor())) {null} else super.visitAnnotation(annotationInfo)}override fun visitParameterAnnotation(parameter: Int, annotationInfo:DexAnnotationVisitorInfo): DexAnnotationVisitor? {//检查方法参数的注解是否匹配删除规则return if (removeAnnotation(annotationInfo, dcn.type.toTypeDescriptor())) {null} else super.visitParameterAnnotation(parameter, annotationInfo)}}}override fun visitField(fieldInfo: DexFieldVisitorInfo?): DexFieldVisitor {val superFiledVisitor = super.visitField(fieldInfo)return object : DexFieldVisitor(superFiledVisitor) {override fun visitAnnotation(annotationInfo: DexAnnotationVisitorInfo):DexAnnotationVisitor? {//检查类变量的注解是否匹配删除规则return if (removeAnnotation(annotationInfo, dcn.type.toTypeDescriptor())) {null} else super.visitAnnotation(annotationInfo)}}}})}
/*** 删除不必要的注解** @param annotationInfo* @param classType* @return Boolean*/private fun removeAnnotation(annotationInfo: DexAnnotationVisitorInfo,classType: String): Boolean {// build类型注解优化,仅根据配置开关决定if (annotationInfo.visibility.name == ANNOTATION_TYPE_BUILD && optBuild) {return true}// system类型注解优化,根据开关与白名单决定if (!optSystem) {return false}when (annotationInfo.type.toTypeDescriptor()) {ANNOTATION_SIGNATURE,ANNOTATION_INNERCLASS,ANNOTATION_ENCLOSINGMETHOD,ANNOTATION_ENCLOSINGCLASS,ANNOTATION_MEMBERCLASS ->if (classType !in whiteListSet) {LogUtil.log("current classType", classType)LogUtil.log("current annotationInfo.type", annotationInfo.type.toTypeDescriptor())LogUtil.log("系统注解", "需要删除")return true}}return false}
GEEK TALK
06
自动化检测和加白
1、自定义lint规则
ClassShipUseDetector:扫描类关系接口调用。
SignatureUseDetector:扫描泛型注解接口调用。
EncapsulationDetector:扫描Gson.fromJson封装,如果fromJson方法封装后,工具没办法确认目标Bean类,需要封装方自行添加白名单。
2、扫描触发流程
3、豁免方法
4、自动化加白
子类加白
注解加白
整包加白
普通类加白
匿名内部类加白
com.baidu.searchbox.net.update.v2.AbstractCommandListener#superclasscom.google.gson.reflect.TypeToken#superclasscom.google.gson.annotations.SerializedName#annotationcom.google.gson.**#packagecom.alipay.**#packagecom.baidu.FinalDb#classname...
GEEK TALK
07
总结
END
[1] Dalvik 可执行文件格式:https://source.android.com/docs/core/dalvik/dex-format?hl=zh-cn