- 一位 Android 工程師,每天對於如何在茫茫技術海中存活感到憂慮。
- 每次動筆都不斷告訴自已:筆記不是為了別人,而是為了自己。
- 在這裡,我將分享我在開發中經歷的大小事,不論是各種奇怪的 Bug,還是學習過程中的心得與反思;偶爾也會夾雜一些因為興趣而產生的分享。
- 希望每個走進來的朋友,都能在這裡找到對你有幫助的資訊。
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() 方法來釋放資源。 ...

Android Studio Gemini Code Assist 安全設定:使用 .aiexclude 保護敏感資料
前言 許多開發者已經習慣使用各種 AI 工具來加速開發流程。對於 Android 開發者來說,如果不想額外付費,Google 官方推出的 Gemini Code Assist 無疑是最佳選擇之一。 不過,為了讓 Gemini 能提供貼近專案脈絡的建議,它需要讀取你專案內的檔案。但你是否曾經思考過:哪些檔案應該避免被 AI 存取? 保護機敏資料 這點就像我們使用 Git 時會建立 .gitignore 來排除不該同步的檔案一樣——像是金鑰、憑證等敏感資料。 在使用 Web 版 Gemini 時,避免提供檔案相對簡單;但在 Android Studio 中,則需要透過特定的機制進行設定。而這項機制,就是本文主角:.aiexclude 檔案。 認識 .aiexclude .aiexclude 是什麼? .aiexclude 的作用,就如同 .gitignore,放在專案資料夾下,告訴 Gemini 哪些檔案或資料夾應該排除在外、不被存取或索引。 .aiexclude 的語法規則與 .gitignore 完全一致,且支援路徑、萬用字元(如 *、**)、副檔名等。 實用語法範例 語法 說明 dev.properties 排除目錄中所有名稱為 dev.properties 的檔案 KEYS.* 排除所有名稱為 KEYS、任意副檔名的檔案 *.api 排除所有 .api 副檔名的檔案 /*.kt 僅排除 .aiexclude 所在目錄下的 .kt 檔案 my/sensitive/dir/ 排除指定目錄與其所有子目錄 my/sensitive/dir/**.txt 排除指定目錄及子目錄下所有 .txt 檔案 my/sensitive/dir/*.txt 僅排除該目錄下的 .txt 檔案,不包含子目錄 只要在專案根目錄(或任一子目錄)建立 .aiexclude 檔案,即可立即生效。 ...
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 名稱。 ...
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 等。 ...
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) 這種字串, 讓相關人員清楚知道這份檔案是什麼時候打包的。 ...