Aapt Overlay 资源重叠机制

欢迎大家加入QQ群一起讨论: 489873144(android格调小窝)
我的github地址:https://github.com/jeasonlzy

概念

假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的资源。通过这种Overlay机制,我们就可以对资源进行定制,而又不失一般性。

aapt overlay

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/main/res \
-S ./src/main/res2

假如我们有如上的aapt命令输入,那么当 src/main/res 与 src/main/res2 有相同资源的时候,就会使用前者的,这里对资源替换的粒度是resource而不是文件,比如两个文件夹的values/string.xml都有对同一个string id的描述,最后就会使用前者的字符串。

然后我们再来看看 –auto-add-overlay 有什么用,

假如我们在 src/main/res2 定义了资源string a,但是在 src/main/res 却没有这个string,那就会报错,因为基础包里是没有那个资源的,这时候就需要加上 –auto-add-overlay ,于是就会自动把新的资源都添加进去。

aaptOptions

在工程src目录下新建两个目录,src2和src3,结构如下

├── res
│   └── values
│       ├── strings.xml
├── res2
│   └── values
│       └── strings.xml
└── res3
    └── values
        └── strings.xml

res2和res3分别定义了一个string,id=test ,value分别为test1,test2,test3

然后在代码中使用getString(R.string.test)

这时as会报错,因为res2和res3并没有标示为资源文件夹。

在module的build.gradle里:

android {
  ...
  aaptOptions {
        additionalParameters '--auto-add-overlay',
                '-S', './src/main/res3',
                '-S', './src/main/res2'
        noCompress 'foo', 'bar'
        ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~'
    }
  ...
}

重新编译运行,成功,打印结果为test3,如果我们对上面的-S参数进行交换,打印结果就变成了test2,这就证明了资源重叠包的优先级是由左至右的

资源合并

在过去的编译系统中,资源合并是通过传给aapt一个作为重叠包的资源文件夹列表来做的,再加上–auto-add-overlay来确保在重叠包里的新资源会被自动添加(默认行为只会重载既有资源)。

基础Gradle的编译系统的一个目标就是提供更大的灵活性,而另一个经常并问到的功能要求则是能拥有多个资源文件夹。aapt无法去处理这个,所以新的编译系统引进了一种新的超越aapt的合并机制,生成一个单独的,合并的,资源文件夹并提供给aapt。这个合并机制拥有增量的优点,既因为Gradle的输入/输出变更检测,又因为其实现方式(可以只使用唯一一个变更文件来做重新merge)。

合并的资源来自3种来源:

  1. 主资源,和main sourceSet相关联,大多位于src/main/res
  2. Variant重叠包,来自Build Type和Flavor(s).
  3. Library项目依赖,通过它们的aar bundle提供资源。

以下大部分来自于文档:Add App Resources

When all resources from each source set or library are unique, they’re all added into the final APK. A resource is considered unique if it’s file name is unique within both its resource type directory and the resource qualifier (if defined).

If there exist two or more matching versions of the same resource, then only one version is included in the final APK. The build tools select which version to keep based on the following priority order (highest priority on the left):

优先级

build variant > build type > product flavor > main source set > library 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个维度http://blog.csdn.net/luoshengyang/article/details/8738877,现在变成了19是因为多了Round screen这个维度,用于描述Android Wear,添加于API 23.
https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/utils/README

处理多个资源文件夹

每个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 25
    buildToolsVersion "25.0.1"
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main.res.srcDirs = ['src/main/res', 'src/main/res2']
    }

    aaptOptions {
        additionalParameters '--auto-add-overlay',
                '-S', './src/main/res3',
                '-S', './src/main/res2'
        noCompress 'foo', 'bar'
        ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~'
    }
}

运行后发现界面显示了 test2,符合预期,因为res2已经和res合并了,所以先找到了
build/intermediates/res/merged/debug/values/values.xml下的string,没有用res3的。

可以执行 gradle assembleDebug -d > a.txt 命令将log输出到文件中,搜索overlay,发现以下log

合并后的资源文件在第一个 -S 已经能找到资源id,后续的 -S将不起作用

overlay的好处就是,不同的buildType、渠道下的包,使用不同的资源,做一些定制,而不用侵入代码本身的逻辑。
我们了解了Android aapt overlay的机制,以及gradle下的资源合并,并分别编写运行了demo验证资源生效的结果。

如果你觉得好,对你有过帮助,请给我一点打赏鼓励吧,一分也是爱呀!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值