Android SDK 基础:项目结构、Gradle 构建与依赖管理
做 Android SDK 适配,首先得摸透 Android 项目的骨架。很多刚转过来的 iOS 同学问我:“Android 项目怎么这么‘重’?” 没错,它确实比 iOS 项目多了一层构建脚本和资源分类。我把最核心的三个点拆开讲:项目结构长啥样、Gradle 怎么配、依赖该用 implementation 还是 api。
Android 项目结构:你得知道的几个文件夹
一个标准的 Android 项目,从根目录往下看,大概长这样:
MySDKProject/
├── app/ # 主模块(可独立运行)
│ ├── build/ # 编译产物,别手改
│ ├── libs/ # 放置 .jar / .aar 文件
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/ # 源代码(Kotlin/Java)
│ │ │ ├── res/ # 资源文件
│ │ │ ├── AndroidManifest.xml
│ │ │ └── jniLibs/ # 存放 .so 库
│ │ └── test/ # 单元测试
│ └── build.gradle.kts # 模块级构建脚本
├── library/ # SDK 模块(可作为库发布)
├── build.gradle.kts # 项目级构建脚本
├── settings.gradle.kts # 项目模块声明
├── gradle.properties # 全局属性
└── local.properties # SDK 路径(本地环境,别提交)
我个人习惯在 SDK 适配阶段,把 jniLibs 文件夹拎出来重点检查。如果你的 SDK 包含了 C/C++ 代码,.so 文件必须按 CPU 架构放在对应子目录(armeabi-v7a、arm64-v8a、x86_64)。少一个架构,用户跑在高通骁龙 8 Gen 3 上可能直接崩。我曾经碰到过某第三方 SDK 只给了 armeabi 的 so,在 Pixel 6 上黑屏半天。
Gradle 构建配置:你绕不开的 DSL
Gradle 是 Android 的构建系统,现在主流是用 Kotlin DSL(.kts 结尾)。以前项目大多用 Groovy,我建议新项目直接上 Kotlin DSL,类型安全,IDE 补全也更友好。
一个典型的模块级 build.gradle.kts 结构:
plugins {
id("com.android.library") // 如果是 SDK 模块就用 library
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.sdk.core"
compileSdk = 34
defaultConfig {
minSdk = 21
targetSdk = 34
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// 注意这里!
api("com.google.guava:guava:33.0.0-android")
}
这里有几个字段你需要留意:
- compileSdk:编译用的 API 版本,最好用最新的稳定版(目前 34)。高了可能会有弃用警告,低了有些新 API 不能用。
- minSdk:最低支持版本。做 SDK 适配时这参数很关键——定得太高(比如 minSdk=26)直接丢掉 5% 的用户;定得太低(14)你又要兼容一堆旧特性。我个人建议 SDK 最少支持到 21 或 23。
- consumerProguardFiles:SDK 专属的 ProGuard 规则。你的 SDK 打包成 aar 后,这个文件会自动合并到宿主应用里。要是忘了配,混淆后反射调用直接炸。
依赖管理:implementation vs api,别再混用了
这个困扰过无数新人的问题,我讲个故事你就懂了。
假设你的 SDK 依赖了 Guava,然后你写了一个工具类 SDKUtils,它的方法签名里用到了 Guava 的 Optional 类型。SDK 内部用了 implementation("com.google.guava:guava:33.0.0-android")。宿主编译时调了 SDKUtils.someMethod(),编译直接报错:
error: cannot access com.google.common.base.Optional
为什么?因为 implementation 的意思是“只在模块内部使用,对外隐藏”。宿主的编译路径上根本拿不到 Guava 的类。
改成 api 就解决了。但注意:滥用 api 会导致依赖传递膨胀。宿主要是也引了个不同版本的 Guava,就可能冲突。
| 关键字 | 是否可传递 | 典型场景 |
|---|---|---|
| implementation | 否(不暴露给依赖方) | 内部实现细节、第三方工具(如 OkHttp 内部使用) |
| api | 是(暴露给依赖方) | SDK 对外 API 使用的依赖(如接口参数或返回类型用到的库) |
| compileOnly | 否(仅编译期) | 只为了编译检查,不打包进去(如注解处理器) |
| runtimeOnly | 否(仅运行时) | 运行时才需要的日志实现等 |
api;否则一律用 implementation。
interface + 代理模式,不让宿主编译期感知你具体用了哪个框架。这样依赖管理会清爽很多。
Gson 类型参数,却用 implementation 引入。结果上线后宿主集成时编译不过,临时发补丁改成 api,又导致宿主原有的 Gson 版本冲突。后来我学乖了——优先用 org.json 这种 JDK 自带的解析方案,避免强绑定第三方库。
一张图看懂核心逻辑
常见问题快问快答
- Q:整个项目用 implementation,编译会更快吗?
是的,Gradle 不需要重新编译依赖方,增量编译速度提升明显。所以默认用 implementation,非必要不用 api。 - Q:SDK 模块里的 aar 文件怎么放?
直接丢进libs/目录,然后在 dependencies 里加一句implementation(fileTree("libs/*.aar"))。或者单独引用:implementation(files("libs/mysdk-1.0.aar"))。 - Q:JNI 库的 so 文件能不能不按架构分文件夹?
不能。Android 系统根据 CPU 架构搜索对应子目录。如果你只放一个 arm64-v8a 下的 so,用户手机是 armeabi-v7a 就会闪退。要么所有架构都放,要么只放最广泛的两个。
build.gradle.kts 里加一段任务打印最终依赖树,调试依赖冲突特别好用:tasks.register("printDeps") { doLast { configurations.implementation.get().forEach { println(it.name) } } }
嗯,Android SDK 基础这块其实不复杂,但细节特别多。项目结构决定你放资源的姿势,Gradle 配置决定你编译的方式,implementation 和 api 的选择直接决定宿主集成的体验。用好这些基础,后面讲 SDK 混淆、多 module 构建、资源覆盖才不会有坑。