android-native-dev

android-native-dev

熱門

Android 原生應用程式開發與 UI 設計指南。涵蓋 Material Design 3、Kotlin/Compose 開發、專案配置、無障礙功能(Accessibility)與建置疑難排解。在進行 Android 原生應用程式開發前請先閱讀本指南。

1.3萬星標
1131分支
更新於 2026/4/18
SKILL.md
唯讀
名稱
android-native-dev
描述

Android 原生應用程式開發與 UI 設計指南。涵蓋 Material Design 3、Kotlin/Compose 開發、專案配置、無障礙功能(Accessibility)與建置疑難排解。在進行 Android 原生應用程式開發前請先閱讀本指南。

1. 專案情境評估

在開始開發之前,請先評估目前的專案狀態:

情境 特徵 應對方式
空目錄 完全沒有任何檔案 需要進行完整初始化,包含建立 Gradle Wrapper
已有 Gradle Wrapper 已存在 gradlewgradle/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} → 例如 devDebugprodRelease

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 -->