Haseeb

開場鉤子

清真寺內一片寂靜。數百人正深深俯伏在有節奏的禮拜中,此時一陣刺耳的合成鈴聲如刀劃破寧靜。那是我的手機。我因為專注於早晨通勤,完全忘記切換到靜音模式。當上百道目光轉向我時,那股尷尬瞬間湧上心頭。這不僅是設定失誤,更是個人紀律的徹底崩潰。那一刻成為了 Muffle 誕生的契機。

問題

我們生活在高情境環境的世界中,人們期望手機成為隱形夥伴。無論是在會議室、講堂還是禮拜場所,手機鈴聲所帶來的社會成本絕非零。Android 現有解決方案的碎片化問題眾所周知。內建的「請勿打擾」(DND)排程雖有幫助,但卻是靜態的。它們無法因應人類很少嚴格遵循 9 至 5 作息的現實。

我意識到,手動干預是主要失敗點。如果依賴記憶來將裝置靜音,總會在某個時刻失敗。我需要一個理解情境的系統。我希望手機知道我正在辦公室、健身房或會議中,而無需伸手掏出手機切換模式。我評估過的大多數第三方應用程式都依賴耗電的輪詢式定位服務,電池在四小時內就會耗盡。我不想要像寄生蟲一樣消耗 CPU 的背景服務。我想要一個精準、事件驅動的架構,只有在地理位置真正改變時才喚醒。

技術決策/實作

為了打造 Muffle,我放棄了持續的定位輪詢,改用 com.google.android.gms.location 套件中的 GeofencingClient。核心架構決策是將繁重工作交給 Google Play 服務的融合定位供應器。我沒有管理自己的 LocationManager 更新(這需要有效的喚醒鎖與持續 GPS 輪詢),而是向系統註冊圓形地理圍欄。

當使用者定義位置時,我會建立一個 GeofencingRequest,加入帶有特定半徑的 Geofence 物件。系統隨後在作業系統層級監控這些邊界。我的應用程式在背景保持閒置,直到發生轉換。一旦使用者跨越邊界,系統就會將 Intent 廣播給我的 BroadcastReceiver。這是關鍵所在:只有在作業系統通知我使用者進入或離開該區域時,我的程式碼才會執行 AudioManager 轉換。

kotlin
val geofence = Geofence.Builder()
.setRequestId(routine.id)
.setCircularRegion(lat, lng, radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()

這種方式確保我不會在迴圈中不斷計算 distanceTo() 結果。透過讓融合定位供應器處理數學運算,我將電池消耗委託給系統,而系統對硬體中斷的優化遠勝於我在 Kotlin 中撰寫的任何邏輯。我也實作了 Priority 系統。若使用者有兩個重疊的地理圍欄,本機 SQLite 資料庫將作為單一事實來源。BroadcastReceiver 會先檢查資料庫中目前最高優先順序的有效例行程序,再發出 AudioManager.setRingerMode() 指令。這避免了經典的「閃爍」問題,即兩個地理圍欄爭奪音量狀態。

讓我驚訝之處/若重來會做什麼改變

真正讓我謙卑的是都市峽谷中 GPS 訊號的不穩定。我最初假設 Geofence 觸發會是即時的。我預期使用者一走進建築物,手機就會靜音。實際上,在高樓大廈反射衛星訊號的密集市中心,訊號延遲意味著 ENTER 轉換有時會在距離目標兩個街區外才觸發。

我慘痛地學到,在城市中僅依賴 GPS 進行地理圍欄是必敗之戰。如果重新開始,我會實作混合式方法。我會以 Wi-Fi 訊號指紋輔助 Geofence API。透過掃描附近路由器的 MAC 位址,我可以比原始 GPS 座標更可靠地驗證位置轉換。我也發現 AlarmManager 在不同 OEM 介面上的行為不一。某些注重積極電池管理的製造商偶爾會終結我的背景服務,儘管我已實作 ForegroundService

我必須撰寫特定邏輯,在裝置收到 BOOT_COMPLETED 廣播時重新註冊地理圍欄。否則,單純的系統更新或重新開機會讓使用者永遠保持靜音,因為「離開」轉換從未觸發。這裡的教訓是,在 Android 上不能信任作業系統維護你的狀態。你必須將應用程式視為隨時可能被從記憶體中清除,並設計能承受突然、非正常終止的資料持久化機制。

實務收穫

如果你正在開發 Android 工具,請停止思考「永遠開啟」的邏輯。最優雅的解決方案是盡可能將責任卸載給作業系統。每當你撰寫自訂背景迴圈時,你很可能錯過了一個原生 API,它能以 10% 的能耗完成相同工作。使用系統的廣播觸發器。對於延遲任務,請依賴 WorkManager,而位置感知功能則使用 GeofencingClient

不要與 Android 系統的電池優化對抗;學會在其中運作。當你的應用程式未出現在電池使用量明細中時,使用者會有所察覺。如果你正在為管理音效設定檔或自動化裝置狀態而苦惱,可以看看我是如何在 Muffle 中處理這些限制的。這是一個完全建立在「只在必要時才做事」理念上的專案,並將使用者資料保留在裝置本機。你可以在 https://play.google.com/store/apps/details?id=com.muffle.app 探索這些例行程序的實作細節與邏輯。為真實世界開發,就是承認硬體的混亂,並撰寫能承受雜訊的程式碼。