乐趣区

关于前端:Android包体积优化上篇-资源混淆优化

导读 :什么时候进行包体积优化?个别在 app 初创期时,因为业务代码较少,包体积也不大,相应这个时候对包体积的优化收益也较少。当业务逐步成熟性能,迭代逐步变多,包体积也会逐步减少。

减少包体积次要影响如下几个方面:

1: 如果针对 app,会影响到下载转化率,但手百外部直播属于插件,所以不存在转化率的问题,存在插件的下载时长问题,进而在肯定水平上影响插件冷启首屏;

2: 渠道商单干的要求,这个要求只在 app 外面,如果是插件不受其影响,app 的体积越大,渠道商的资费会更高,如要求更大的 ROM 空间,更长的安装时间,这些都极大影响手机曾经用户体验。

3: 同时更大的包体积,不论是插件还是 app,也意味着更长的安装时间,更长的 oat 工夫(Android 零碎 9 之前,装置会主动转换为 oat)。

本文章依赖的知识点有 Gradle 插件编译流程、Gradle 插件编写、Groovy、Android 打包流程、Android AAPT 流程等。

全文 4221 字,预计浏览工夫 12 分钟。

一、apk 包的组成

Android 安装包是由很多局部组成的,次要有如下:

  1. 代码相干:class.dex
  2. 动静库(SO)相干:lib
  3. 资源相干 resource.arsc res/, assets
  4. META-INF 寄存签名等信息

通过以上包体积的组成部分,不难剖析,要想管制 apk 的包体积,须要在 dex、动静库、资源数据上进行管制。动静库(SO)可通过动静下载、按需下载的形式;其次只有 dex 和资源,dex 是咱们下一篇外面要讲的货色,本片次要讲资源的优化。

二、资源优化

针对图片资源,应用 Android Size Analyzer 可生成资源的排序列表,通过剖析后生成后果,可对剖析后的图片采取优化伎俩,如图片压缩、云控资源、webp、矢量图、移除冗余资源等,以上所有这些都是对于单张图片的解决,本篇次要谈资源的混同优化,整个资源混同的实现是基于 AndroidResGuard 做一个二次开发扩大。

为什么要做二次开发?整个直播是基于插件实现,原工具不反对插件资源混同;大家晓得为了解决插件与宿主之间资源 id 抵触的问题,插件都须要固定器 resid 的 package 局部,如下资源 id 的组成:

[<package_name>.]R.<resource_type>.<resource_name>
针对插件 //
additionalParameters "--package-id", "0x61", "--allow-reserved-package-id"

简略介绍下资源映射表协定的原理,资源混同其原理非常简单:通过如下的映射关系,将简单的字符串,转换为一个简短的字符串,进而减小资源映射表的字符串常量池的大小。

如下图为混同前和混同后比照,一旦退出 packageid,整个资源混同会出错,通过对资源映射表二进制以及协定的比照发现,是因为 arsc 在进行扩大 packageid 的时候,与协定不合乎导致,问题定位当前,即找到适合的解决办法,即扩大 arsc 资源映射表协定。

通过上图能够看出混同后,资源映射表体积大幅度缩小。

三、资源映射表协定

从上图中剖析定位一个资源首先通过资源类型,而后通过资源 id,通过资源 id(ID),找到资源名(name),找到后依据以后机器的 mdpi/land/ ldpi,加载不同的文件门路下的资源。

resources.arsc 是以一个个 Chunk 块的模式组织的,Chunk 的头部信息记录了这个 Chunk 的类型、长度等数据。

从整体上来看,其构造为资源索引表头部 + 字符串资源池 + N 个 Package 数据块。

001Editor 下的资源映射表构造如下:

四、施行

计划的施行蕴含三局部:计划的设计实现、QA 流水线配置、单功能版本灰度。

五、方案设计实现

因为是在原有工程上做二次开发扩大,须要对原有我的项目的构造有一个清晰的意识,包含到每一个细节,其中包含资源混同规定、白名单怎么映射,从中理出咱们的需要局部,包含资源白名单映射、夜间模式、映射表批改、映射表重写,整体代码构造如下图,黄标处为须要二次开发波及的模块。

如上图总共波及到 3 局部的外围工作,包含配置模块、插件模块、与外围的映射表混同模块;

  • a: 配置模块配置如下参数:

mappingFile = file("./resource_mapping.txt")
    use7zip = false
    useSign = false
    // 关上这个开关,会 keep 住所有资源的原始门路,只混同资源的名字
    keepRoot = true
 
    // 设置这个值,会把 arsc name 列混同成雷同的名字,缩小 string 常量池的大小
//    fixedResName = "arg"
    // 关上这个开关会合并所有哈希值雷同的资源,但请不要适度依赖这个性能去除去冗余资源
    mergeDuplicatedRes = true
 
 
    nightmode_pattern = "_1" // 夜间模式配置
 
    // 根底的 sdk 都退出白名单, 配合白名单配置文件
    whiteList = []

动静配置参数,这个是针对插件特有的 fixid,生成白名单配置列表混同资源不能将其混同,在模块外部数据筹备阶段,要将其偷传到 Gradle 插件内

// 生成 fix 文件白名单
def generateWhiteList() {def fixFile = file("host-res-fix/${rootProject.ext.plugin_fix_file_map[host]}")
    def resGuard = file("resguard/res_guard_config.xml")
    def resproguard = new XmlParser().parse(resGuard)
    def issueNode = new NodeBuilder().issue(id: 'whitelist', isactive: true)
    resproguard.issue[0].replaceNode (issueNode)
    fixFile.eachLine { line ->
// TODO 白名单行生成
    }
new XmlNodePrinter(new PrintWriter(resGuard)).print(resproguard)
return resGuard
}
  • b:资源映射表读写:

资源映射表读取的目标是,造成根本的 arsc 框架,架构造成后,通过映射逻辑将内存数据从新写回到 arsc, 可如下图构造:

扩大协定局部是反对插件资源混同的外围,根本规定就是依照扩大协定进行程序读写即可。

代码片段如下:

private void writeLibraryType() throws AndrolibException, IOException {  checkChunkType(Header.TYPE_LIBRARY);int libraryCount = mIn.readInt();  mOut.writeInt(libraryCount);for (int i = 0; i < libraryCount; i++) {mOut.writeInt(mIn.readInt());/*packageId*/    mOut.writeBytes(mIn, 256); /*packageName*/  }  writeNextChunk(0);while (mHeader.type == Header.TYPE_TYPE) {writeTableTypeSpec();  }}private void readLibraryType() throws AndrolibException, IOException {  checkChunkType(Header.TYPE_LIBRARY);int libraryCount = mIn.readInt();int packageId;  String packageName;for (int i = 0; i < libraryCount; i++) {packageId = mIn.readInt();    packageName = mIn.readNullEndedString(128, true);    System.out.printf("Decoding Shared Library (%s), pkgId: %d\n", packageName, packageId);  }while (nextChunk().type == Header.TYPE_TYPE) {readTableTypeSpec();  }}

### 六、流水线配置

基于曾经生成的 apk,做 apk 批改;明确定义输入输出

`
./gradlew :app:resguardUseApk -PHOST=${assemble_host} -PMODE=debug
`

因为是批改 apk 外部文件构造影响面较大,版本公布采纳单灰验证,通过单灰验证优化后的 apk 性能稳固,单功能版本灰度 & 收益。

体积优化收益 8%,当初在多插件应用,包含主播端、开播端,下一步会在其余插件推广。

### 七、注意事项

通过 resouce name 获取资源的形式须要退出到白名单,体现模式如在插件中通过名字取宿主资源, 倡议通过 lint 全局扫描 getIdentifier,应用

`
fixedResId = context.getApplicationContext().getResources()
.getIdentifier(resName, null, null);

uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + “://”
+ resources.getResourcePackageName(model) + ‘/’
+ resources.getResourceTypeName(model) + ‘/’
+ resources.getResourceEntryName(model));
`

### 八、相干链接

https://developer.android.com…

https://github.com/shwenzhang…

### 九、举荐浏览:

百度 APP 视频播放中的解码优化

百度爱番番实时 CDP 建设实际

当技术重构遇上 DDD,如何实现业务、技术双赢?

接口文档主动更改?百度程序员开发效率 MAX 的秘诀

技术揭秘!百度搜寻中台低代码的摸索与实际

百度智能云实战——动态文件 CDN 减速

化繁为简 – 百度智能小程序主数据架构实战总结

———- END ———-

百度 Geek 说

百度官网技术公众号上线啦!

技术干货 · 行业资讯 · 线上沙龙 · 行业大会

招聘信息 · 内推信息 · 技术书籍 · 百度周边

退出移动版