目前 Android 并不支持 xml 文件中颜色与透明度分开定义,若是想用带透明度的颜色值,只能在 colors.xml 文件中定义一个新色值。好比,有一个颜色名字叫 N900,定义以下:android
<color name="N900">#1F2329</color>
复制代码
当我须要一个 50% 透明度 N900 的颜色时,只能本身定义再一个色值:git
<color name="N900_alpha_50">#7F1F2329</color>
复制代码
因而,colors.xml 内就出现不少不规则颜色,就像下面这样子:github
而且还会继续增长这些不规则颜色。当下次换颜色时,这些带透明度的颜色每个都须要更换,维护起来十分麻烦。而且,这些颜色目前所在的module已经打成aar,每次若是须要增长新的颜色,都须要从新打包aar上传,十分影响开发效率。缓存
为了解决上述问题,开发了ResKitPlugin 插件,在编译时期动态替换颜色,支持颜色与透明度分开定义。app
基本思路是在aapt最终打包前,替换资源编译后生成的文件,使 aapt 最后打包时使用的资源二进制文件内的相应的颜色值已经带上了透明度。先上一张图:性能
下面详细介绍:测试
build/intermediates/res/merged/{*flavor*}/{buildType}
目录下。build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml
内,这里面包括了定义的颜色资源。最开始,drawable_a.xml.flat 文件是由(代码-1)编译生成:gradle
<solid android:color="@color/N900" android:alpha="0.5" />
复制代码
经过咱们的处理,drawable_a.xml.flat 文件 变成了由(代码-2)编译生成:优化
<solid android:color="@color/reskit_tmp_color_N900_alpha_0_5" />
复制代码
对于硬编码的颜色,会直接进行以下转换:ui
<solid android:color="#1F2329" android:alpha="0.5" />
|
\|/
<solid android:color="#7F1F2329"/>
复制代码
经过这样的处理,咱们的颜色在运行时就拥有了透明度。下面介绍具体处理的步骤。
经过computeResourceSetList去获取到全部参与编译的资源文件,而后修改源码,编译生成新的.flat文件,并替换原来的.flat文件。分为如下几步:
build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml
内挑出全部的颜色定义并生成colors.xml,为后续根据id找颜色值提供基础。android:color ="#1F2329"
,则直接修改值便可。android:color="@color/lkui_N900"
,会生成一个新key,而后将其替换为新key,并把这个新key与颜色的对应关系存在一个Map里,待新key 所有生成后,统一将新key 与颜色的对应关系写入build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml
文件,参与后续编译。Map<String, Map<String, String>>
资源文件的父文的名字 + “/” + 资源文件的名字 : [ 原始文件全路径 : 处理了alpha 后的新文件的全路径 ]
举例:
drawable/aab.xml : [/Users/guoxiao/ResPluginDemo/app/src/main/res/drawable/aab.xml : /Users/guoxiao/ResPluginDemo/app/build/coloralpha/res/drawable/aab.xml]
复制代码
intermediates/res/merged/{*flavor*}/{buildType}
目录下,对于重名文件来讲,系统究竟使用了哪一个文件去参与编译的。这里使用的方法是:
intermediates/res/merged/{flavor}/{buildType}
目录下的同名文件作md5比较,比较结果相同的,说明找到了系统编译使用的文件intermediates/res/merged/{flavor}/{buildType}
目录下的文件的md5和上次一致,则直接使用上次的缓存的.flat文件进行替换咱们缓存了上次颜色处理获得的.flat文件,对于本次颜色处理:
目前的实现并非最初的方案,测试时,替换7个文件,耗时从30s ,到20s, 最终优化到如今的5s左右。下面介绍这期间经历的几个方案:
这种方式,一次连接耗时7s左右,并且为了连接,还须要作一些压缩与解压的文件操做,压缩所有.flat文件须要7秒多,反编译.ap_又须要7秒多,最终一次颜色处理下来,耗时30s左右。
这种方式,能够去除对系统生成的.ap_文件的修改,耗时20s左右。
前两种方案耗时,主要是进行连接和反编译,从而获得源码。因而思考,有没有可能不经过连接和反编译的方式来获得源码,最终有个方案3。
最终耗时5s 左右。
处理完成后,系统的processDebugResources就会使用咱们处理过的.flat文件。
mergeResource Task 若使用了 gradle 的构建缓存(运行该Task 会输出 FROM_CACHE) ,会缺失这个Task的中间产物,即merged.dir文件夹为空。
针对这种状况,咱们每次在MergeResource执行前判断是否有merged.dir,若没有,不让它走 FROM_CACHE。
具体作法是:临时生成一个资源文件,致使缓存失效,这样就会触发mergeResources走一遍,而后 在mergeResource以后删除咱们临时生成的资源文件。
因为咱们能够拿到参与编译的全部资源文件,也能够修改替换系统编译产生的文件。这两个能力,提供了巨大的想象空间,如:
文章做者:国霄(EE Lark Android 团队)
邀请优秀的人一块儿作有挑战的事儿!字节跳动效率工程团队研发职位招聘,想成为技术大牛的伙伴快点进来看!职位介绍