介绍#
官方文档中是这样介绍fat-aar的:
Gradle script that allows you to merge and embed dependencies in generted aar file
fat-aar是一个允许你合并和嵌入依赖关系到生成的aar文件的gradle脚本。
从fat-aar的用途来讲,它的主要工作就是将project中内部所依赖的aar下载到本地,然后一起发布aar到maven库,外部项目依赖aar时不用去下载project内部依赖的aar
fat-aar概览#
graph LR headtitle(fat-aar)-->titlechild1(介绍) headtitle-->titlechid2(用途) headtitle-->titlechild3(工作原理) headtitle-->titlechild4(关键流程) headtitle-->titlechild5(问题和坑)
aar结构介绍#
aar是Android Library Project的二进制文件包,文件的扩展名是aar,其实文件本身就是一个简单的Zip文件,解压后有以下几种类型,相信Android开发同学都不会陌生
- /AndroidManifest.xml(必须)
- /classes.jar(必须)
- /res/(必须)
- /R.txt(必须)
- /assets/(可选)
- /libs/*.jar(可选)
- /jni/
/*.so(可选) - /proguard.txt(可选)
- /lint.jar(可选)
备注:R.txt文件是aapt –output -text -symbols输出,aapt相关细节这里不再叙述
fat-aar解决思路:合并aar#
如上图所示,我们把依赖的外部aar和内部module(可以看成aar)输出的N个aar文件进行合并,这样原来A模块的调用者接入方式保持不变,而且在依赖A时不必再重新下载A内部依赖的其他aar,可以提供给外部项目使用而避免访问远程maven库的场景
参考上面aar包结构形式,fat-aar合并主要过程为:
- 合并Manifest
- 合并jar
- 合并res资源
- 合并R文件(最关键的一步)
- 合并assets
- 合并libs
- 合并jni
- 合并Proguard
什么场景下需要fat-aar?#
发布一个aar时,你可能需要它#
我们发布aar时,会带有一个aarName-x.x.x.pom
文件(aarName
是aar
的名字,x.x.x
是版本号),这个pom
文件里面定义了我们aar内部依赖的aar,外部project依赖该aar会在编译时去下载其内部依赖的aar
;对外发布的aar中如果有依赖内网maven库中的aar,会因无法连接内部maven库导致下载内部aar失败,最终编译失败。
所以在这里引入fat-aar解决方案,在对外发布aar时将其内部依赖的aar全部下载到本地,发布时一起打包进去,外部project在编译时不用去内部maven库下载
引用多个aar时,你可能需要它#
当我们引用到多个外部aar
如a
、b
、c
三个aar
的时候,需要对其统一管理,则需要建立一个lib-base.aar
,让base
合并a`、`b`、`c`的内容,这样我们只需要依赖`lib-base.aar`,就可以让主工程分离多
a、
b、
c`的依赖。
fat-aar接入流程#
Step 1#
下载fat-aar
文件到本地项目module
下,然后在build.gradle
中依赖fat-aar.gradle
1 | apply from: 'fat-aar.gradle' |
Step 2#
将需要下载的内部aar
或本地lib
的compile
替换成embedded
,embedded
是fat-arr
内部定义的一个属性
1 | dependencies { |
Step 3#
将embedded依赖的project在对外发布aar时从pop.xml文件中去掉,避免外部依赖时再次下载,参考fat-aar下面的 publish.gradle,当然也可以自己实现
fat-aar工作原理#
fat-aar
工作原理
fat-aar
主要思路就是合并aar
,根据aar
的文件结构,可划分为多个子任务
首先了解一下gradle构建流程,gradle执行主要分为三个过程:
- Initialization(初始化阶段):分析有哪些module将要被构建,为每个module创建对应的project实例,settings.gradle文件会被解析
- Configuration(配置阶段):处理所有模块的build脚本,处理依赖、属性等,配置执行完后,有一个很重要的回调afterEvaluate,表示所有的模块都配置完了,可以执行task了
- Execution(执行阶段):根据task链表来执行某一个特定的task,这个task所依赖的其他task都将会被提前执行
fat-aar就是在配置阶段完成进入afterEvaluate回调中,然后执行合并内部依赖aar的操作,我们在对外发布aar时需要定义Task uploadArchives(如何上传aar至maven请自行了解),而uploadArchives内部依赖assembleRelease执行结果
Step1寻找#
首先,根据定义的embedded
属性找出需要合并的aar
,并将aar
解压到相应目录下(注意gradle tools
版本影响,建议设置为2.2.3
)
1 | def dependencies = new ArrayList(configurations.embedded.resolvedConfiguration.firstLevelModuleDependencies) |
Step2插入#
如果存在embedded属性的依赖,则定义各个子task执行的顺序(注意gradle版本影响,建议gradle tools版本设置为2.2.3)
1 | if (dependencies.size() > 0) { |
想要理解fat-aar执行流程,首先需要知道dependsOn是什么,dependsOn用来指定Task之间的依赖关系,比如TaskA.depensOn TaskB表示TaskA在TaskB之后执行
与dependsOn相反的mustRunAfter,有兴趣的同学可以自行了解下
fat-aar执行过程依赖assembleRelease任务,定义了embedAssets、embedLibraryResources、embedJniLibs、embedManifests、embedProguard、generateRJava、embedJavaJars多个Task,而这些Task的执行顺序与assembleRelease内部执行顺序相关
先来了解一下assembleRelease的执行顺序:
1 | :preBuild |
注:上面代码基本上列举了Gradle构建Android项目时执行assembleRelease的所有Task,Lint、Test等非必需Task除外
根据上面assembleRelease的执行顺序,下面逐个分析fat-aar中各Task的执行顺序:
- embedAssets: 合并Assets文件,与Assets文件编译相关,必须在build的generateReleaseAssets之前执行
- embedLibraryResources: 合并Res资源文件,与Res资源编译相关必须在build的packageReleaseResource之前执行
- embedJniLibs: 合并jni so文件,在build转换JniLibs之后执行
- embedManifests: 合并Manifest文件,在processReleaseManifest之后执行
- embedProguard: 合并Proguard文件,在embedLibraryResources之前执行
- generateRJava: 生成R.java文件,在build编译Java文件之前执行
- embedJavaJars: 打包Jar文件,在build编译Java文件之后执行
综上画出下面的流程图,可以帮助我们更好的理解fat-aar的工作流程(各子Task的执行顺序)
fat-aar关键流程#
前面介绍了aar的结构以及fat-aar的工作原理,下面具体介绍几个Task
embedAssets#
用途:合并Assets文件,其实就是简单的将embedded依赖的assets路径直接添加到当前project的assets目录下
1 | task embedAssets << { |
embedLibraryResources#
用途:合并Res文件,通过getMergedInputResourceSets获取所有aar的res资源路径,然后添加到当前project的res资源路径
1 | task embedLibraryResources << { |
embedManifests#
用途:合并Manifest,因代码片段过长,这里不粘贴代码了,主要思路就是通过XmlDocument操作Manifest节点将所有aar的Manifest文件合并
embedProguard#
用途:合并Proguard,读取embedded依赖的aar中proguard混淆代码,直接追加在project的proguard后面
1 | task embedProguard << { |
embedJniLibs#
用途:合并jni中so文件,将embedded的aar中jni目录下所有文件拷贝到当前project的jni目录下
1 | task embedJniLibs << { |
generateRJava#
用途:根据aar的R.txt文件生成相对应的R文件,首先通过Manifest文件获取相应的包名,然后通过遍历embeddedAarDirs查找每个aar中是否存在R.txt文件,根据R.txt生成相应的R文件,所有的id指向project的id
1 | task generateRJava << { |
collectRClass#
用途:将generateRClass生成的R文件拷贝到’$build_dir/fat-aar/release/‘目录下
1 | task collectRClass << { |
embedJavaJars#
用途:将build_dir/fat-aar/release/
路径中R文件打包进同一个jar包,放在bundle_release_dir/libs/
目录下,在collecRClass后执行
1 | task embedRClass(type: org.gradle.jvm.tasks.Jar, dependsOn: collectRClass) { |
问题和坑#
generateRJava生成的R文件中id找不到#
修改generateRJava,在project生成R文件之后执行,可根据project的R文件来过滤aar中R.txt中的id(aar和project依赖的v7、v4版本不同),如果R.txt中的id在project的R.class文件中找不到,则过滤掉
1 | def rClassFile = file("$generated_rsrc_dir/com/didi/unified/pay/R.java") |
自定义style,找不到相对应的id#
修改generateRJava,自定义Style在R.txt中存在形式为style,但是在class文件引用中为styleable,可以直接将style改为styleable
1 | if (rTxt.exists()) { |
发布aar打包时忘记去掉pop.xml中embedded依赖的aar#
记得删除pop.xml中的依赖
依赖aar不全#
embeded依赖的aar内部可能还依赖其他aar,对外发布时需要将内部嵌套的aar全部embeded进来
gradle tools版本配置过高#
project目录下gradle tools版本配置为3.1.0时编译出错,建议使用2.2.3
distributionUrl版本过高#
建议配置distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip 参考issue
类或资源文件冲突#
fat-aar合并aar时注意不要把一些公共库合并进去(比如v7、v4),如果模块中有重复的依赖,fat-aar会报错提示你某些类或资源文件冲突,解决方案有:
- 打包aar时配置相关依赖transitive false
1 | compile ('com.example.internal:lib: x.x.x') { |
- 外部项目中忽略掉远程的依赖
1 | configurations { |
embedded属性不等同于compile#
fat-aar最好只用来合并aar使用,embedded属性不等同于compile,开发和调试模块时最好使用compile,打包时使用embedded(建议开发和发布两个分支,需要打包时发布分支合并开发分支代码)