安装方式
命令行安装
在项目根目录执行以下命令,完成 Skill 安装。
npx bzskills add MiniMax-AI/skills --skill android-native-dev Android原生应用开发和UI设计指南。涵盖Material Design 3、Kotlin/Compose开发、项目配置、无障碍性和构建问题排查。请在开始Android原生应用开发前阅读本文。
44
下载量
命令行安装
在项目根目录执行以下命令,完成 Skill 安装。
npx bzskills add MiniMax-AI/skills --skill android-native-dev name: android-native-dev
description: Android原生应用开发和UI设计指南。涵盖Material Design 3、Kotlin/Compose开发、项目配置、无障碍性和构建问题排查。请在开始Android原生应用开发前阅读本文。
license: MIT
metadata:
version: "1.0.0"
category: mobile
sources:
- Material Design 3 Guidelines (material.io)
- Android Developer Documentation (developer.android.com)
- Google Play Quality Guidelines
- WCAG Accessibility Guidelines在开始开发之前,评估当前项目状态:
| 场景 | 特征 | 处理方法 |
|---|---|---|
| 空目录 | 无任何文件 | 需要完整初始化,包括 Gradle Wrapper |
| 已有 Gradle Wrapper | 存在 gradlew 和 gradle/wrapper/ 目录 | 直接使用 ./gradlew 构建 |
| Android Studio 项目 | 完整的项目结构,可能缺少 Wrapper | 检查 Wrapper,如需则运行 gradle wrapper |
| 不完整的项目 | 部分文件存在 | 检查缺失文件,补全配置 |
关键原则:
./gradlew assembleDebug 成功执行gradle.properties,先创建并配置 AndroidXMyApp/
├── gradle.properties # 配置 AndroidX 和其他设置
├── settings.gradle.kts
├── build.gradle.kts # 根级别
├── gradle/wrapper/
│ └── gradle-wrapper.properties
├── app/
│ ├── build.gradle.kts # 模块级别
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/com/example/myapp/
│ │ └── MainActivity.kt
│ └── res/
│ ├── values/
│ │ ├── strings.xml
│ │ ├── colors.xml
│ │ └── themes.xml
│ └── mipmap-*/ # 应用图标
---
# 必需配置
android.useAndroidX=true
android.enableJetifier=true
# 构建优化
org.gradle.parallel=true
kotlin.code.style=official
# JVM 内存设置(根据项目规模调整)
# 小项目:2048m,中等项目:4096m,大项目:8192m+
# org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
注意:如果构建过程中遇到OutOfMemoryError,增加-Xmx值。依赖较多的大型项目可能需要 8GB 或更多。
dependencies {
// 使用 BOM 管理 Compose 版本
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
// Activity & ViewModel
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
}
产品风味允许你创建不同版本的应用(例如免费/付费、开发/预发/生产)。
在 app/build.gradle.kts 中配置:
android {
// 定义风味维度
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
// 每个风味不同的配置值
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
// 不同资源
resValue("string", "app_name", "MyApp Dev")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
resValue("string", "app_name", "MyApp Staging")
}
create("prod") {
dimension = "environment"
// 生产环境不加后缀
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "false")
resValue("string", "app_name", "MyApp")
}
}
buildTypes {
debug {
isDebuggable = true
isMinifyEnabled = false
}
release {
isDebuggable = false
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
构建变体命名:{风味}{构建类型} → 例如 devDebug、prodRelease
Gradle 构建命令:
# 列出所有可用的构建变体
./gradlew tasks --group="build"
# 构建特定变体(风味 + 构建类型)
./gradlew assembleDevDebug # Dev 风味,Debug 构建
./gradlew assembleStagingDebug # Staging 风味,Debug 构建
./gradlew assembleProdRelease # Prod 风味,Release 构建
# 构建特定风味的所有变体
./gradlew assembleDev # 所有 Dev 变体(debug + release)
./gradlew assembleProd # 所有 Prod 变体
# 构建特定构建类型的所有变体
./gradlew assembleDebug # 所有风味的 Debug 构建
./gradlew assembleRelease # 所有风味的 Release 构建
# 安装特定变体到设备
./gradlew installDevDebug
./gradlew installProdRelease
# 一键构建并安装
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity
在代码中访问 BuildConfig:
注意:从 AGP 8.0 开始,BuildConfig默认不再生成。必须在build.gradle.kts中显式启用:
```kotlin
android {
buildFeatures {
buildConfig = true
}
}
```
// 在代码中使用构建配置值
val apiUrl = BuildConfig.API_BASE_URL
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING
if (BuildConfig.DEBUG) {
// 仅调试模式下的代码
}
风味特定的源集:
app/src/
├── main/ # 所有风味共享的代码
├── dev/ # Dev 独有的代码和资源
│ ├── java/
│ └── res/
├── staging/ # Staging 独有的代码和资源
├── prod/ # Prod 独有的代码和资源
├── debug/ # Debug 构建类型代码
└── release/ # Release 构建类型代码
多个风味维度(例如 environment + tier):
android {
flavorDimensions += listOf("environment", "tier")
productFlavors {
create("dev") { dimension = "environment" }
create("prod") { dimension = "environment" }
create("free") { dimension = "tier" }
create("paid") { dimension = "tier" }
}
}
// 结果:devFreeDebug, devPaidDebug, prodFreeRelease 等
---
| 类型 | 约定 | 示例 |
|---|---|---|
| 类/接口 | PascalCase | UserRepository, MainActivity |
| 函数/变量 | camelCase | getUserName(), isLoading |
| 常量 | SCREAMING_SNAKE | MAX_RETRY_COUNT |
| 包名 | 小写 | com.example.myapp |
| Composable 函数 | PascalCase | @Composable fun UserCard() |
空安全:
// ❌ 避免:非空断言 !! (可能崩溃)
val name = user!!.name
// ✅ 推荐:安全调用 + 默认值
val name = user?.name ?: "Unknown"
// ✅ 推荐:let 处理
user?.let { processUser(it) }
异常处理:
// ❌ 避免:在业务层随意使用 try-catch 吞掉异常
fun loadData() {
try {
val data = api.fetch()
} catch (e: Exception) {
// 吞掉异常,难以调试
}
}
// ✅ 推荐:让异常传播,在合适层次处理
suspend fun loadData(): Result<Data> {
return try {
Result.success(api.fetch())
} catch (e: Exception) {
Result.failure(e) // 包裹并返回,让调用方决定处理
}
}
// ✅ 推荐:在 ViewModel 中统一处理
viewModelScope.launch {
runCatching { repository.loadData() }
.onSuccess { _uiState.value = UiState.Success(it) }
.onFailure { _uiState.value = UiState.Error(it.message) }
}
线程选择原则:
| 操作类型 | 线程 | 描述 |
|---|---|---|
| UI 更新 | Dispatchers.Main | 更新 View、State、LiveData |
| 网络请求 | Dispatchers.IO | HTTP 调用、API 请求 |
| 文件 I/O | Dispatchers.IO | 本地存储、数据库操作 |
| 计算密集型 | Dispatchers.Default | JSON 解析、排序、加密 |
正确用法:
// 在 ViewModel 中
viewModelScope.launch {
// 默认在主线程,可以更新 UI State
_uiState.value = UiState.Loading
// 切换到 IO 线程进行网络请求
val result = withContext(Dispatchers.IO) {
repository.fetchData()
}
// 自动返回主线程,更新 UI
_uiState.value = UiState.Success(result)
}
// 在 Repository 中(挂起函数应为主线程安全)
suspend fun fetchData(): Data = withContext(Dispatchers.IO) {
api.getData()
}
常见错误:
// ❌ 错误:在 IO 线程更新 UI
viewModelScope.launch(Dispatchers.IO) {
val data = api.fetch()
_uiState.value = data // 崩溃或警告!
}
// ❌ 错误:在主线程执行耗时操作
viewModelScope.launch {
val data = api.fetch() // 阻塞主线程!ANR
}
// ✅ 正确:在 IO 线程获取数据,在主线程更新
viewModelScope.launch {
val data = withContext(Dispatchers.IO) { api.fetch() }
_uiState.value = data
}
// 默认为 public,需要时显式声明
class UserRepository { // public
private val cache = mutableMapOf<String, User>() // 仅在类内可见
internal fun clearCache() {} // 仅在模块内可见
}
// data class 属性默认为 public,跨模块使用时需小心
data class User(
val id: String, // public
val name: String
)
// ❌ 错误:访问未初始化的 lateinit
class MyViewModel : ViewModel() {
lateinit var data: String
fun process() = data.length // 可能崩溃
}
// ✅ 正确:使用可空类型或默认值
class MyViewModel : ViewModel() {
var data: String? = null
fun process() = data?.length ?: 0
}
// ❌ 错误:在 lambda 中使用 return
list.forEach { item ->
if (item.isEmpty()) return // 从外部函数返回!
}
// ✅ 正确:使用 return@forEach
list.forEach { item ->
if (item.isEmpty()) return@forEach
}
// ❌ 错误:字段声明为非空(服务端可能不返回)
data class UserResponse(
val id: String = "",
val name: String = "",
val avatar: String = ""
)
// ✅ 正确:所有字段声明为可空
data class UserResponse(
@SerializedName("id")
val id: String? = null,
@SerializedName("name")
val name: String? = null,
@SerializedName("avatar")
val avatar: String? = null
)
// ❌ 错误:只添加 Observer,不删除
class MyView : View {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
activity?.lifecycle?.addObserver(this)
}
// 内存泄漏!
}
// ✅ 正确:配对添加和删除
class MyView : View {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
activity?.lifecycle?.addObserver(this)
}
override fun onDetachedFromWindow() {
activity?.lifecycle?.removeObserver(this)
super.onDetachedFromWindow()
}
}
import android.util.Log
// Info:正常流程的关键检查点
Log.i(TAG, "loadData: started, userId = $userId")
// Warning:可恢复的异常情况
Log.w(TAG, "loadData: cache miss, fallback to network")
// Error:失败/错误情况
Log.e(TAG, "loadData failed: ${error.message}")
| 级别 | 使用场景 |
|---|---|
i (Info) | 正常流程、方法入口、关键参数 |
w (Warning) | 可恢复异常、回退处理、空返回 |
e (Error) | 请求失败、捕获到的异常、不可恢复错误 |
---
// ❌ 错误:从非 Composable 函数中调用 Composable
fun showError(message: String) {
Text(message) // 编译错误!
}
// ✅ 正确:标记为 @Composable
@Composable
fun ErrorMessage(message: String) {
Text(message)
}
// ❌ 错误:在 LaunchedEffect 之外使用 suspend
@Composable
fun MyScreen() {
val data = fetchData() // 错误!
}
// ✅ 正确:使用 LaunchedEffect
@Composable
fun MyScreen() {
var data by remember { mutableStateOf<Data?>(null) }
LaunchedEffect(Unit) {
data = fetchData()
}
}
// 基本状态
var count by remember { mutableStateOf(0) }
// 派生状态(避免重复计算)
val isEven by remember { derivedStateOf { count % 2 == 0 } }
// 跨重组持久化(例如滚动位置)
val scrollState = rememberScrollState()
// ViewModel 中的状态
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}
// ❌ 错误:在 Composable 中创建对象(每次重组都会创建)
@Composable
fun MyScreen() {
val viewModel = MyViewModel() // 错误!
}
// ✅ 正确:使用 viewModel() 或 remember
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
// ...
}
---
必须提供多分辨率图标:
| 目录 | 尺寸 | 用途 |
|---|---|---|
| mipmap-mdpi | 48x48 | 基准 |
| mipmap-hdpi | 72x72 | 1.5x |
| mipmap-xhdpi | 96x96 | 2x |
| mipmap-xxhdpi | 144x144 | 3x |
| mipmap-xxxhdpi | 192x192 | 4x |
推荐:使用自适应图标(Android 8+):
<!-- res/mipmap-anydpi-v26/ic_launcher.xml -->
<adaptive-icon>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
| 类型 | 前缀 | 示例 |
|---|---|---|
| 布局 | layout_ | layout_main.xml |
| 图片 | ic_, img_, bg_ | ic_user.png |
| 颜色 | color_ | color_primary |
| 字符串 | - | app_name, btn_submit |
变量名、资源 ID、颜色、图标和 XML 元素不得使用 Android 保留字或系统资源名称。使用保留名称会导致构建错误或资源冲突。
常见的保留名称(不要使用):
| 类别 | 保留名称(不要使用) |
|---|---|
| 颜色 | background, foreground, transparent, white, black |
| 图标/可绘制对象 | icon, logo, image, drawable |
| 视图 | view, text, button, layout, container |
| 属性 | id, name, type, style, theme, color |
| 系统 | app, android, content, data, action |
示例:
<!-- ❌ 错误:使用保留名称 -->
<color name="background">#FFFFFF</color>
<color name="icon">#000000</color>
<!-- ✅ 正确:添加前缀或具体命名 -->
<color name="app_background">#FFFFFF</color>
<color name="icon_primary">#000000</color>
// ❌ 错误:变量名与系统冲突
val icon = R.drawable.my_icon
val background = Color.White
// ✅ 正确:使用描述性名称
val appIcon = R.drawable.my_icon
val screenBackground = Color.White
<!-- ❌ 错误:可绘制对象名称冲突 -->
<ImageView android:src="@drawable/icon" />
<!-- ✅ 正确:添加前缀 -->
<ImageView android:src="@drawable/ic_home" />
---
| 错误关键词 | 原因 | 修复 |
|---|---|---|
Unresolved reference | 缺少导入或未定义 | 检查导入,验证依赖 |
Type mismatch | 类型不兼容 | 检查参数类型,添加转换 |
Cannot access | 可见性问题 | 检查 public/private/internal |
@Composable invocations | Composable 上下文错误 | 确保调用方也是 @Composable |
Duplicate class | 依赖冲突 | 使用 ./gradlew dependencies 调查 |
AAPT: error | 资源文件错误 | 检查 XML 语法和资源引用 |
./gradlew clean assembleDebug# 清理并构建
./gradlew clean assembleDebug
# 查看依赖树(调查冲突)
./gradlew :app:dependencies
# 查看详细错误
./gradlew assembleDebug --stacktrace
# 刷新依赖
./gradlew --refresh-dependencies
---
审查 Android UI 文件,确保符合 Material Design 3 指南和 Android 最佳实践。
#### M3 核心原则
| 原则 | 描述 |
|---|---|
| 个性化 | 基于用户偏好和壁纸的动态颜色 |
| 自适应 | 响应所有屏幕尺寸和形态因素 |
| 表现力 | 大胆的色彩和字体,彰显个性 |
| 无障碍 | 为所有用户设计的包容性设计 |
#### M3 表现力(最新)
最新演进通过以下方式增加情感驱动的 UX:
关键决策:将视觉风格与应用类别和目标受众匹配。
| 应用类别 | 视觉风格 | 主要特征 |
|---|---|---|
| 工具/实用 | 极简 | 干净、高效、中性颜色 |
| 金融/银行 | 专业信任 | 保守颜色、安全第一 |
| 健康/养生 | 平静自然 | 柔和颜色、有机形状 |
| 儿童(3-5岁) | 趣味简单 | 鲜艳颜色、大触摸目标(56dp+) |
| 儿童(6-12岁) | 有趣互动 | 活力四射、游戏化反馈 |
| 社交/娱乐 | 表现力 | 品牌驱动、手势丰富 |
| 生产力 | 干净专注 | 极简、高对比度 |
| 电子商务 | 转化导向 | 清晰的行动号召、便于扫描 |
详细风格配置文件请参阅 [设计风格指南](references/design-style-guide.md)。
#### 颜色对比度要求
| 元素 | 最小比率 |
|---|---|
| 正文 | 4.5:1 |
| 大字体(18sp+) | 3:1 |
| UI 组件 | 3:1 |
#### 触摸目标
| 类型 | 尺寸 |
|---|---|
| 最小 | 48 × 48dp |
| 推荐(主要操作) | 56 × 56dp |
| 儿童应用 | 56dp+ |
| 目标之间间距 | 最小 8dp |
#### 8dp 网格系统
| 令牌 | 值 | 用法 |
|---|---|---|
| xs | 4dp | 图标内边距 |
| sm | 8dp | 紧凑间距 |
| md | 16dp | 默认内边距 |
| lg | 24dp | 区块间距 |
| xl | 32dp | 大间隔 |
| xxl | 48dp | 屏幕边距 |
#### 字体比例(概要)
| 类别 | 字号 |
|---|---|
| Display | 57sp, 45sp, 36sp |
| Headline | 32sp, 28sp, 24sp |
| Title | 22sp, 16sp, 14sp |
| Body | 16sp, 14sp, 12sp |
| Label | 14sp, 12sp, 11sp |
#### 动画时长
| 类型 | 时长 |
|---|---|
| 微动效(波纹) | 50-100ms |
| 短动效(简单) | 100-200ms |
| 中动效(展开/折叠) | 200-300ms |
| 长动效(复杂) | 300-500ms |
#### 组件尺寸
| 组件 | 高度 | 最小宽度 |
|---|---|---|
| 按钮 | 40dp | 64dp |
| 浮动操作按钮 | 56dp | 56dp |
| 文本字段 | 56dp | 280dp |
| 应用栏 | 64dp | - |
| 底部导航栏 | 80dp | - |
#### UI 反模式
#### 性能反模式
#### 无障碍反模式
| 主题 | 参考 |
|---|---|
| 颜色、排版、间距、形状 | [视觉设计](references/visual-design.md) |
| 动画与过渡 | [动效系统](references/motion-system.md) |
| 无障碍指南 | [无障碍](references/accessibility.md) |
| 大屏幕与折叠屏 | [自适应屏幕](references/adaptive-screens.md) |
| Android 核心指标与性能 | [性能与稳定性](references/performance-stability.md) |
| 隐私与安全 | [隐私与安全](references/privacy-security.md) |
| 音频、视频、通知 | [功能需求](references/functional-requirements.md) |
| 按类别划分的应用风格 | [设计风格指南](references/design-style-guide.md) |
---
注意:仅当用户明确要求测试时,才添加测试依赖。
一个经过良好测试的 Android 应用使用分层测试:快速的本地单元测试用于逻辑,仪器化测试用于 UI 和集成,以及 Gradle Managed Devices 可在任何机器(包括 CI)上可重现地运行模拟器。
在添加测试依赖之前,检查项目现有版本以避免冲突:
gradle/libs.versions.toml —— 如果存在,使用项目的版本目录样式添加测试依赖build.gradle.kts 中已固定的依赖版本版本对齐规则:
| 测试依赖 | 必须与以下版本对齐 | 如何检查 |
|---|---|---|
kotlinx-coroutines-test | 项目的 kotlinx-coroutines-core 版本 | 在构建文件或版本目录中搜索 kotlinx-coroutines |
compose-ui-test-junit4 | 项目的 Compose BOM 或 compose-compiler | 在构建文件中搜索 compose-bom 或 compose.compiler |
espresso-* | 所有 Espresso 构件必须使用相同版本 | 在构建文件中搜索 espresso |
androidx.test:runner, rules, ext:junit | 应使用兼容的 AndroidX Test 版本 | 在构建文件中搜索 androidx.test |
mockk | 必须支持项目的 Kotlin 版本 | 在根 build.gradle.kts 或版本目录中检查 kotlin 版本 |
依赖参考 —— 仅添加你需要的组:
dependencies {
// --- 本地单元测试 (src/test/) ---
testImplementation("junit:junit:<version>") // 4.13.2+
testImplementation("org.robolectric:robolectric:<version>") // 4.16.1+
testImplementation("io.mockk:mockk:<version>") // 匹配 Kotlin 版本
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:<version>") // 匹配 coroutines-core
testImplementation("androidx.arch.core:core-testing:<version>") // LiveData 的 InstantTaskExecutorRule
testImplementation("app.cash.turbine:turbine:<version>") // Flow/StateFlow 测试
// --- 仪器化测试 (src/androidTest/) ---
androidTestImplementation("androidx.test.ext:junit:<version>")
androidTestImplementation("androidx.test:runner:<version>")
androidTestImplementation("androidx.test:rules:<version>")
androidTestImplementation("androidx.test.espresso:espresso-core:<version>")
androidTestImplementation("androidx.test.espresso:espresso-contrib:<version>") // RecyclerView, Drawer
androidTestImplementation("androidx.test.espresso:espresso-intents:<version>") // Intent 验证
androidTestImplementation("androidx.test.espresso:espresso-idling-resource:<version>")
androidTestImplementation("androidx.test.uiautomator:uiautomator:<version>")
// --- Compose UI 测试 (仅当项目使用 Compose 时) ---
androidTestImplementation("androidx.compose.ui:ui-test-junit4") // 版本来自 Compose BOM
debugImplementation("androidx.compose.ui:ui-test-manifest") // createComposeRule 必需
}
注意:如果项目使用 Compose BOM,ui-test-junit4和ui-test-manifest不需要显式版本 —— BOM 会管理它们。
在 android 块中启用 Robolectric 资源支持:
android {
testOptions {
unitTests.isIncludeAndroidResources = true // Robolectric 必需
}
}
| 层次 | 位置 | 运行环境 | 速度 | 用途 |
|---|---|---|---|---|
| 单元测试 (JUnit) | src/test/ | JVM | ~ms | ViewModels, repos, mappers, validators |
| 单元测试 + Robolectric | src/test/ | JVM + 模拟 Android | ~100ms | 需要 Context、资源、SharedPrefs 的代码 |
| Compose UI (本地) | src/test/ | JVM + Robolectric | ~100ms | Composable 渲染和交互 |
| Espresso | src/androidTest/ | 设备/模拟器 | ~秒 | 基于 View 的 UI 流程、Intent、数据库集成 |
| Compose UI (设备) | src/androidTest/ | 设备/模拟器 | ~秒 | 完整 Compose UI 流程,真实渲染 |
| UI Automator | src/androidTest/ | 设备/模拟器 | ~秒 | 系统对话框、通知、多应用 |
| Managed Device | src/androidTest/ | Gradle-managed AVD | ~分钟(首次运行) | CI、跨 API 级别的矩阵测试 |
有关详细示例、代码模式以及 Gradle Managed Device 配置,请参阅 [测试](references/testing.md)。
# 本地单元测试(快速,无需模拟器)
./gradlew test # 所有模块
./gradlew :app:testDebugUnitTest # app 模块,debug 变体
# 单个测试类
./gradlew :app:testDebugUnitTest --tests "com.example.myapp.CounterViewModelTest"
# 仪器化测试(需要设备或 managed device)
./gradlew connectedDebugAndroidTest # 在连接的设备上运行
./gradlew pixel6Api34DebugAndroidTest # 在 managed device 上运行
# 同时运行两种测试
./gradlew test connectedDebugAndroidTest
# 带覆盖率报告测试(JaCoCo)
./gradlew testDebugUnitTest jacocoTestReport