首页 / 资讯中心 / 文章详情

Kotlin Android工程搭建全攻略:从Gradle KTS到构建优化

Kotlin Android工程搭建全攻略:从Gradle KTS到构建优化 ★ FEATURED ARTICLE
1. 从零开始的 Kotlin Android 工程为什么值得认真对待先说明一下这篇文章主要面向两类人一类是刚从 Java 或者别的语言转过来、想用 Kotlin 写 Android 的小白另一类是已经写过几个 Android 项目、但一直在 Java 的舒适区里没走出来、想系统地试试 Kotlin 的开发者。标题里的“建立 Android 工程”听起来像是最简单的一步但恰恰是这个“第一步”最能体现 Kotlin 与老一套 Android 开发流程之间的磨合点。我用 Kotlin 开发 Android app 的时间不算短这几年从新建工程、配置 Gradle、写业务代码到上架都走了一遍。我个人的体会是Kotlin 的语法只是表面真正的门槛在于工程配置、依赖管理、以及和 Android 生态里那些老工具链的相处方式。如果你只是把 Java 代码“翻译”成 Kotlin 语法而不去理解项目结构、构建脚本、SDK 版本这些底层逻辑后面会踩很多莫名其妙的坑。这篇文章从新建工程开始讲但不止于“点几下下一步”。我会把每一步背后的原理、常见的坑、以及我现在实际在用的配置都拆开说清楚。文章内容基于我自己的开发环境Android Studio、Gradle Kotlin DSL、新版 AGP并会补充一些基于常见实践的选型理由。你跟着走一遍应该能拿到一个干净、可扩展、好维护的 Kotlin Android 工程而不是一个能编译能跑、但一加功能就出问题的“玩具项目”。2. 环境准备与工具链选型先弄明白这些再动手2.1 Android Studio 与 SDK 的正确安装思路Kotlin 开发 Android 的官方推荐 IDE 依然是 Android Studio这一点没什么好争论的。Eclipse 时代的 Android 开发插件已经彻底退出历史舞台IntelliJ IDEA 社区版虽然也能写 Kotlin、也能配 Android 工程但缺少很多 Android 专属的调试工具和模板除非你有特殊原因否则不建议从 IDEA 起步。安装 Android Studio 时有两点容易被忽略SDK 目录规划Windows 默认装到 C 盘但 Android SDK、Gradle 缓存、模拟器镜像都很大最好从一开始就把 SDK 路径改到非系统盘。我见过不少人因为 C 盘空间不足导致构建失败最后只能重装。不要用便携版或绿色版Android Studio 下载页面提供的是标准安装包网上有一些所谓“便携版”“汉化版”我建议一律别用。一是版本更新跟不上二是插件和 SDK 组件的路径经常被改坏出了问题很难排查。版本上没有太多纠结的必要现在 Google 官方已经不再提供 Android Studio 4.x 以下的版本下载了你直接去官网下载最新稳定版即可。所谓稳定版指的是正式发布版不要碰 Canary 和 Beta。Beta 版偶尔会有一些新特性但用来建工程、跑业务代码稳定压倒一切。2.2 JDK 版本与 Kotlin 编译环境的匹配Kotlin 编译和 Android Gradle PluginAGP对 JDK 版本有明确要求。工程建好之后Android Studio 会自动帮你配置 JDK但如果你在命令行环境下跑 Gradle或者你的机器上有多个 JDK 版本这里就很容易出问题。以当前的主流版本为例JDK 17 是 Android Studio 和 AGP 8.x 系列最稳妥的选择。JDK 21 在较新的 AGP 版本上也能用但部分第三方插件未必兼容。我的做法是在项目的gradle.properties里不强行指定 JDK 路径而是在 Android Studio 的 Settings 里设置 Gradle JDK 为jbr-17或者本机安装的 JDK 17。这套组合我用了一年多没出过编译层面的怪问题。如果你在命令行里跑构建注意JAVA_HOME环境变量必须指向 JDK 17。很多人遇到过“Unsupported class file major version”之类的报错基本都是 JAVA_HOME 指向了系统自带的旧 JDK而 Android Studio 内嵌的 JBR 版本反而更高。2.3 Gradle 与 Kotlin DSL 的选择从 Groovy 到 KTS 的迁移理由Gradle 构建脚本早期只用 Groovy文件后缀是.gradle。Kotlin 成为 Android 官方语言之后Gradle 也开始支持 Kotlin DSL.gradle.kts后缀而且 AGP 官方文档逐渐把 KTS 作为示例格式。我强烈建议你新建工程时直接选 Kotlin DSL原因很简单KTS 有类型检查写配置时会自动补全拼错属性名会编译报错而不是运行期才炸。Kotlin DSL 本身是 Kotlin 代码和你的项目语言一致心智负担小。官方新模板和文档都以 kts 为主跟着学不会过时。不过老实说KTS 也有一个让人想骂人的地方首次同步时编译脚本比较慢而且报错信息有时候比 Groovy 更加隐晦。比如括号不匹配这种错误Groovy 会提示在哪一行KTS 可能只给你一个“编译脚本失败”的大概范围。这个只能慢慢习惯遇到报错先往下翻看具体 cause而不是只看堆栈头部。3. 新建 Kotlin Android 工程的完整实操3.1 从 IDE 模板创建项目每一步都在干什么打开 Android Studio选择 New Project。模板选择上对初次学习 Kotlin 的人来说推荐选Empty Views Activity而不是 Compose 模板。为什么Compose 是新的 UI 框架学习曲线本身就陡如果同时学习 Kotlin 和 Compose变量太多容易劝退。先选 Views也就是传统的 XML 布局 Activity把 Kotlin 语法和 Android 组件生命周期先吃透之后再学 Compose 会更容易因为思维模型可以平移。填写项目信息时有几个字段值得注意Package name这是应用的唯一标识一旦上架就不能改。命名规则一般是com.你的域名.项目名。有人喜欢用com.example我建议还是认真起一个不然以后要改包名涉及的地方不止一处非常麻烦。Minimum SDK这个字段决定你的 app 最低支持到哪个 Android 版本。选太高会丢失用户选太低会被一些新 API 限制。我一般默认选 API 24Android 7.0这个版本覆盖了当前绝大多数存量设备而且很多现代库的最低要求也是 24。Build configuration language看到这个选项选 Kotlin DSL理由上面已经说了。点 Finish 之后Android Studio 会开始创建工程并自动执行 Gradle Sync。这一步如果卡很久基本都是在下载依赖和网络环境有关。你可以检查 Gradle 的下载源是否配置了阿里云或其他国内镜像关于这块后文会专门讲。3.2 手动建一个 Kotlin Android 工程不理解原理就复制不了IDE 的模板会帮你处理掉 90% 的细节但我强烈建议你至少手动建过一次工程。理解目录结构和 Gradle 脚本之间的关系你才能真正诊断自己的构建问题。一个最简 Kotlin Android 工程文件结构如下MyApplication/ ├── app/ │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/example/myapplication/ │ │ │ │ └── MainActivity.kt │ │ │ ├── res/ │ │ │ │ ├── layout/activity_main.xml │ │ │ │ └── values/strings.xml │ │ │ └── AndroidManifest.xml │ ├── build.gradle.kts │ └── proguard-rules.pro ├── build.gradle.kts ├── settings.gradle.kts ├── gradle.properties └── gradle/wrapper/ ├── gradle-wrapper.jar └── gradle-wrapper.properties关键文件的作用settings.gradle.kts声明项目名称和包含哪些模块。模块多了之后这个文件就是你的模块索引。build.gradle.kts根目录配置全局插件版本但不在这里配置应用自己的依赖。app/build.gradle.kts这是整个工程的核心应用插件、SDK 版本、依赖、签名配置全在这里。gradle.propertiesJVM 参数、AndroidX 开关等全局属性。gradle wrapper用来固定 Gradle 版本保证团队开发和 CI 构建环境一致。如果你只是点了 IDE 的“下一步”这些事情都是自动完成的。但你至少要知道每个文件是干嘛的因为你迟早会遇到“依赖加不上”“版本冲突”“构建慢”这类问题到时候你必须能定位到具体文件。3.3 根构建文件与模块构建文件的配置示例这里给一份我现在常用的app/build.gradle.kts配置里面加了注释方便你理解每一项的作用plugins { id(com.android.application) id(org.jetbrains.kotlin.android) } android { namespace com.example.myapplication compileSdk 34 defaultConfig { applicationId com.example.myapplication minSdk 24 targetSdk 34 versionCode 1 versionName 1.0 testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner } buildTypes { release { isMinifyEnabled true proguardFiles( getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro ) } } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget 17 } } dependencies { implementation(androidx.core:core-ktx:1.12.0) implementation(androidx.appcompat:appcompat:1.6.1) implementation(com.google.android.material:material:1.11.0) implementation(androidx.constraintlayout:constraintlayout:2.1.4) testImplementation(junit:junit:4.13.2) androidTestImplementation(androidx.test.ext:junit:1.1.5) androidTestImplementation(androidx.test.espresso:espresso-core:3.5.1) }几个容易困惑的点namespace和applicationId不一样。namespace用于 R 类和 BuildConfig 的包名一般和你的包名一致applicationId是上架后的唯一 ID。混乱的时候可以把 namespace 理解成“代码层面的身份”applicationId 是“商店层面的身份”。compileSdk和targetSdk不一样。compileSdk 是编译用的 API 版本targetSdk 是你声明的兼容目标。targetSdk 高会触发系统一些行为变更比如权限策略更严格但这也是应用跟上系统演进的唯一方式。isMinifyEnabled是代码混淆和收缩的开关release 包一般打开debug 包关掉不然断点调试会被混淆影响。3.4 配置国内镜像源不让网络成为第一道坎如果你在国内使用默认的 Maven Central 和 Google MavenGradle 同步大概率会慢到让你怀疑人生。这不是项目配置的问题是网络链路的问题。解决方式是配置镜像仓库。在settings.gradle.kts的dependencyResolutionManagement块里添加阿里云镜像dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven(https://maven.aliyun.com/repository/public) maven(https://maven.aliyun.com/repository/google) maven(https://maven.aliyun.com/repository/gradle-plugin) google() mavenCentral() } }注意顺序阿里云镜像尽量写在前面这样会优先被请求。google()和mavenCentral()我通常还会保留因为镜像偶尔会有同步延迟尤其是一些新发布的库版本镜像源里可能还没有。镜像源只是治标治本的办法是确保 Gradle Wrapper 分发的 Gradle 包也走国内源。在gradle-wrapper.properties里默认地址是services.gradle.org/distributions如果你每次下载 Gradle 都卡在 99%可以改手动下载后放到本地。4. Gradle 同步、依赖管理与常见构建问题排查4.1 Gradle 同步到底在做什么很多人点了一次 Sync Now看它转了几分钟就以为只是“在下载东西”。实际上Gradle 同步的过程包含了几件事读取 settings 文件确定有哪些模块。解析插件版本并下载对应插件。解析项目的依赖树把所有直接和间接依赖都下载到本地缓存。生成 IDE 需要的项目模型供代码跳转和提示使用。理解了同步是“下载依赖 建立模型”之后你就能解释很多怪现象了。比如你改了依赖版本Sync 报错比如明明没动代码Sync 却要跑几十秒。这些都是因为依赖树需要重新解析并验证一致性。4.2 依赖版本冲突的排查思路Kotlin Android 工程最常见的构建错误不是语法错误而是依赖冲突。最常见的表现形式是More than one file was found with OS independent path META-INF/xxx。排查这类问题的标准思路是使用./gradlew :app:dependencies --configuration debugRuntimeClasspath把完整的依赖树打出来看看哪些库拉入了重复的东西。还有一种常见冲突是 Kotlin 版本不一致。你的项目用 Kotlin 1.9.x某个库强制依赖 Kotlin 1.8.xGradle 会自动选用较高版本看起来没问题但实际上如果库是用旧版本 Kotlin 编译的可能会出现 runtime 时的kotlin.KotlinNullPointerException或NoClassDefFoundError。我的建议是依赖库尽量选维护活跃的版本发布不超过两年。如果 AAR 库是你自己维护的统一 Kotlin 版本。遇到诡异报错先检查是不是 Kotlin 标准库版本冲突。4.3 Android 构建缓存与增量编译的优化Kotlin 编译速度在大型项目里确实是个痛点。有一些配置可以明显改善体验。在gradle.properties里我会配置org.gradle.jvmargs-Xmx2048m -Dfile.encodingUTF-8 org.gradle.paralleltrue org.gradle.cachingtrue kotlin.incrementaltrueorg.gradle.paralleltrue多模块并行构建对大型项目有效。org.gradle.cachingtrue构建缓存Gradle 会缓存任务输出下次构建跳过不变的部分。kotlin.incrementaltrueKotlin 增量编译开关改动单个文件不需要全量编译。不过这些配置不是玄学它们的效果和项目结构高度相关。如果你的项目只有一个 app 模块并行构建的提升几乎为零如果拆了 library 模块收益会明显一些。5. 从“能编译”到“好用”的工程化配置这也算第一篇的干货5.1 AndroidX 与 Material Components 的正确引入方式如果你在 2018 年左右写过 Android 项目应该还记得那个 androidx 迁移的阵痛。现在新模板默认启用 AndroidX但你最好还是确认gradle.properties里有这两行android.useAndroidXtrue android.enableJetifiertrueandroid.useAndroidXtrue启用 AndroidX 库没有它用了 androidx 依赖会告警。android.enableJetifiertrue让旧的支持库自动转换成 AndroidX主要是兼容一些老库。如果你新建的工程里依赖全是新版库Jetifier 的作用不大可以关闭能省一点构建时间。但如果项目里还有老 AAR比如从同事那边拷来的老模块先开着不然会报Failed to resolve: androidx.core:core-ktx之类的错误。Material Components 库引入后你需要在主题里继承Theme.Material3.DayNight.NoActionBar这类 Material 主题按钮、文本输入框这些组件才能用上 Material 的样式。原生Theme.AppCompat不会自动获得 Material 组件的主题支持。5.2 ViewBinding让 Kotlin 代码告别 findViewById传统 Android 开发里需要在 Activity 里写findViewByIdTextView(R.id.tv_name)或者在 ViewHolder 里反复 findViewById。这个写法不仅啰嗦而且容易写错 ID运行期才闪退。Kotlin 时代的工程推荐直接用 ViewBinding。在app/build.gradle.kts的android块里添加buildFeatures { viewBinding true }然后用起来是这样的class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.tvMessage.text Hello Kotlin } }你会注意到ActivityMainBinding是根据你的 XML 文件名activity_main.xml自动生成的类。如果你的布局里有控件tv_message那就会生成一个tvMessage属性。这是编译期生成的代码不是运行时反射所以性能没有损失。ViewBinding 相比旧式 findViewById 有几个好处类型安全不会出现强转错误。空安全如果 XML 里删了某个 ID访问也不算为空编译期就报错。自动处理 include 布局的绑定。5.3 把项目模块化从单模块到多模块的演进时机第一篇文章不讨论复杂的模块拆分但你最好在初始阶段就养成“按功能分模块”的意识。不是说一上来就拆成 model / network / ui 三四个模块而是当你发现app模块下的代码超过两三千行且还有继续增长的趋势时就该考虑拆 Module 了。模块化带来的优势很实际编译隔离只改 network 模块时不需要重新编译整个 app增量编译范围小。依赖控制一些 internal 级别的接口只对模块内可见避免了类爆炸式的互相依赖。功能复用如果将来要做平板适配或另一个应用直接复用模块。但拆模块不是没有代价。模块多了以后Gradle 同步时间变长依赖版本管理也需要抽出来单独处理。所以小项目还是老老实实单模块等痛点出现了再拆。5.4 版本目录统一管理依赖版本的现代做法如果你坚持手动建工程你会遇到一个问题多个模块里都写implementation(androidx.core:core-ktx:1.12.0)版本号散落在各处升级 SDK 版本时得人工搜索替换特别容易漏。Gradle 提供了一种叫 Version Catalog 的机制在gradle/libs.versions.toml里统一管理依赖版本。我建议你从新工程开始就用起来好处是版本集中升级依赖只改一个文件。IDE 有自动补全。多模块之间的版本天然一致。一个简单的libs.versions.toml示例[versions] agp 8.2.2 kotlin 1.9.22 coreKtx 1.12.0 appcompat 1.6.1 [libraries] androidx-core-ktx { group androidx.core, name core-ktx, version.ref coreKtx } androidx-appcompat { group androidx.appcompat, name appcompat, version.ref appcompat } [plugins] android-application { id com.android.application, version.ref agp } kotlin-android { id org.jetbrains.kotlin.android, version.ref kotlin }然后在app/build.gradle.kts里这样引用plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) }看着比硬编码版本号繁琐但你多维护两个模块之后就会明白 Version Catalog 对“避免版本失控”的价值。6. 常见问题与排查技巧实录6.1 新建工程后 Gradle Sync 一直失败的常见原因这是新手问得最多的问题。通常分为几类第一类网络问题。表现是 Sync 卡在 Could not resolve org.jetbrains.kotlin:kotlin-stdlib:1.9.22等依赖下载位置。解决方式就是配镜像源上文已经说过。第二类JDK 版本不对。表现是报Unsupported class file major version或Unsupported Java. Your build is currently configured to use Java XX.0.0 and Java 17.0.0 required.去 Settings 里把 Gradle JVM 换成 17。第三类AGP 版本和 Gradle 版本不匹配。AGP 8.2 需要 Gradle 8.2 以上AGP 8.5 需要 Gradle 8.7。如果你手滑在gradle-wrapper.properties里写过错误的 Gradle 版本Sync 会有很长一段红色的错误提示会直接点名版本不匹配。按照提示换成对应版本就行。6.2 Kotlin 编译时报“Unresolved reference”的排查思路在 Kotlin 工程里Unresolved reference 是最高频的编译错误。它不一定真的是你代码写错了更多时候是依赖没有引入。比如你用androidx.lifecycle.lifecycle-runtime-ktx时写了lifecycleScope.launch { }但没在依赖里加lifecycle-runtime-ktxIDE 就会报 Unresolved referencelifecycleScope。这时候你不应该怀疑 Kotlin 语法而是去检查依赖。还有一种情况比较隐蔽你把一个类写在了错误包名下面然后在别的文件 import 时用了旧包名也报 Unresolved reference。我自己的习惯是报这种错误先Build - Clean Project再 Sync 一次。如果还报错再用Ctrl Shift Omac 是Cmd Shift O搜索类名看它究竟存在于哪个包。6.3 构建成功但安装失败常见设备相关错误跑模拟器或者真机调试时常见报错有几种INSTALL_FAILED_UPDATE_INCOMPATIBLE手机上已经装了签名不一致的同包名应用。卸载旧的再安装即可。INSTALL_FAILED_INSUFFICIENT_STORAGE手机存储空间不足。清理或卸载不用的应用。Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]APK 签名有问题常见于 Gradle 配置里签名信息错误或 debug 签名丢失。这些大部分不需要动 Gradle 配置先把设备环境弄干净。6.4 build.gradle.kts 里的几个常见“语法坑”KTS 虽然类型安全但有几个地方容易踩坑坑一在 KTS 里写字符串误用单引号。Groovy 同时支持单双引号KTS 只支持双引号字符串。把com.android.application写成单引号编译直接报错。坑二compileSdk不能直接写变量。某些版本下可以但我在统一维护时还是喜欢直接写数字避免解析顺序问题。坑三proguardFiles方法在 KTS 里的写法不同。Groovy 里可以直接写proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.proKTS 里需要getDefaultProguardFile(...)加上括号。上面给的模板我实测过没问题。坑四在 app/build.gradle.kts 里用apply plugin: xxx旧写法也没错但和 plugins 块混用时可能出现插件顺序问题。现在统一用plugins {}块别混用。6.5 真机调试时看不到设备怎么办Windows 下最常见的两个原因一是没有开启开发者模式和 USB 调试二是缺少 USB 驱动。解决思路如下在手机设置里连点版本号七次开启开发者选项。在开发者选项里打开“USB 调试”。插上数据线后手机弹窗询问“是否允许 USB 调试”点允许。在终端运行adb devices查看设备是否出现。如果显示unauthorized说明手机端没允许如果显示offline换一根数据线或重启 adb server。Mac 和 Linux 情况类似不过 Linux 下有时候要配 udev 规则Windows 则要装对应厂商的 USB 驱动。7. 一个最小可用的 Kotlin 工程跑起来之后到这里你已经有了一个可以编译、可以安装的 Kotlin Android 工程。但工程建好只是开始真正让你和 Java 时代说再见的是后面的 Kotlin 语法学习和 Android 组件实战。作为一个过来人我建议接下来的学习路径是先把 Activity 的生命周期和 Kotlin 的onCreate、onStart、onResume这些回调结合着过一遍不要光看 DSL 语法。写一两个只操作 UI 的小例子比如按钮点击后改变文本、根据输入框内容动态显示列表。引入 ViewModel LiveData 或 StateFlow 这类架构组件之前先把 Kotlin 的协程基础补上。直接调 Retrofit 的 suspend 函数比用 callbacks 方便得多但前提是你知道Dispatchers.Main和viewModelScope是怎么回事。我个人在实际操作中的建议是新建完工程后先加一个简单的网络请求和一个简单的列表展示把依赖注入、协程、生命周期这几块核心内容串一遍。这个“最小闭环”做完你对 Kotlin Android 开发的信心和对工程结构的理解都会有质的飞跃。等到能独立跑通一个完整功能时再回头深挖 Gradle 性能优化和模块化拆分你会发现自己不再是照着模板写代码而是真的在“搭建”一个应用。最后再分享一个小技巧如果你以后要给别人交付一个 Android 工程或者在公司里参与协作一定要把gradle-wrapper相关文件纳入版本控制并且尽量让组内所有人用同一个 Gradle 版本。版本不一致导致的构建问题是团队协作里最不值得花时间解决的一类问题。把这些底层的配置一次搞定后面写业务代码的时候你才能真正心无旁骛。
阅读完成 · 觉得有帮助?
咨询建站