百度APP Android包体积优化实践系列文章的前三篇分别介绍了体积优化的整体方案、Dex行号优化和资源优化。和Dex行号优化一样,Dex注解优化也是针对Dex文件进行的优化,但是优化的内容却有所不同。Dex行号优化的对象是Dex文件中的DebugInfo字段,而注解优化则是通过去除Dex中的非必要注解来优化包体积。
注解是Java 5.0引入的注释机制,Java语言的类、方法、变量、参数和包都可以被注解标注。不同于普通注释,注解最终可以保留在字节码里,虚拟机可通过反射获取注解内容。我们分析了Dex中的不同注解类型和常见的几种注解,发现Dex中所有的编译时注解,大部分泛型与类关系信息注解是可以去掉的,同时不会对代码运行有影响,因此我们使用自研的字节码操作框架针对性的去掉了上述非必要的注解,并建立了注解优化自动化检测和加白机制,实现优化Dex体积的目的。
本文将详细描述Dex注解优化的内容,包括Dex注解类型、Dex注解格式、优化目标、优化方案以及Dex注解优化自动化检测和加白。
2.1 注解的生命周期分类
我们知道注解按生命周期来划分可分为3类:
2.2 Dex注解的可见性分类
如下图所示,按照注解的可见性,Dex中的注解又可以分为以下3类:
(1)编译时注解
其中 BUILD 对应 Java RetentionPolicy.SOURCE 和 RetentionPolicy.CLASS,表明在源文件中和class文件中存在的注解,在运行时是无效的。
(2)运行时注解
RUNTIME 对应 RetentionPolicy.RUNTIME。
(3)系统注解
SYSTEM表示仅供系统使用,与业务代码无直接关系。
在Dex中,用smali标识的注解格式如下所示:
.annotation [注解属性] <注解类名>
[注解字段 = 值]
.end annotation
如果注解的作用范围是类, .annotation 指令会直接定义在 smali 文件中,如果作用范围是方法或者字段,则会包含在方法或字段定义中。
我们具体反编译apk后,对于在源码中一个方法上的注解@SuppressLint("BanParcelableUsage"),查看smali中注解表现如下:
.annotation build Landroid/annotation/SuppressLint;
value = {
"BanParcelableUsage"
}
.end annotation
以上图为例,可以看出 build表明注解类型是编译时注解,Landroid/annotation/SuppressLint 表明注解的类型,而value的内容则表明注解的值是"BanParcelableUsage"。
我们分析了Dex中所有的注解,总结出几种可以优化的注解类型,如下图所示,包括所有的build注解,system注解中的泛型注解和四种类关系注解。具体说明如下:
△ 可以优化的注解(标黄部分)
4.1 build注解
正如官方文档里所写的,build类型注解仅作用于编译期,最终apk中无需保留。proguard规则 -keepattribute **Annotations**会将其保留到最终dex中,由于proguard规则可能是由三方库引入的,所以我们需要后置处理build注解。
4.2 system注解-泛型注解
描述泛型内容的注解,注解名为Ldalvik/annotation/Signature。每一处使用泛型的源码最终都会由编译器自动生成一个泛型注解,可存在于class、method、field。例如我们在一个类中定义了如下变量,由于jsonObjectList使用了泛型,因此Dex中会对该变量生成对应的泛型注解,如下所示:
public List<JSONObject> jsonObjectList = new ArrayList<>()
public List<JSONObject> jsonObjectList = new ArrayList<>()
同时系统也提供了如下接口来获取泛型信息,如果代码中不存在以下接口获取泛型信息,那么泛型注解就可以被优化。
java/lang/Class.getTypeParameters
java/lang/Class.getGenericSuperclass
java/lang/Class.getGenericInterfaces
java/lang/reflect/Field.getGenericType
java/lang/reflect/Method.getGenericReturnType
java/lang/reflect/Method.getTypeParameters
java/lang/reflect/Method.getGenericParameterTypes
java/lang/reflect/Method.getGenericExceptionTypes
java/lang/reflect/Constructor.getTypeParameters
java/lang/reflect/Constructor.getGenericParameterType
java/lang/reflect/Constructor.getGenericExceptionTypes
4.3 system注解—类关系注解
描述类关系的注解,仅存在于class,这类信息通常只能通过客户端(非系统)代码来间接获取。包括下面几种:
注解名 | 含义 |
---|---|
.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 = 0x1
name = "InnerClass"
.end annotation
同时系统也提供了如下接口来获取类关系信息,如果代码中不存在以下接口获取类关系信息,那么类关系注解就可以被优化。
com/google/gson/Gson.fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object
com/google/gson/Gson.fromJson(Lcom/google/gson/JsonElement;Ljava/lang/Class;)Ljava/lang/Object
com/google/gson/Gson.fromJson(Ljava/io/Reader;Ljava/lang/Class;)Ljava/lang/Object
Titan-Dex是百度开源的面向Android Dalvik(ART)字节码操作框架,可以在二进制格式下实现修改已有的类,或者动态生成新的类。由于Dex注解优化是直接对生成的Dex进行修改,因此选用了Titan-Dex来操作DexAnnotation。
我们自定义了一个task在默认的packaging task之前执行,首先遍历Dex中的所有类、方法、字段,扫描所有的DexAnnotation,当扫描到注解类型为build、或注解名为Sginature/MemberClasses/InnerClass/EnclosingClass/EnclosingMethod 时,移除该DexAnnotation。
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
}
同时,我们还定义了白名单机制,对于一些调用了上面的系统接口的情况会跳过注解优化,保留原有注解。
在上述Dex注解优化开发完成后,当时的接入步骤是首先扫描整个APK中相关的注解反射接口调用,然后根据扫描的结果去排查对应的业务场景,确认是否可以移除对应的注解。最后确认需要加白后,由业务手动加入白名单并提交。整个过程较为繁杂,过于滞后且依赖人工,导致整个注解优化方案接入成本过高,因此需要一套前置的注解自动化检测方案。对于这种问题,我们选择了基于Android Lint来检查注解反射接口调用的情况。我们自定义了三个Lint规则如下:
加入目前warning拦截流程,在提测/上车时拦截,能前置的发现问题。
对应方法添加@SuppressLint("${detector_name}"),提取抽象规则,或者给目标类添加@KeepAllDavilkAnnotation加白。
为了避免对问题场景逐个手动加白,我们抽象了一套加白规则并开发了一套Gradle插件来实现自动化加白,下面是抽象出的五种加白规则。其中子类加白规则优先于其他规则。每条规则使用#${type}做结尾。
规则格式:${父类名}#superclass
若声明规则 classA#superclass,则classA以及继承了classA的所有子类均保留注解。
备注:如果子类 signature 不为null,需解析后一并加入白名单。
常见场景:Gson TypeToken等
规则格式:${注解名}#annotation
若声明规则annotationA#annotation,则使用了@annotationA(类、方法、属性注解)的类均保留注解。
常见场景:使用Gson进行序列化/反序列化的类,常会使用@SerializedName
规则格式:${包名}.**#package
常见场景:三方sdk
规则格式:${类名}#classname
常见场景:暂时无法抽象规则的类。比如百度内开发的老jar包,无法通过包名进行区分
规则格式:${包含该匿名内部类的类名}#anonymous
匿名内部类的名字是由编译器分配的,我们无法提前得知它的全名。这个加白规则会将该匿名内部类平级的所有内部类都加入白名单。范围不可控,匹配成本也比较高,所以建议对这种使用方式进行改造,改为前4种规则可命中的方式
下面是百度App根据上述规则抽象出的一套白名单,同时我们通过Gradle插件实现了具体类白名单的自动生成。
com.baidu.searchbox.net.update.v2.AbstractCommandListener#superclass
com.google.gson.reflect.TypeToken#superclass
com.google.gson.annotations.SerializedName#annotation
com.google.gson.**#package
com.alipay.**#package
com.baidu.FinalDb#classname
...
在Gradle Transform阶段获取到所有的class文件,匹配到加白规则的class( 类、类成员中的泛型信息)则加入白名单。这样可以自动生成大部分的白名单类,只需要人工check和补充少量的白名单内容即可,减少了人工配置白名单的成本。
本文主要介绍了百度APP Dex注解优化方案,其中重点讲述了Dex注解优化的目标,详细方案,自动化检测和加白机制。经过百度App上线验证,减少了Dex体积约1.2M。感谢各位阅读至此,如有问题请不吝指正。
参考资料:
[1] Dalvik 可执行文件格式:https://source.android.com/docs/core/dalvik/dex-format?hl=zh-cn
[2] Android 注解:https://developer.android.com/studio/write/annotations?hl=zh-cn
[3] Titan-Dex字节码操作框架:https://github.com/baidu/titan-dex[4] gson源码:https://github.com/google/gson
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Cm1Ma-NdedbYUiPaqIfE4A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。