编译时替换资源 - Android重叠包与资源合并一见
2016-12-12 16:16
399 查看
来源http://www.open-open.com/lib/view/open1455888339167.html
来自: http://blog.zhaiyifan.cn/2016/02/18/android-resource-overlay/
在 Android逆向分析(2)
APK的打包与安装 一文中对资源编译过程的介绍中,笔者提到了overlay(重叠包)这个概念,一位每天都被自己帅醒的好友看了那篇东西后,来问我这个重叠包究竟是个什么东西,笔者想了想,确实这个概念有很多同学们都不甚了解,搜索了一下网上了介绍,也几乎没有看到任何对这个的讲解,只有 老罗的博客 提到过
–mOverlay:表示当前正在编译的资源的重叠包。重叠包是什么概念呢?假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的资源。通过这种Overlay机制,我们就可以对资源进行定制,而又不失一般性。
那我们应该怎么怎么去使用重叠包呢?它又能用在什么地方,带来什么便利呢?
本文的测试源码已上传: ResourceOverlayDemo 。
我们看看aapt的命令help里是怎么描述的,省略版:
举个例子
假如我们有如上的aapt命令输入,那么当 src/com/example/res 与 src/com/example/ui/res 有相同资源的时候,就会使用前者的,这里对资源替换的粒度是resource而不是文件,比如两个文件夹的values/string.xml都有对同一个string id的描述,最后就会使用前者的字符串。
然后我们再来看看 --auto-add-overlay 有什么用,
假如我们在 src/com/example/ui/res 定义了资源string a,但是在 src/com/example/res 却没有这个string,那就会报错,因为基础包里是没有那个资源的,这时候就需要加上 --auto-add-overlay ,于是就会自动把新的资源都添加进去。
overlay大致就是这么一回事啦。
google的官方文档简直说了和没说一样。还是自己来吧,用AS的模板新建一个Testapp工程,随便建两个res文件夹,各放两个strings.xml,结构为:
res2和res3分别定义了一个string hehe ,value分别为 hehe res2 和 hehe res3 。
content_main.xml的TextView使用了 hehe (原来就是那个Hello World)。当然这里as会报错,因为res2和res3并没有标示为资源文件夹。
在module的build.gradle里:
然后我们试图编译:
看到 <add-resource> 这个,大概知道啥问题了…于是在 additionalParameters 最后又加上了 --auto-add-overlay ,成功编译运行。
在屏幕中央,显示了hehe res3,交换-S顺序后则变成了hehe res2,符合我们第一节中说到的,选择首个匹配原则。
不仅是string,anim,layout等等资源都可以使用重叠包来进行动态指定。
和aapt的overlay有关,但使用场景略有不同,也介绍一下。
Google在Android Tools Project Site专门为此开了一个页面: Resource
Merging(资源合并) 。
在过去的编译系统中,资源合并是通过传给aapt一个作为重叠包的资源文件夹列表来做的,再加上–auto-add-overlay来确保在重叠包里的新资源会被自动添加(默认行为只会重载既有资源)。
基础Gradle的编译系统的一个目标就是提供更大的灵活性,而另一个经常并问到的功能要求则是能拥有多个资源文件夹。aapt无法去处理这个,所以新的编译系统引进了一种新的超越aapt的合并机制,生成一个单独的,合并的,资源文件夹并提供给aapt。这个合并机制拥有增量的优点,既因为Gradle的输入/输出变更检测,又因为其实现方式(可以只使用唯一一个变更文件来做重新merge)。
合并的资源来自3种来源:
主资源,和main sourceSet相关联,大多位于src/main/res
Variant重叠包,来自Build Type和Flavor(s).
Library项目依赖,通过它们的aar bundle提供资源。
优先级为:BuildType -> Flavor -> main -> Dependencies.
这意味着如果一个资源同时在Build Type和main存在,会使用Build Type里的。
需要注意的是合并的scope,同样(类型,名字)的资源但标示符不同的,是分开处理的。
即如果src/main/res有:
res/layout/foo.xml
res/layout-land/foo.xml
而src/debug/res有:
res/layout/foo.xml
则合并后的资源文件夹会包含默认的来自src/debug/res的foo.xml,但横屏版本则会选择src/main/res下的。
PS: android的资源有19个维度,见 Grouping
Resource Types 的Table 2,这19个维度会唯一指定1个资源(qualifier标示符)。在老罗的资源介绍博客中曾经提到过18个维度,现在变成了19是因为多了Round screen这个维度,用于描述Android Wear,添加于API 23.
每个sourceSet可以定义多个资源文件夹,举个例子:
这种情况下,两个资源文件夹具有相同优先级,即如果一个资源在两个文件夹都声明了,合并会报错。
根据传递的依赖,Library项目的实际集被工程视为一个图,而不是平铺的列表,然后合并机制只会处理一个平优先级列表。
如果我们考虑如下例子的依赖关系:
项目 -> A, B (意味着A的优先级高于B)
A -> C, D
B -> C
则最后的优先级list为A, D, B, C,同时保证了A和B可以重载C。
继续在之前我们建立的工程的基础上做个小测试吧。在sourceSet加上res2文件夹,最后build.gradle的android域如下:
运行后发现界面显示了 hehe res2,符合预期,因为res2已经和res合并了,所以先找到了build/intermediates/res/merged/debug下的string,没有用res3的。
不同的buildType、渠道下的包,使用不同的资源,做一些定制,而不用侵入代码本身的逻辑。
我们了解了Android aapt overlay的机制,以及gradle下的资源合并,并分别编写运行了demo验证资源生效的结果。
https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/utils/README
http://blog.csdn.net/luoshengyang/article/details/8744683
http://tools.android.com/tech-docs/new-build-system/resource-merging
来自: http://blog.zhaiyifan.cn/2016/02/18/android-resource-overlay/
前言
在 Android逆向分析(2)APK的打包与安装 一文中对资源编译过程的介绍中,笔者提到了overlay(重叠包)这个概念,一位每天都被自己帅醒的好友看了那篇东西后,来问我这个重叠包究竟是个什么东西,笔者想了想,确实这个概念有很多同学们都不甚了解,搜索了一下网上了介绍,也几乎没有看到任何对这个的讲解,只有 老罗的博客 提到过
–mOverlay:表示当前正在编译的资源的重叠包。重叠包是什么概念呢?假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的资源。通过这种Overlay机制,我们就可以对资源进行定制,而又不失一般性。
那我们应该怎么怎么去使用重叠包呢?它又能用在什么地方,带来什么便利呢?
本文的测试源码已上传: ResourceOverlayDemo 。
aapt overlay
我们看看aapt的命令help里是怎么描述的,省略版:Usage: aapt l[ist] [-v] [-a] file.{zip,jar,apk} List contents of Zip-compatible archive. aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]] ... aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \ ... [--utf16] [--auto-add-overlay] \ ... [-S resource-sources [-S resource-sources ...]] \ [-F apk-file] [-J R-file-dir] \ ... Package the android resources. It will read assets and resources that are supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R options control which files are output. ... Modifiers: ... # 特别说明下,这就是前一篇我们说到的include的base set啦,比如android.jar -I add an existing package to base include set ... # overlay通过-S指定,可以指定多个目录, -S directory in which to find resources. Multiple directories will be scanned and the first match found (left to right) will take precedence. ... # 自动添加overlays包里的资源 --auto-add-overlay Automatically add resources that are only in overlays. ...
举个例子
aapt package \ -M AndroidManifest.xml \ -m -J gen \ -S src/com/example/res \ -S src/com/example/ui/res
假如我们有如上的aapt命令输入,那么当 src/com/example/res 与 src/com/example/ui/res 有相同资源的时候,就会使用前者的,这里对资源替换的粒度是resource而不是文件,比如两个文件夹的values/string.xml都有对同一个string id的描述,最后就会使用前者的字符串。
然后我们再来看看 --auto-add-overlay 有什么用,
假如我们在 src/com/example/ui/res 定义了资源string a,但是在 src/com/example/res 却没有这个string,那就会报错,因为基础包里是没有那个资源的,这时候就需要加上 --auto-add-overlay ,于是就会自动把新的资源都添加进去。
overlay大致就是这么一回事啦。
Gradle实践
aaptOptions
google的官方文档简直说了和没说一样。还是自己来吧,用AS的模板新建一个Testapp工程,随便建两个res文件夹,各放两个strings.xml,结构为:├── res │ ├── drawable │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ ├── menu │ │ └── menu_main.xml │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── res2 │ └── values │ └── strings.xml └── res3 └── values └── strings.xml
res2和res3分别定义了一个string hehe ,value分别为 hehe res2 和 hehe res3 。
content_main.xml的TextView使用了 hehe (原来就是那个Hello World)。当然这里as会报错,因为res2和res3并没有标示为资源文件夹。
在module的build.gradle里:
android { ... aaptOptions { additionalParameters '-S', '/Users/yifan/dev/github/Testapp/app/src/main/res3', '-S', '/Users/yifan/dev/github/Testapp/app/src/main/res2', '--auto-add-overlay' noCompress 'foo', 'bar' ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~' } ... }
然后我们试图编译:
All input files are considered out-of-date for incremental task ':app:processDebugResources'. Starting process 'command '/Users/yifan/dev/sdk/adt-bundle-mac-sdk/build-tools/23.0.2/aapt''. Working directory: /Users/yifan/dev/github/Testapp/app Command: /Users/yifan/dev/sdk/adt-bundle-mac-sdk/build-tools/23.0.2/aapt package -f --no-crunch -I /Users/yifan/dev/sdk/adt-bundle-mac-sdk/platforms/android-23/android.jar -M /Users/yifan/dev/github/Testapp/app/build/intermediates/manifests/full/debug/AndroidManifest.xml -S /Users/yifan/dev/github/Testapp/app/build/intermediates/res/merged/debug -A /Users/yifan/dev/github/Testapp/app/build/intermediates/assets/debug -m -J /Users/yifan/dev/github/Testapp/app/build/generated/source/r/debug -F /Users/yifan/dev/github/Testapp/app/build/intermediates/res/resources-debug.ap_ --debug-mode --custom-package cn.zhaiyifan.testapp -0 apk -S /Users/yifan/dev/github/Testapp/app/src/main/res2 --output-text-symbols /Users/yifan/dev/github/Testapp/app/build/intermediates/symbols/debug Successfully started process 'command '/Users/yifan/dev/sdk/adt-bundle-mac-sdk/build-tools/23.0.2/aapt'' /Users/yifan/dev/github/Testapp/app/build/intermediates/res/merged/debug/values-af/values-af.xml:3 : AAPT: Resource at abc_action_bar_home_description appears in overlay but not in the base package; use <add-resource> to add. ... ...各种类似报错 /usr/local/google/buildbot/src/googleplex-android/mnc-supportlib-release/frameworks/support/v7/appcompat/res/color/switch_thumb_material_light.xml:19 : AAPT: No resource found that matches the given name (at 'color' with value '@color/switch_thumb_normal_material_light'). :app:processDebugResources FAILED :app:processDebugResources (Thread[main,5,main]) completed. Took 10.493 secs.
看到 <add-resource> 这个,大概知道啥问题了…于是在 additionalParameters 最后又加上了 --auto-add-overlay ,成功编译运行。
在屏幕中央,显示了hehe res3,交换-S顺序后则变成了hehe res2,符合我们第一节中说到的,选择首个匹配原则。
不仅是string,anim,layout等等资源都可以使用重叠包来进行动态指定。
资源合并
和aapt的overlay有关,但使用场景略有不同,也介绍一下。Google在Android Tools Project Site专门为此开了一个页面: Resource
Merging(资源合并) 。
在过去的编译系统中,资源合并是通过传给aapt一个作为重叠包的资源文件夹列表来做的,再加上–auto-add-overlay来确保在重叠包里的新资源会被自动添加(默认行为只会重载既有资源)。
基础Gradle的编译系统的一个目标就是提供更大的灵活性,而另一个经常并问到的功能要求则是能拥有多个资源文件夹。aapt无法去处理这个,所以新的编译系统引进了一种新的超越aapt的合并机制,生成一个单独的,合并的,资源文件夹并提供给aapt。这个合并机制拥有增量的优点,既因为Gradle的输入/输出变更检测,又因为其实现方式(可以只使用唯一一个变更文件来做重新merge)。
合并的资源来自3种来源:
主资源,和main sourceSet相关联,大多位于src/main/res
Variant重叠包,来自Build Type和Flavor(s).
Library项目依赖,通过它们的aar bundle提供资源。
优先级
优先级为:BuildType -> Flavor -> main -> Dependencies.这意味着如果一个资源同时在Build Type和main存在,会使用Build Type里的。
需要注意的是合并的scope,同样(类型,名字)的资源但标示符不同的,是分开处理的。
即如果src/main/res有:
res/layout/foo.xml
res/layout-land/foo.xml
而src/debug/res有:
res/layout/foo.xml
则合并后的资源文件夹会包含默认的来自src/debug/res的foo.xml,但横屏版本则会选择src/main/res下的。
PS: android的资源有19个维度,见 Grouping
Resource Types 的Table 2,这19个维度会唯一指定1个资源(qualifier标示符)。在老罗的资源介绍博客中曾经提到过18个维度,现在变成了19是因为多了Round screen这个维度,用于描述Android Wear,添加于API 23.
处理多个资源文件夹
每个sourceSet可以定义多个资源文件夹,举个例子:android.sourceSets { main.res.srcDirs = ['src/main/res', 'src/main/res2'] }
这种情况下,两个资源文件夹具有相同优先级,即如果一个资源在两个文件夹都声明了,合并会报错。
Library依赖的优先级顺序
根据传递的依赖,Library项目的实际集被工程视为一个图,而不是平铺的列表,然后合并机制只会处理一个平优先级列表。如果我们考虑如下例子的依赖关系:
项目 -> A, B (意味着A的优先级高于B)
A -> C, D
B -> C
则最后的优先级list为A, D, B, C,同时保证了A和B可以重载C。
小测试
继续在之前我们建立的工程的基础上做个小测试吧。在sourceSet加上res2文件夹,最后build.gradle的android域如下:android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "cn.zhaiyifan.testapp" minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } aaptOptions { additionalParameters '-S', '/Users/yifan/dev/github/Testapp/app/src/main/res3', '-S', '/Users/yifan/dev/github/Testapp/app/src/main/res2', '--auto-add-overlay' noCompress 'foo', 'bar' ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~' } android.sourceSets { main.res.srcDirs = ['src/main/res', 'src/main/res2'] } }
运行后发现界面显示了 hehe res2,符合预期,因为res2已经和res合并了,所以先找到了build/intermediates/res/merged/debug下的string,没有用res3的。
使用场景
不同的buildType、渠道下的包,使用不同的资源,做一些定制,而不用侵入代码本身的逻辑。
总结
我们了解了Android aapt overlay的机制,以及gradle下的资源合并,并分别编写运行了demo验证资源生效的结果。
参考文献
https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/utils/READMEhttp://blog.csdn.net/luoshengyang/article/details/8744683
http://tools.android.com/tech-docs/new-build-system/resource-merging
相关文章推荐
- 编译时替换资源 - Android重叠包与资源合并一见
- Android 系统编译资源文件
- 如何反编译android的apk文件得到资源文件
- 编译android源码中的资源文件
- android中编译资源文件的相关问题
- 编译android源码中的资源文件
- 三星S3C6410(ARM1176)平台Android资源获取与编译(RedHat AS5)
- Android framework中修改或者添加资源无变化或编译不通过问题详解
- Android 重新编译frameworks/base/core/res资源文件
- Android添加资源文件编译,R文件未重新生成解决办法
- Android 4.1源码编译找不到资源文件解决办法
- android中编译资源文件的相关问题
- 反编译Android APK 源代码和资源文件详解
- 反编译Android APK 源代码和资源文件防止反编译详解
- Android 重新编译资源文件
- Android字符串资源编译错误的解决方法
- 如何忽略Android资源编译错误 Error: this attribute must be localized.
- Android 编译找不到资源文件的几种可能的原因
- 如何反编译android的apk文件得到资源文件
- android反编译之获得res下的xml配置文件及图片等资源