ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android AAR包生成与使用全攻略:从模块化到Maven发布

Android AAR包生成与使用全攻略:从模块化到Maven发布 1. 项目概述为什么我们需要aar包在Android开发中我们经常需要将一些功能模块化比如一个自定义的UI控件库、一个网络请求框架或者一个封装了特定业务逻辑的SDK。直接复制粘贴代码显然不是好办法不仅难以维护也容易造成版本混乱。这时候aar包就登场了。你可以把它理解为一个Android版本的“乐高积木块”它包含了编译后的代码classes.jar、资源文件res/、清单文件AndroidManifest.xml以及可能的原生库jni/。相比于jar包aar能打包Android特有的资源是组件化、模块化开发的核心载体。我见过不少团队在项目初期图省事直接采用模块依赖module dependency但随着项目膨胀编译速度慢得让人抓狂。后来切换到aar依赖不仅清晰了模块边界还大幅提升了编译效率。对于提供第三方SDK的开发者来说生成aar更是交付的标配。今天我就结合自己踩过的坑从头到尾捋一遍在Android Studio中生成aar和使用aar的完整流程以及那些官方文档里不会写的细节。2. aar包生成全流程与核心配置生成aar听起来简单点几下鼠标就行但要想生成一个“靠谱”、能在各种环境下稳定工作的aar里面的门道可不少。2.1 基础环境与模块创建首先确保你的Android Studio是最新版老版本在构建支持上可能会有一些奇怪的问题。我们从一个干净的工程开始。创建Android Library模块这是生成aar的源头。不要在你的主App模块里折腾。点击File - New - New Module选择Android Library。我习惯以lib_开头命名比如lib_mynetwork这样在项目结构里一目了然。模块结构审视创建好后打开这个Library模块的build.gradle.kts(或build.gradle)。你会看到第一行是plugins { id(com.android.library) }这标志着它是一个库模块而不是应用模块id(com.android.application)。这是最根本的区别。2.2 build.gradle关键配置解析库模块的构建配置决定了最终aar包的“质量”。下面是一个经过实战检验的配置示例我会逐段解释plugins { id(com.android.library) id(org.jetbrains.kotlin.android) // 如果用Kotlin } android { namespace com.example.mynetwork compileSdk 34 defaultConfig { minSdk 21 // targetSdk 在Library中通常不需要设置由宿主App决定 testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner consumerProguardFiles(consumer-rules.pro) // 关键混淆规则文件 } buildTypes { release { isMinifyEnabled true // 开启代码混淆 proguardFiles( getDefaultProguardFile(proguard-android-optimize.txt), consumer-rules.pro // 再次指定消费者混淆规则 ) } debug { isMinifyEnabled false } } // 解决构建变体Flavor相关依赖问题 flavorDimensions environment productFlavors { create(dev) { dimension environment } create(prod) { dimension environment } } // 关键配置指定Java版本 compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget 17 } // 可选如果你需要打包额外的资源或排除某些文件 sourceSets { getByName(main) { // 可以在这里指定额外的资源目录 // res.srcDirs src/main/customRes } } } dependencies { // 声明你的库所依赖的其他库 implementation(androidx.core:core-ktx:1.12.0) implementation(androidx.appcompat:appcompat:1.6.1) // 注意谨慎使用 api 与 implementation // api依赖会传递到使用你aar的宿主App // implementation依赖只在本库内部使用不会传递 api(com.squareup.retrofit2:retrofit:2.9.0) // 宿主App也需要Retrofit implementation(com.squareup.okhttp3:logging-interceptor:4.12.0) // 仅内部使用 }配置要点与避坑指南consumerProguardFiles这是最容易被忽略也最容易出问题的地方。你的库如果使用了混淆isMinifyEnabled true必须通过这个属性提供一个consumer-rules.pro文件。这个文件里的规则不会用来混淆你的库代码而是会合并到最终宿主App的混淆配置中告诉宿主App“在使用我的类和方法时请保留它们不要混淆掉。” 否则宿主App开启混淆后调用你的库方法可能会因为类名、方法名被改变而引发ClassNotFoundException或NoSuchMethodError。依赖传递性务必理清api和implementation的区别。如果你希望宿主App无需再次声明就能使用你库所依赖的某个库例如你暴露了Retrofit的接口就用api。如果某个依赖纯粹是你的库内部实现细节例如一个日志工具就用implementation可以避免依赖冲突和增大宿主APK。Java版本统一设置sourceCompatibility、targetCompatibility和jvmTarget避免因版本不一致导致的字节码问题。构建变体Flavor如果你的库有不同的环境配置如开发、生产需要像上面一样配置productFlavors。这样生成的aar会包含对应的变体例如mylibrary-dev-release.aar和mylibrary-prod-release.aar。2.3 生成aar包的多种方式与产物定位配置好后就可以生成aar了。有几种常用方式通过Gradle任务面板推荐给初学者在Android Studio右侧的Gradle面板中找到你的Library模块展开Tasks - build双击assemble或assembleRelease。assemble会生成所有变体Debug/Release 如果有Flavor则包括所有Flavor组合的aar而assembleRelease只生成Release版本。通过命令行适合CI/CD集成在项目根目录打开终端执行# 生成所有变体的aar ./gradlew :lib_mynetwork:assemble # 仅生成Release版本的aar ./gradlew :lib_mynetwork:assembleRelease # 生成特定Flavor的Release版本例如prod ./gradlew :lib_mynetwork:assembleProdRelease通过Build菜单在Android Studio顶部菜单栏选择Build - Make Module ‘lib_mynetwork’这会触发编译但不会直接打开输出目录。生成的aar包在哪里这是另一个常见问题。构建成功后aar文件位于你的Library模块目录/build/outputs/aar/例如项目根目录/lib_mynetwork/build/outputs/aar/lib_mynetwork-release.aar在这个目录下你可能会看到多个aar对应不同的构建变体。通常我们发布给第三方使用的是-release版本。注意在生成aar前务必先执行一次Clean Project(Build - Clean Project)。我遇到过多次因为缓存导致的新代码没有被打包进aar的情况清理重建可以避免这类诡异问题。3. aar包的多种使用方式详解拿到了aar文件接下来就是在其他项目中使用它。根据使用场景的不同主要有三种引入方式。3.1 方式一本地文件依赖最直接当你需要快速测试或者aar包是团队内部共享但尚未发布到仓库时这种方式最方便。放置aar文件在宿主App模块或其他模块内创建一个目录习惯上叫libs如果不存在就新建。将你的xxx.aar文件复制进去。修改build.gradle打开宿主App模块的build.gradle.kts在dependencies块中添加依赖。对于旧版Gradle使用implementation filesdependencies { implementation(files(libs/xxx.aar)) }对于新版Gradle推荐使用implementation fileTree或更规范的flatDir 单纯使用files()在某些复杂构建场景下可能有问题。更健壮的做法是在项目根build.gradle.kts或settings.gradle.kts中声明仓库或者在模块级配置// 在模块的build.gradle中 repositories { flatDir { dirs(libs) // 指定libs目录为本地仓库 } } dependencies { implementation(name: xxx, ext: aar) // 注意这里没有版本号 // 或者使用 fileTree 引入目录下所有aar // implementation(fileTree(libs) { include(*.aar) }) }flatDir的方式让Gradle将libs目录视为一个特殊的本地Maven仓库管理起来更清晰。实操心得使用flatDir时name就是aar的文件名不带后缀。例如文件是lib_mynetwork-release.aar则name为lib_mynetwork-release。强烈建议对aar文件进行版本命名如mylibrary-1.0.0.aar并在flatDir依赖时也体现版本方便管理。这种方式最大的缺点是依赖不会传递。如果你的aar包A内部依赖了另一个库B并且你用api方式引入了B那么宿主App在使用A时仍然需要手动在dependencies中再次添加对B的依赖否则会编译报错。这是本地文件依赖的固有局限。3.2 方式二发布到本地Maven仓库团队协作推荐对于团队内部共享的通用组件发布到本地Maven仓库是更专业的选择。它模拟了远程仓库的机制可以处理传递性依赖并且有版本管理。配置发布脚本在你的Library模块的build.gradle.kts文件末尾添加发布配置。// 应用Maven发布插件 apply(plugin maven-publish) // 配置发布任务 afterEvaluate { publishing { publications { createMavenPublication(release) { // 指定要发布的组件这里是Android库的Release变体 from(components[release]) // 配置Maven坐标GroupId, ArtifactId, Version groupId com.example artifactId mynetwork version 1.0.0 } // 如果需要同时发布Debug版本可以再创建一个‘debug’ publication } // 指定发布到的本地仓库目录 repositories { maven { url uri(${project.rootDir}/local-repo) } } } }执行发布任务在Gradle任务面板中找到你的Library模块下的publishing - publishReleasePublicationToMavenRepository双击执行。或者用命令行./gradlew :lib_mynetwork:publishReleasePublicationToMavenRepository在宿主项目中引用发布成功后会在项目根目录生成一个local-repo文件夹里面是按照Maven规范存放的aar、pom文件等。在项目根目录的settings.gradle.kts中声明这个本地仓库dependencyResolutionManagement { repositories { mavenLocal() // 可选指全局的 ~/.m2/repository maven { url uri(${rootDir}/local-repo) } // 我们的项目本地仓库 google() mavenCentral() } }在宿主App模块的build.gradle.kts的dependencies中像引用远程库一样引用dependencies { implementation(com.example:mynetwork:1.0.0) }优势完美解决了传递性依赖问题。宿主App只需要声明对你的库的依赖你的库所api的依赖会被自动传递和解析。版本管理清晰非常适合团队内部分发。3.3 方式三发布到远程仓库正式交付对于对外发布的SDK或者公司内部的私有制品库如Nexus、Artifactory需要发布到远程Maven仓库。配置逻辑与本地Maven类似主要区别在于repositories的配置。publishing { publications { createMavenPublication(release) { from(components[release]) groupId com.example artifactId mynetwork version 1.0.0 // 可选配置POM文件信息如许可证、开发者信息等 pom { name.set(My Network Library) description.set(A fantastic network library for Android) url.set(http://www.example.com) licenses { license { name.set(The Apache License, Version 2.0) url.set(http://www.apache.org/licenses/LICENSE-2.0.txt) } } } } } repositories { maven { // 这里是你的私有Maven仓库地址 val releasesRepoUrl uri(https://your.company.com/repository/maven-releases/) val snapshotsRepoUrl uri(https://your.company.com/repository/maven-snapshots/) url if (version.toString().endsWith(SNAPSHOT)) snapshotsRepoUrl else releasesRepoUrl // 通常需要认证信息 credentials { username project.findProperty(mavenUser) as String? ?: password project.findProperty(mavenPassword) as String? ?: } } } }执行发布任务后库就会被上传到远程仓库。其他开发者只需要在项目的repositories块中添加你的仓库地址即可通过implementation(com.example:mynetwork:1.0.0)进行依赖。4. 高级主题与深度避坑指南掌握了基本操作我们来看看那些容易让人栽跟头的高级问题和优化技巧。4.1 资源冲突与资源ID固定当你的aar包中包含资源如图片、字符串、布局文件并且宿主App也有同名的资源时就会发生资源冲突。默认情况下Android构建工具会优先使用宿主App的资源这可能导致你的库UI显示异常。解决方案1资源前缀推荐在库模块的build.gradle.kts中强制为所有资源添加前缀android { ... resourcePrefix mylib_ // 自定义前缀如 mylib_ }设置后你在库中新建的资源文件其名称会被建议或强制加上mylib_前缀例如mylib_icon.png、string/mylib_hello。这从根源上避免了命名冲突。解决方案2谨慎选择资源名称即使不用前缀也养成使用具有唯一性、描述性资源名的习惯避免使用icon.png、title这种过于通用的名字。资源ID固定Resource ID Fixing这是一个更底层的问题。在AAPT2中库模块的资源ID在每次编译时可能是不稳定的非final。这通常不是问题因为最终打包APK时所有资源会被合并并分配最终的固定ID。但在一些动态加载、反射使用资源的极端场景下需要注意。通常我们不需要干预。4.2 混淆与consumer-rules.pro的编写混淆是发布Release版本aar的必备步骤但配置不当就是灾难。前面提到了consumer-rules.pro这里详细说说怎么写。假设你的库有一个公开的API类MyNetworkClient内部有一个实现类InternalHttpEngine。你的混淆规则应该保留所有公开的API包括类、方法、字段。通常通过-keep规则实现。允许混淆内部实现类以减小体积。一个典型的consumer-rules.pro文件内容如下# 保留我的库中所有公开的类、方法、字段。注意包名路径。 -keep class com.example.mynetwork.api.** { *; } # 或者更精确地保留某个类及其公有成员 -keep public class com.example.mynetwork.MyNetworkClient { public methods; public fields; } # 保留实现了某个接口的所有类如果你使用了接口暴露功能 -keep class * implements com.example.mynetwork.RequestCallback { *; } # 保留带有特定注解的类和方法例如Keep注解 -keep androidx.annotation.Keep class ** { *; } # 注意不要在这里混淆第三方库那是宿主App该操心的事。 # 但如果你用了反射调用第三方库可能需要keep对应的部分。关键点consumer-rules.pro是给宿主App的混淆器看的规则。你库内部的混淆规则由库模块自己的proguard-rules.pro控制通过proguardFiles配置。两者职责分离。4.3 多模块依赖与传递依赖管理当你的项目结构复杂库模块A本身还依赖另一个本地模块B或外部库时管理起来需要技巧。依赖本地模块在库A的build.gradle.kts中使用project路径依赖。dependencies { implementation(project(:moduleB)) // 依赖同项目下的另一个模块 }当你发布A的aar时B的内容默认不会打包进A的aar。A的pom文件会记录它对B的依赖。如果B也是你发布的库那么宿主App需要同时依赖A和B如果B是api依赖或者由A的pom文件传递解决如果发布到Maven仓库。如果B是纯内部模块这种结构可能不适合生成独立aar考虑将A和B合并或重构。处理依赖冲突当你的aar通过api依赖了Retrofit 2.9.0而宿主App依赖了Retrofit 2.11.0就会发生冲突。Gradle默认会选择最高版本2.11.0但这可能不兼容你的库。策略一推荐在你的库中将这类依赖声明为implementation不传递让宿主App自行决定版本。但这要求你的库接口不暴露第三方库的类型。策略二使用resolutionStrategy在宿主App中强制指定某个库的版本。策略三在库文档中明确声明兼容的依赖版本范围。4.4 调试与源码关联Source JAR直接依赖aar无法在Android Studio中点击跳转到库的源码给调试带来困难。解决方法是为aar同时提供源码包source JAR。在库模块的build.gradle.kts中修改发布配置publishing { publications { createMavenPublication(release) { from(components[release]) groupId com.example artifactId mynetwork version 1.0.0 // 添加源码打包任务 artifact(sourceJar) } } } // 定义一个生成源码Jar的任务 val sourceJar by tasks.registering(Jar::class) { from(android.sourceSets[main].java.srcDirs) archiveClassifier.set(sources) }发布后Maven仓库中会包含一个-sources.jar文件。当你在宿主项目中依赖这个库时Android Studio会自动下载并关联源码实现点击跳转。5. 常见问题排查与实战技巧实录即使按照指南操作实际开发中还是会遇到各种奇怪的问题。这里记录一些高频问题的排查思路。5.1 编译时常见错误与解决问题1Direct local .aar file dependencies are not supported when building an AAR.现象当你尝试构建一个本身输出为aar的库模块A而该模块通过files()或flatDir依赖了另一个本地aar文件B时会报此错误。原因Android Gradle插件不支持在编译aar时直接引用本地aar文件作为依赖。解决方案最佳方案将本地aar文件B发布到Maven仓库本地或远程然后通过implementation(com.example:B:1.0.0)方式依赖。临时方案如果B只是简单的jar包无资源可以将其重命名为.jar并依赖。如果是aar可以解压aar将其中的classes.jar作为jar依赖并将其res等资源手动合并到模块A中非常不推荐维护成本高。问题2宿主App编译报错提示找不到aar中的类或资源。排查步骤检查依赖是否成功添加在宿主App的build.gradle中确认依赖语句无误。执行./gradlew :app:dependencies查看依赖树确认你的aar出现在列表中。检查aar内容用解压软件打开aar文件查看classes.jar里是否包含你预期的类res/目录下是否有资源文件。可能你的代码并没有被成功编译打包进去。检查混淆规则如果宿主App开启了混淆请确认你的consumer-rules.pro文件是否正确配置并被打包。检查宿主App的混淆输出日志通常在build/outputs/mapping/release/mapping.txt看你的类是否被错误混淆了。检查依赖传递如果是本地文件依赖确认aar的所有传递依赖是否已在宿主App中声明。问题3运行时崩溃NoClassDefFoundError或NoSuchMethodError。原因这通常是版本冲突或混淆问题的典型表现。排查执行./gradlew :app:dependencies --configuration releaseRuntimeClasspath查看所有运行时依赖的版本检查是否有同一个库存在多个不同版本。仔细检查混淆规则确保所有需要暴露的公共API都被-keep了。5.2 性能与优化建议最小化aar体积启用代码混淆minifyEnabled true和资源压缩shrinkResources true注意库模块中此选项可能不直接生效主要在App模块生效。使用implementation而非api来减少传递依赖让宿主App控制最终打包的库。移除未使用的资源考虑将大图片放在CDN库中只放置必要的小图标。加速构建对于不常变化的稳定库尽量使用远程Maven依赖而非项目模块依赖。Gradle会对远程依赖进行缓存。在开发阶段如果库模块和宿主App在同一项目可以使用compileOnly或debugImplementation依赖你的库模块避免频繁构建aar。但发布前需切换回正式依赖方式测试。5.3 版本管理与发布策略语义化版本SemVer严格遵守主版本号.次版本号.修订号MAJOR.MINOR.PATCH的规则。PATCH增加表示向后兼容的问题修复MINOR增加表示向后兼容的功能新增MAJOR增加表示发生了不兼容的API变更。使用SNAPSHOT版本进行开发测试在版本号后加上-SNAPSHOT如1.0.0-SNAPSHOT发布到快照仓库。Gradle每次构建会尝试检查并下载最新的快照版本方便联调。文档与变更日志Changelog每次发布新版本aar务必更新文档和变更日志明确列出新增、废弃、移除的功能以及重要的修复这对你的协作者或第三方开发者至关重要。生成和使用aar是Android开发者进阶的必备技能它关乎代码的复用性、工程的解耦和团队的协作效率。从简单的本地文件依赖到规范的Maven仓库发布每一步都体现了工程化的思维。记住一个好的aar包不仅仅是功能的集合更是一份清晰的契约和一份用心的礼物。多花点时间在混淆规则、依赖管理和版本控制上能为你和你的团队省去无数排查问题的时间。
返回列表