Android Studio 編譯失敗 - Error loading build artifacts

前幾天在公司專案遇到一個奇怪的 Bug,這裡筆記一下解決過程。 問題描述 在 Android Studio 點選 Run 'app' 時,專案完全沒編譯就直接報錯,錯誤訊息如下: 1 Error loading build artifacts from: D:\Daniel\Projects\cropssurvey-mk2-android\app\build\intermediates\apk_ide_redirect_file\debug\createDebugApkListingFileRedirect\redirect.txt 環境資訊 我的 Android Studio 設定參數如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Android Studio Meerkat Feature Drop | 2024.3.2 Patch 1 Build #AI-243.26053.27.2432.13536105, built on May 22, 2025 Runtime version: 21.0.6+-13368085-b895.109 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Toolkit: sun.awt.windows.WToolkit Windows 11.0 GC: G1 Young Generation, G1 Concurrent GC, G1 Old Generation Memory: 4096M Cores: 16 Registry: debugger.new.tool.window.layout=true ide.experimental.ui=true Non-Bundled Plugins: one.util.ideaplugin.screenshoter (1.8.1) com.wzc.sw.plugin (1.3.3) Dart (243.26753.1) com.duke.screenmatch (3.2) idea.plugin.protoeditor (243.22562.13) com.developerphil.adbidea (1.6.19) com.godwin.kdocer (1.6) org.sonarlint.idea (10.27.0.81781) io.flutter (86.0.1) 嘗試過的解法(失敗) Sync Project with Gradle Files Rebuild Project Invalid Cache and Restart 重開 Android Studio 刪除 /.gradle 與 /.idea 兩個資料夾。參考來源 結果,以上方法,都 沒 有 用 … ...

Published on September 12, 2025 · 1 min · 193 words · Daniel Huang

TypedArray.use{} 在 Android 11 以下出錯? Crash 原因與正確寫法一次看懂

在 Android 開發中,我們常使用 Kotlin 的 use {} 語法來自動管理資源,例如關閉檔案、關閉資料庫 Cursor。但這個便利的語法在 Android 11 以下,對某些類別其實會出錯造成應用程式閃退。如果一時不查,小心這坑就這樣踩了下去… 錯誤說明 1 2 3 4 5 context .obtainStyledAttributes(attrs, R.styleable.ActionFooterView, 0, 0) .use { // ... } 這段程式碼在 Android 12(API 31)以上沒問題,但在 Android 11 (API 30) 以下執行時,會拋出以下錯誤: 1 2 java.lang.IncompatibleClassChangeError Class 'android.content.res.TypedArray' does not implement interface 'java.lang.AutoCloseable' in call to 'void java.lang.AutoCloseable.close()' 白話來說,上面這個錯誤告訴我們,TypedArray 並沒有實作 AutoCloseable 介面,所以在試圖呼叫 AutoCloseable.close() 時拋出 IncompatibleClassChangeError 錯誤。 問題釐清:use {} 的背後原理 Kotlin use {} 是一個 extension function,會在 Block 結束後自動呼叫 AutoCloseable.close() 方法來釋放資源。 ...

Published on June 12, 2025 · 1 min · 211 words · Daniel Huang

Android 開發|用 easylauncher 為每個版本自動加上專屬 App Icon

前言 在 Android 開發中,專案預設會有 debug 和 release 這兩種 build type,用來區分開發環境與正式環境。根據專案規模與開發流程,有些團隊甚至會進一步細分為 alpha、beta 等不同的 build variant,用以區隔開發的不同階段。 對開發者或測試人員來說,在測試機上同時安裝好幾個版本的 App 是稀鬆平常的事。但問題來了──怎麼快速分辨桌面上哪個 App 是哪個版本? 這時候就有兩個選項可以幫上忙:改 App 名稱 或是 換 Icon。 改名稱其實很簡單,只要在對應 variant 的 strings.xml 裡覆寫 app_name 就搞定了。 至於換 App Icon,雖然理論上可以透過資源目錄的變體(例如 mipmap-debug)手動準備不同版本的 Icon,但這樣不僅麻煩,還得準備好幾套圖示,光想就頭痛。 easylauncher-gradle-plugin 就是專門為這種情境設計的工具。 easylauncher-gradle-plugin 介紹 usefulness/easylauncher-gradle-plugin 是一款可以自動幫你的 App Icon 加上標記(Ribbon)的 Gradle Plugin,能依據不同的 build type 或 variant 自動套用樣式。 你可以自由設定 ribbon 的文字、顏色、位置與樣式,也支援使用圖片當作覆蓋圖層,功能非常齊全。最棒的是──完全自動化,不需要額外準備 Icon! 快速上手 以下是快速設定的方式,如果你想要深入客製化,推薦直接參考官方的 GitHub 文件。 使用 Kotlin DSL(KTS)搭配 libs.versions.toml libs.versions.toml 1 2 3 4 5 6 7 8 [versions] easylauncher = "6.4.1" # 略... [plugins] # 開發版 Icon easylauncher = { id = "com.starter.easylauncher", version.ref = "easylauncher" } build.gradle.kts (module-level) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 plugins { // ... alias(libs.plugins.easylauncher) } // ... easylauncher { buildTypes { create("debug") { setFilters(chromeLike("dev")) } } } 📢 如果沒有特別設定 ribbon 樣式,預設會加上一個淡綠色的 ribbon,文字會是目前的 build type 或 variant 名稱。 ...

Published on May 15, 2025 · 2 min · 227 words · Daniel Huang

Android 開發 | 讓 Gradle 自動為 AAR、AAB、APK 設定客製化檔名

每次打包 AAB 或 APK,Android Studio 預設都會產出像 app-release.aab 或 app-debug.apk 這類的檔案名稱。這種預設命名在使用 Firebase App Distribution 或 Google Play 內部測試流程時,雖然不太會造成困擾,但如果你需要將 APK 或 AAB 檔案直接提供給 PM、QA 或外部合作夥伴安裝測試,那麼能夠一眼看出檔案版本與類型,會讓流程更順暢也更不容易搞混。 本文將示範如何透過 Gradle 設定,讓你在打包時自動加上版本號、Build Type 等資訊,生成便於辨識的輸出檔名。 設定方式 你可以根據不同的產出檔案類型(AAB、APK、AAR),在 app module 或 library module 的 build.gradle 中加入以下設定。 AAB:變更 App Bundle 檔名 在 build.gradle(app module)中 android 區塊內加入以下設定: 1 2 3 4 android { // 其他設定省略... setProperty("archivesBaseName", "taiwanNo1") } BTW: setProperty(key, value) 在 groovy 與 kts 都是通用的。 🚨 注意: 這個設定只會影響輸出檔案名稱的前綴,並不會完整覆蓋檔名結構。例如: 1 2 原始檔名:app-release.aab 變更後:taiwanNo1-release.aab 💡 為什麼使用 archivesBaseName ? 因為目前 Gradle 尚未提供官方 API 可以直接命名 AAB 檔。 archivesBaseName 是 Gradle 用來設定各類 archives 類檔案輸出名稱的共通屬性。 所謂的 archives 檔案包含: APK、AAR、JAR、ZIP 等。 ...

Published on April 30, 2025 · Updated on May 14, 2025 · 2 min · 283 words · Daniel Huang

Android 開發 | 讓 Gradle 自動在版本名稱後加上 Build 時間

在開發 App 的過程中,常常需要產出不同版本的 APK,給團隊成員或測試人員驗證功能。 這時候,「快速辨識版本」就變成一件非常重要的事了。 最常見、也最直接的方法,就是利用 Gradle 的 versionNameSuffix,在 App 的版本名稱後面自動加上一些額外資訊。 以我們團隊的習慣來說,我們會直接加上Build 當下的時間作為版本流水號,這樣每個 APK 都能有獨一無二的識別。但如果每次出新版本,都還要手動去改 Gradle 設定,實在是有點麻煩。 下面就分享一個簡單的做法,讓 Gradle 在每次 Build 的時候,自動把「打包時間」加到版本名稱後面。 程式碼範例 Groovy 寫法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 產生日期時間字串 def getDate() { def date = new Date() def formattedDate = date.format('yyyyMMddHHmmss') return formattedDate } // 在buildType中就可使用 debug { applicationIdSuffix '.debug' // 設定版本名稱後綴 versionNameSuffix '-dev' + ' (' + getDate() + ')' // 其他設定... } Kotlin DSL (KTS) 寫法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 android { // 其他設定... buildTypes { // 略... debug { isMinifyEnabled = false applicationIdSuffix = ".dev" // 設定版本名稱後綴 versionNameSuffix = "-dev (${getDate()})" } } } /** * 取得目前的日期時間字串 * * @return */ fun getDate(): String { val date = Calendar.getInstance().time val formatter = SimpleDateFormat("yyyyMMdd-HHmm", Locale.getDefault()) return formatter.format(date) } 這樣設好之後,每次打包 Debug 版 APK,都會自動帶上像 -dev(20250428-1430) 這種字串, 讓相關人員清楚知道這份檔案是什麼時候打包的。 ...

Published on April 28, 2025 · 1 min · 168 words · Daniel Huang