原项目结构
moduleA:主要sdk和对sdk封装,还有一些基础库封装。
muduleB:相对独立模块,对其他模块无依赖。
moduleC:主module,包括UI及业务,依赖moduleB,非直接依赖moduleA(反射调用)
需求
整个项目原本是作为应用存在,现在需要打包成sdk,提供给其他应用使用,需要包含UI及全部功能。
简单拆分
1.moduleA、muduleB、moduleC 打包到一个aar。
2.工程既需要打包aar,又需要打包apk,所以moduleC不能再作为application存在,moduleC 属性变成library。
3.新增空壳moduleD,处理作为应用的职责,属性application,当打包sdk时,此module用来验证aar功能,打包应用时,此moudle作为应用指责存在
所以现项目结构:
moduleA: library
muduleB: library
moduleC: library
moduleD: application
开工
三个module需要打包到一个aar
方案一:fat-aar,
最原始的项目已经无人维护,https://github.com/kezong/fat-aar-android 算是比较新,可以用的,使用方式见github。
fat-aar的原理:
aar本身就是压缩包,有时候为了减小包体积,会把三方库中的so删掉,只留v7a平台的,所以aar可以直接进行增删合并等文件操作,没有apk那么麻烦,反编译后需要重新打包。
fat-aar的原理其实就是根据aar中的文件类型进行文件合并,在fat-aar.gradle中大体可以看到。
generateReleaseAssets.dependsOn embedAssets
embedAssets.dependsOn prepareReleaseDependencies
// Embed Resources by overwriting the inputResourceSets
packageReleaseResources.dependsOn embedLibraryResources
embedLibraryResources.dependsOn prepareReleaseDependencies
// Embed JNI Libraries
bundleRelease.dependsOn embedJniLibs
if(gradleApiVersion >= 2.3f) { //gradle版本不一致的处理
embedJniLibs.dependsOn transformNativeLibsWithSyncJniLibsForRelease
ext.bundle_release_dir = "$build_dir/intermediates/bundles/default"
}else{
embedJniLibs.dependsOn transformNative_libsWithSyncJniLibsForRelease
ext.bundle_release_dir = "$build_dir/intermediates/bundles/release";
}
// Merge Embedded Manifests
bundleRelease.dependsOn embedManifests
embedManifests.dependsOn processReleaseManifest
// Merge proguard files
embedLibraryResources.dependsOn embedProguard
embedProguard.dependsOn prepareReleaseDependencies
// Generate R.java files
compileReleaseJavaWithJavac.dependsOn generateRJava
generateRJava.dependsOn processReleaseResources
// Bundle the java classes
bundleRelease.dependsOn embedJavaJars
embedJavaJars.dependsOn compileReleaseJavaWithJavac
// If proguard is enabled, run the tasks that bundleRelease should depend on before proguard
if (tasks.findByPath('proguardRelease') != null) {
proguardRelease.dependsOn embedJavaJars
} else if (tasks.findByPath('transformClassesAndResourcesWithProguardForRelease') != null) {
transformClassesAndResourcesWithProguardForRelease.dependsOn embedJavaJars
}
支持合并的文件如下
- productFlavors
- manifest merge
- classes jar and external jars merge
- res merge
- assets merge
- jni libs merge
- proguard.txt merge
- R.txt merge
- R.class merge
fat-aar遇到的问题:
1. 找不到R文件,drawable,style等
使用方式不对
开始将moudleA 使用embed方式打包为一个aarA,moudleB使用embed方式打包为一个aarB,moudleC依赖aarA,aarB使用embed方式打包到最终的sdk,R文件丢失,程序异常。
修复:只需要处理moduleC的gradle文件即可,moudleC使用fat-aar插件,embed moudleA 和 moudleB,注意,AB两个module中依赖的aar都要在moduleC中调用embed依赖,这样才会把所有依赖的aar打到最终的sdk中。
2. aar中资源文件重名,如app_name
- 组件原则中提到每个组件资源名称需增加前缀,防止命名重复,导致资源冲突。
- 最好修改重名资源,防止资源异常。
- 实在不想修改,在gradle.properties中增加 android.disableResourceValidation = true 忽略此错误
3. jar包非java文件资源丢失
项目中依赖了mqtt的jar包,jar包中有Resource Bundle非java文件,编译出来的aar中,此部分文件丢失,导致应用异常。
修改为依赖远程库解决
4. Butterknife
Butterknife在组件化开发中显得有点睿智,存在Library中需要使用R2文件,普通的Library还行,但是需求是技能打包aar,又能打包apk,这部分不能动态切换,比较麻烦,最终忍痛移除了Butterknife的依赖项,手动findViewById解决。
5.关于jar包的依赖
依赖的jar包都在aar中的lib目录中,打包的时候可以不关心jar包打不打进去,如果提供给外部使用的时候有重复jar包引用,只需要把aar中jar包删掉即可,不需要重复打包。
一套代码维护两种打包方式
主要就是gralle和shell脚本编程。
1. 动态替换manifest占位符,因为作为Library,启动Activity不能有Launcher和main属性。
2. 使用fat-aar的制作gradle文件抽取独立脚本,正常依赖不使用fat-aar编译。
3. gradle.proterties中定义开关,是否打包sdk,build.gradle中指向编译方向,调用fat-aar脚本,还是正常编译。
4. 打包aar shell脚本,自动打包aar上传。
方案二:maven私服
把依赖的aar全部上传到maven私服,打包aar生成的pom文件依赖,外部使用时使用transitive = true,即可以自动下载依赖项。目前项目中没有使用此方案,虽然有maven私服,但非公共库不宜上传,而且众多aar版本管理更新也比较麻烦。