欢迎光临
专注android技术,聚焦行业精粹,我们一直在努力

fat-aar 实现分析

1、fat-aar介绍

简单的说他可以把一个android library工程所依赖的module(包含远程依赖)的内容(包括:jar, assets, res,jni, manifest等)在打包时都包含进来,最终生成一个完整的aar,可以单独提供出去使用。

fat-aar非常方便,尤其当你所依赖的库不公开,或者对方无法访问你的传递依赖库时,又或者对方不会使用gradle依赖,只会拷贝资源到eclipse工程中时非常有用。

注意:fat-aar使用方面会有一些限制,fat-aar只会处理embedded关键字指向的这层一级依赖,而它的再下一层的依赖则不会处理。对于这个问题fat-aar也提供了一个publish.gradle来处理,也就是说最终你发布的aar虽然是单独的一个,但是随之也会有一个pom文件告诉接入方还有一些子依赖。所以接入方还得用gradle方式去接入,以保证这些子依赖可以完整的下载下来。

具体使用很简单,参考github的原仓库介绍:链接

步骤如下:

第一步:导入fat-aar.gradle

有两种方式:

1.可以拷贝fat-aar.gradle到你的library工程目录,在你的build.gradle里面导入即可:

fat-aar的下载地址:链接 ,或者到上面的github原项目直接下载

2. 也可以通过url来导入fat-aar

第二步: 定义需要合并的 embedded 依赖

你需要修改build.gradle里面的dependencies块,把你想要合并进来的aar依赖前面的compile字段改为embedded。修改的结果例如下面这样:

依赖的前面有embedded 将会被合并进来,其它的会保持gradle原来的依赖方式。

第三步: 同一个工程依赖这个embedded工程需要移除传递依赖

经过上面两步你已经将子工程embedded到了你的主library工程了。但你会发现的你的application类型的工程在依赖(compile)你的这个工程时会提示类重复错误,原因是在类型为application的工程compile你的library时也会把该library的传递依赖也包含进来,此时你的library如果使用了fat-aar,那么实际你的library也包含了这些传递依赖,所以会出现类重复。你需要确保其它application类型的工程在依赖你已经使用embedded的library工程(简单称为:fat-library)时不开启传递依赖(transitive)。

如果你再同一个工程里面使用了这个fat-library,你需要简单的定义你的fat-library工程的依赖为non transitive:

3、fat-aar实现分析

按照上面的步骤我们创建一个fat-library并embedded两个aar工程:library-one和library-two,build.gradle的dependencies配置如下:

3.1、fat-aar的配置阶段流程分析

fat-aar的配置流程是在gradle的afterEvaluate里面实现的,以下是对afterEvaluate的介绍:

在所有build.gradle解析完成后,开始执行task之前,此时所有的脚本已经解析完成,task,plugins等所有信息可以获取,task的依赖关系也已经生成,如果此时需要做一些事情,可以写在afterEvaluate

fat-aar在这里做的事情主要包括2件事情:

  • 将所有embedded的aar里面的artifact(包括:aar或jar)文件都放在正确的目录,并记录它们的位置
  • 对fat-aar自定义的task配置好dependsOn,也就是依赖的先后顺序

接下来详细的介绍afterEvaluate里面的每个流程点:

 

1. 获取embedded的所有直接依赖

这里使用的是这样的代码:

其中embedded是自定义的一种configuration, 然后通过dependenciesembedded配置的依赖 compile进来,具体实现如下:

firstLevelModuleDependencies的官方解释如下:

Returns the {@link ResolvedDependency} instances for each direct dependency of the configuration. Via those
you have access to all {@link ResolvedDependency} instances, including the transitive dependencies of the
configuration.

返回configuration对应的直接依赖。通过这些直接依赖你可以访问所有的依赖实例,包括configuration的传递依赖

2. 获取每一个依赖的aar artifact路径

注意这里会由于工程的配置不同而导致最终生成的aar包经常出现以下两个问题:

问题1:如果你的gradleApiVersion >=2.3 , fat-aar默认会在build/intermediates/bundles/default目录下面去找,但是我们工程的目录bundles下面只有release,该怎么解决?:

问题2:aarPath是否可以修改?我想改一下这些临时文件的地方,方便clean时统一清除

问题1分析:我理解fat-aar设计的初衷只针对release环境的library 工程,而在gradleApiVersion >=2.3时,gradle构建时多了一个default目录的概念,而此版本之前都是release,debug等。而fat-aar代码中写死了2.3用default:

这样的话如果library工程使用了publishNonDefault 并设置为true,则构建时不再是default目录,而是release目录了

publishNonDefault配置的作用: 是否关闭默认发布配置 (true关闭, false开启)
因此最先想到的是: 关闭默认发布配置, 即将publishNonDefault设为true (言外之意, library的buildType会和主module的buildType保持一致了).

解决办法:修改bundle_release_dir和aarPath变量的default为release,具体如下图:

 

问题2分析:aarpath不要修改它的路径(问题1提出的修改除外),我们讲一下它的作用,然后你就明白怎么正确的使用它了

aarPath的生成规则:aarPath是fat-aar 中每一个dependency的artifact所在的目录,最终fat-aar打aar时会它收集的所有aarPath(embeddedAarDirs)里面的资源都合并起来。这些aarPath 如果是本地project,它会按照下面的方式拼接找到它的aarPath:

问题2隐藏的风险:有时你发现aarPath下面找不到artifact资源,这个是什么情况呢? 很多情况下你的project里面的目录名称和moduleName不是一致的,例如你改了moduleName让它更容易理解,那么此时fat-aar会仍然根据moduleName去找artifact目录(因为fat-aar认为moduleName即project目录名称),那么此时就找不到了。但如果你了解了aarPath的生成规则,你就不难发现此问题了。

 

3. 获取当前gradle版本对应的aar文件路径

依赖的aar路径随着gradle的版本不同而不同:

得到的aarPath是该gradle版本下依赖的aar资源的存放路径,注意当gradle版本>=2.3时默认aar会下载到本地C盘的.gradle/caches目录,这里做了一步操作就是定义了一个新的目录赋值给aarPath,接下来会把C盘目录下面下载的aar资源拷贝到当前工程的aarPath目录下面。

4.获取dependency的所有moduleArtifacts

我们知道每个dependency会包含可能多个artifact(就是具体依赖的aar或jar文件),步骤3,4,5就是把这些artifact路径记录下来,对于gradle版本>=2.3时会把artifact从C盘拷贝到当前指定的aarPath里面。这一步为接下来的aar合并做铺垫。

5、配置fat-aar自定义的task与gradle task的依赖关系和先后顺序

我们知道生成aar的最直接task是bundleRelease或者bundleDebug等,由于fat-aar只处理release类型,所以fat-aar所有自定义的task都是在hook bundleRelease的子任务,这样最终改变bundleRelease的最终输出,生成一个我们想要的一个聚合了所有embedded资源的aar包。详细的task 依赖关系请参考下面的3.2节。

3.2、fat-aar task依赖关系

蓝色为gradle自带的task,红色为fat-aar自定义的task。从图中可以看出来fat-aar主要对bundleRelease(打aar包的task)的子任务hook,这些子任务包括assets,resources,jni,manifest,java,R,proguard这些组成aar的必要资源的处理任务。通过hook将这些资源整合成fat-aar需要输出的一个聚合aar包。下面对这些task分别做分析:

 

处理资源类型task名称依赖的gradle task功能
assetsembedAssetsprepareReleaseDependencies将所有embedded方式依赖的aar路径中assets目录追加到当前的fat-library工程对应的目录中来(android.sourceSets.main.assets.srcDirs+=file("$aarPath/assets"))
proguardembedProguardprepareReleaseDependencies将所有embedded方式依赖的aar路径中proguard.txt 中的内容append到当前的fat-library的proguard.txt里面
resembedLibraryResources依赖:
embedProguard
prepareReleaseDependencies

被依赖:
packageReleaseResources
首先找到bundleRelease打res资源的task(packageReleaseResources)的输入,然后往输入中添加aar路径中的资源目录($aarPath/res)
jniembedJniLibstransformNativeLibsWithSyncJniLibsForRelease将所有embedded方式依赖的aar路径中jni目录($aarPath/jni)中的文件拷贝到当前的fat-library工程对应的jni目录中来
manifestembedManifestsprocessReleaseManifest将embedded library里面的manifest和当前fat-library的manifest合并(使用ManifestMerger2),输出替换当前工程的manifest.xml和替换aapt生成的manifest.xml(即:build/intermediates/manifests/aapt/release/AndroidManifest.xml)
R.javagenerateRJavaprocessReleaseResources
被依赖:collectRClass, embedRClass,embedJavaJars
该task负责把embedded依赖的aar工程里面的R.java的资源id引用都指向当前library工程(fat-library)的R.java文件里面来。
JavaembedJavaJarscompileReleaseJavaWithJavac1.将所有embedded方式依赖的aar路径中classes.jar里面的class拷贝到fat-library的build/intermediates/classes/release目录下面
2.将所有embedded方式依赖的aar路径中libs目录下面的jar文件拷贝到当前fat-library的build目录下面(fat-library/build/intermediates/bundles/default/libs)
3.将所有embedded方式依赖的jar artifact也拷贝到当前的fat-library的build目录下面(同上)


3.2.1、embedManifests task

一张图来描述embeddedManifest task的流程,一句话总结这个流程是将embedded library里面的manifest和当前fat-library的manifest合并,输出替换当前工程的manifest.xml和替换aapt生成的manifest.xml(即:build/intermediates/manifests/aapt/release/AndroidManifest.xml)

 

3.2.2、generateRJava task

功能描述:generateRJava是dependsOn processReleaseResources该task负责把embedded依赖的aar工程里面的R.java的资源id引用都指向当前library工程(fat-library)的R.java文件里面来。如果不执行generateRJava会怎么样呢?我们单独执行processReleaseResources task,打开library-onefat-library的R.java是这样的

library-one:

fat-library:

执行完generateRJava task后,library-one的R.java会在上面结果的基础上将资源ID指向了fat-library工程的资源id,最终library-one的R.java如下:

library-one:

 

实现分析:

找到fat-aar里面的generateRJava task

首先它会获取当前fat-library工程的包名

它会先找到当前fat-library工程的manifest文件路径,android.sourceSets.main.manifest.srcFile 返回的是xxx\src\main\AndroidManifest.xml ,如果Manifest文件存在则读取里面的package属性,并存放在libPackageName变量中。这个libPackageName在后面逻辑中用于embedded工程(library-one和library-two)的R.java中的资源ID指向fat-library的R.java资源ID时使用。

上面这段代码是把遍历embedded的aar依赖,把他们的R.txt种的subclassnametype读取出来存放在Map。注意这里没有存放value,是因为此时fat-library的R.java已经包含了所有依赖的aar工程的资源ID值了,这里仅仅是为后续用subclass, nametype来映射到这些值

接下来上面的代码是用Map来生成该embedded aar(例如:library-one)工程的R.java文件,我们可以看到这个R文件路径就是根据该embedded aar的包名创建的。

3.2.3、fat-aar如何实现将R.class合并

上面介绍了generateRJava task,它的任务是做R文件合并和关联。但最终的aar包里面是如何包含这些R.class 的呢?我们来一张图描述generateRJava和其它task的关系:

每个task职责如下:

generateRJava: 做R文件合并和关联。将所有embed进来的aar里面的R文件都指向最外层的R文件。这样不会出现每个aar无法找到自己资源的情况了。

collectRClass: 将上面的R.class拷贝到build目录下面的fat-aar/release目录

embedRClass: 将build/fat-aar/release目录下面的R.class打包成Jar并输出到“build/intermediates/bundles/default(或者release)/libs”下面

embedJavaJars: 合并所有embed方式依赖的aar和jar里面的jar包(包括:classes.jar, libs里面的jar),并输出到“build/intermediates/bundles/default(或者release)/libs”下面

 

遇到的问题:

  1. 我曾今遇到过在“build/intermediates/bundles/default(或者release)/libs”居然没有任何jar文件的错误,这个原因是transformClassesAndResourcesWithSyncLibJarsForRelease task会清除libs下面的jar。具体为什么会清除我还不清楚,希望知道的同学可以回复本文章。

解决方法:

a. 在embedJavaJars task末尾将libs里面的内容拷贝到一个临时目录(例如:libs_tmp), 代码如下:

b. 并在打aar包之前将libs_temp目录里面的内容复制回来,代码如下:

 

 

赞(6) 打赏
未经允许不得转载:花花鞋 » fat-aar 实现分析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

国内精品Android技术社区

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏