SKILL.md
唯讀
名稱
android-native-dev
描述
Android 原生應用程式開發與 UI 設計指南。涵蓋 Material Design 3、Kotlin/Compose 開發、專案配置、無障礙功能(Accessibility)與建置疑難排解。在進行 Android 原生應用程式開發前請先閱讀本指南。
1. 專案情境評估
在開始開發之前,請先評估目前的專案狀態:
| 情境 | 特徵 | 應對方式 |
|---|---|---|
| 空目錄 | 完全沒有任何檔案 | 需要進行完整初始化,包含建立 Gradle Wrapper |
| 已有 Gradle Wrapper | 已存在 gradlew 與 gradle/wrapper/ |
直接使用 ./gradlew 執行建置 |
| Android Studio 專案 | 專案結構完整,但可能缺少 Wrapper | 檢查 Wrapper,必要時執行 gradle wrapper |
| 不完整專案 | 僅有部分檔案 | 檢查缺少的檔案並補全配置 |
核心原則:
- 在撰寫商業邏輯之前,務必確保
./gradlew assembleDebug能順利建置成功 - 若缺少
gradle.properties,請先建立該檔案並完成 AndroidX 配置
1.1 必要檔案檢核表
MyApp/
├── 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-*/ # App 圖示
2. 專案配置
2.1 gradle.properties
# 必要配置
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 或更高的記憶體。
2.2 依賴項宣告規範
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")
}
2.3 Build Variants 與 Product Flavors
Product Flavors 允許你為 App 建立不同的版本(例如:免費版/付費版、開發版/測試版/正式版)。
在 app/build.gradle.kts 中的配置範例:
android {
// 定義 flavor 維度
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
// 為各個 flavor 設定不同的配置值
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")
}
}
}
Build Variant 命名規則:{flavor}{BuildType} → 例如 devDebug、prodRelease
Gradle 建置命令:
# 列出所有可用的 build variants
./gradlew tasks --group="build"
# 建置特定的 variant(flavor + buildType)
./gradlew assembleDevDebug # Dev flavor,Debug 建置
./gradlew assembleStagingDebug # Staging flavor,Debug 建置
./gradlew assembleProdRelease # Prod flavor,Release 建置
# 建置特定 flavor 的所有 variants
./gradlew assembleDev # 所有 Dev variants(包含 debug + release)
./gradlew assembleProd # 所有 Prod variants
# 建置特定 build type 的所有 variants
./gradlew assembleDebug # 所有 flavors 的 Debug 建置
./gradlew assembleRelease # 所有 flavors 的 Release 建置
# 將特定 variant 安裝至裝置
./gradlew installDevDebug
./gradlew installProdRelease
# 一鍵建置並安裝執行
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity
在程式碼中存取 BuildConfig:
說明:自 AGP 8.0 起,預設不再自動產生
BuildConfig。你必須在build.gradle.kts中明確啟用:android { buildFeatures { buildConfig = true } }
// 在程式碼中使用 build config 的設定值
val apiUrl = BuildConfig.API_BASE_URL
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING
if (BuildConfig.DEBUG) {
// 僅在 Debug 模式下執行的程式碼
}
針對特定 Flavor 的 Source Set 結構:
app/src/
├── main/ # 所有 flavor 共用的程式碼
├── dev/ # 僅限 Dev 使用的程式碼與資源
│ ├── java/
│ └── res/
├── staging/ # 僅限 Staging 使用的程式碼與資源
├── prod/ # 僅限 Prod 使用的程式碼與資源
├── debug/ # 僅限 Debug build type 使用的程式碼
└── release/ # 僅限 Release build type 使用的程式碼
多個 Flavor 維度(例如:環境 + 版本分級):
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 等等
3. Kotlin 開發規範
3.1 命名規範
| 型別 | 規範 | 範例 |
|---|---|---|
| 類別 / 介面 | PascalCase | UserRepository, MainActivity |
| 函式 / 變數 | camelCase | getUserName(), isLoading |
| 常數 | SCREAMING_SNAKE | MAX_RETRY_COUNT |
| 套件(Package) | 小寫字母 | com.example.myapp |
| Composable | PascalCase | @Composable fun UserCard() |
3.2 程式碼規範(重要)
空值安全(Null Safety):
// ❌ 避免:使用非空斷言 !!(可能引發崩潰)
val name = user!!.name
// ✅ 推薦:安全呼叫 + 預設值
val name = user?.name ?: "Unknown"
// ✅ 推薦:使用 let 處理
user?.let { processUser(it) }
例外處理(Exception Handling):
// ❌ 避免:在商業邏輯層隨意使用 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) }
}
3.3 執行緒與協程(關鍵)
執行緒選擇原則:
| 操作類型 | 執行緒 | 說明 |
|---|---|---|
| UI 更新 | Dispatchers.Main |
更新 View、State、LiveData |
| 網路請求 | Dispatchers.IO |
HTTP 呼叫、API 請求 |
| 檔案 I/O | Dispatchers.IO |
本地儲存、資料庫操作 |
| 計算密集型 | Dispatchers.Default |
JSON 解析、排序、加解密 |
正確用法:
// 在 ViewModel 中
viewModelScope.launch {
// 預設為主執行緒(Main),可更新 UI State
_uiState.value = UiState.Loading
// 切換至 IO 執行緒進行網路請求
val result = withContext(Dispatchers.IO) {
repository.fetchData()
}
// 自動切換回主執行緒,更新 UI
_uiState.value = UiState.Success(result)
}
// 在 Repository 中(suspend 函式應確保 main-safe)
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 取得資料,在 Main 更新 UI
viewModelScope.launch {
val data = withContext(Dispatchers.IO) { api.fetch() }
_uiState.value = data
}
3.4 可見性規則(Visibility Rules)
// 預設為 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
)
3.5 常見語法陷阱
// ❌ 錯誤:存取未初始化的 lateinit 變數
class MyViewModel : ViewModel() {
lateinit var data: String
fun process() = data.length // 可能引發崩潰
}
// ✅ 正確:使用可空型別(Nullable)或預設值
class MyViewModel : ViewModel() {
var data: String? = null
fun process() = data?.length ?: 0
}
// ❌ 錯誤:在 Lambda 中使用 return
list.forEach { item ->
if (item.isEmpty()) return // 會直接從外層函式回傳(Return)!
}
// ✅ 正確:使用 return@forEach
list.forEach { item ->
if (item.isEmpty()) return@forEach
}
3.6 後端回應 Data Class 欄位必須宣告為可空型別(Nullable)
// ❌ 錯誤:欄位宣告為非空(伺服器可能未回傳該欄位)
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
)
3.7 生命週期資源管理
// ❌ 錯誤:只新增 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 onDetache
<!-- truncated for translation batch; full body continues in source -->






