如何在 Android 上取得準確且不被用戶竄改的時間

一、前言 前陣子專案有一項需求,是希望 App 能夠取得準確且不會被使用者竄改的時間。 研究了一下 StackOverflow: How do I get the most accurate time with Android? 上面的討論,決定整理成一篇完整的筆記,也許能拯救到未來的自己? XD 1-1 在 Android 中取得當下時間 大家應該都會直覺地想到以下幾個方式: System.currentTimeMillis()。 Calendar.getInstance() Instant.now() 但這幾個方式,拿到的時間都是所謂的系統時間,只要使用者到系統設定中手動調整,就可以輕易地修改這幾個方式回傳的時間。 二、Android SDK 的 SystemClock Android 的 SystemClock 類別中,有提供以下兩個方法可以拿到準確的時間。 2-1 SystemClock.currentGnssTimeClock() 時間跟稍後會提到的LocationManager.GPS_PROVIDER拿到的時間一樣。 官方文件 SystemClock.currentGnssTimeClock() Returns a Clock that starts at January 1, 1970 00:00:00.0 UTC, synchronized using the device’s location provider. API 29 以上 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import android.os.Build import android.os.SystemClock import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter fun getGpsApiDateTime(): String { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { try { val zoneClock = SystemClock.currentGnssTimeClock().withZone(ZoneId.of("Asia/Taipei")) ZonedDateTime.now(zoneClock).format(dateTimeFormatter) } catch (e: Exception) { "GNSS NOT AVAILABLE" } } else { "SYSTEM NOT AVAILABLE" } } :::danger 📢 注意! 在部分裝置上,GNSS API 回傳的時間有可能是錯誤的… 細節請看 問題-GNSS-API-與-Location-Provider-所拿到的時間可能是錯誤的。 ::: ...

Published on August 26, 2024 · 6 min · 1093 words · Daniel Huang

Android 14 AlarmManager SCHEDULE_EXACT_ALARM 權限閃退

一、問題描述 Android 14 的使用者反映應用程式閃退,經查測後發現是設定指定時間提醒功能時出現閃退。 1 Fatal Exception: java.lang.SecurityException: Caller com.tms.qpass needs to hold android.permission.SCHEDULE_EXACT_ALARM or android.permission.USE_EXACT_ALARM to set exact alarms. 從錯誤訊息可以清楚看到,問題發生當下是拋出 SecurityException 。 二、錯誤原因說明 其實從錯誤訊息中就可略知一二,一切都跟 SCHEDULE_EXACT_ALARM 還有 USE_EXACT_ALARM 有關。 ✍ 如果是日曆或鬧鐘應用程式,應該要宣告的是 USE_EXACT_ALARM 精確鬧鐘權限。 USE_EXACT_ALARM 是一項即將推出的新權限,作用是針對以 Android 13 (API 級別 33) 以上版本為目標的應用程式,授予精確鬧鐘功能的存取權。 USE_EXACT_ALARM 是一項受限制權限,只有當應用程式的核心功能支援精確鬧鐘需求,應用程式才能宣告這項權限。要求這項受限制權限的應用程式必須接受審查,若是應用程式不符合使用限制條件,就禁止在 Google Play 發布 核心功能: - 應用程式是鬧鐘或計時器應用程式。 - 應用程式是會顯示活動通知的日曆應用程式。 上述以外的用途,則應評估能否選擇使用 SCHEDULE_EXACT_ALARM 做為替代方案。 以上整理自 Google Play Policy。 SCHEDULE_EXACT_ALARM 權限 💡 適用的精準鬧鐘 API setExact() setExactAndAllowWhileIdle() setAlarmClock() Android 12 以前 可直接使用 API。 Android 12 後 從 Android 12 開始,所有 Target SDK 為 Android 12 的 App ,如果要使用 AlarmManager 的精準鬧鐘 API,都必須要在 AndroidManifest.xml 中宣告SCHEDULE_EXACT_ALARM 權限。 ...

Published on March 27, 2024 · 2 min · 294 words · Daniel Huang

印出 API 的 Log--OkHttp HttpLoggingInterceptor (Retrofit 適用)

介紹 以往為了檢查 API 呼叫的 Request 或 Response 是否正確,都是土法煉鋼式的自行加註 Log。後來改用 Retrofit 之後,發現要加 Log 變得好麻煩,這才發現原來有 HttpLoggingInterceptor 可以自動印 Log 。 Java Doc: HttpLoggingInterceptor How to log request and response body with Retrofit-Android? 👀 OkHttp 的 Interceptors 介面有許多實作的類別,可以針對 API 呼叫做很多不一樣的事情,有興趣的讀者可以多利用這個關鍵字下去尋找相關的資料,本篇文章就不贅述。 使用 1. add gradle dependency 1 implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0' 🚨 注意! 套件引用的版本,請以官方最新版本為準。 2. 新增 HttpLoggingInterceptor 到 OkHttpClient 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private fun getOkHttpClient(): OkHttpClient { val loggingInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().setLevel( if (BuildConfig.DEBUG) { // Debug 模式下才開啟 HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } ) return OkHttpClient.Builder() .addInterceptor(loggingInterceptor) // ... .build() } 3. 正常呼叫 API Logcat 就會出現相關的 Log 囉 Level.NONE 什麼都不印 ...

Published on March 14, 2024 · 2 min · 308 words · Daniel Huang

Android 開發|一鍵清除 App 資料,還原初始狀態的實作方法

問題說明 前陣子剛好海巡到 一則 iT 邦幫忙上的技術提問,大略找了一下資料,發現實作細節不難就順手回了一下。對方最後成功地完成他預期的需求,很高興能夠幫到對方的忙~ 提問問題如下: 📢 App 需要新增一個刪除帳號的功能,需求方期望按下刪除鈕後,App 能夠回到初始安裝的狀態。等同於用戶直接到手機系統設定的應用程式清單中,按下清除資料。 解決方案 Android 4.4 以後請用: ActivityManager.clearApplicationUserData() 根據文件的說明,呼叫 ActivityManager.clearApplicationUserData() 等同使用者於設定中按下清除資料。 Android API 19 以上可用。 內部 (Internal) 與外部 (External) 的應用程式私有資料都會被清除。 所有已取得的動態請求權限都會被撤銷。 1 2 val activityManager = application.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager activityManager.clearApplicationUserData() 📢 注意: 呼叫此方法後 App 會強制退出,使用者需要自行重新開啟 App。 Android 4.4 以前請用: Runtime.exec() 透過 Runtime 執行 ADB 指令 pm clear packageName。 1 Runtime.getRuntime().exec("pm clear ${applicationContext.packageName}") Reference: StackOverflow: Clear Application’s Data Programmatically 💡 延伸討論: 開發過程中完整清除 App 資料 有時候在開發或測試 App 的時候,也會需要還原 App 的初始狀態。 這時候可以改在 ADB 環境下,執行前述 Runtime 執行的清除指令。 ...

Published on December 5, 2023 · 1 min · 113 words · Daniel Huang

Android 動態更改應用程式圖標 App Icon

一、問題描述 最近使用 Todoist 做任務管理時發現,App 內有提供自選圖標的功能,也就是動態更換 App 的 Launcher Icon。 Step 0 Step 1 Step 2 Step 3 按下按鈕啟用自選圖標功能。 原本 App 預設是紅色的 App Icon。 在 App 內設定指定的 Icon 後。 Launcher 上的 Todoist Icon 就真的換剛才所選的 App Icon 了! 另外一個 App DuckDuckGo 也有一樣的功能。不過操作流程上不太一樣。DuckDuckGo 不需要特別啟用自選圖標,但在每次更換 Icon 時,App 都會在設定完成後自動關閉,使用者必須自動重啟 App 才能繼續使用。 🚩 兩者使用體驗差異整理: Todoist: 首次使用必須啟用功能。 啟用功能後 App 會自行關閉,需要使用者自行重開 App。 重開後就可以使用自選圖標的功能,而且往後的更換行為 App 都不會再自動關閉。 DuckDuckGo: 不須特別啟用功能。 每次更換圖標都會自動關閉 App,使用者要自己重開。 ==如果想直接看怎麼做,可以跳到 三、解決方案。== 二、研究歷程 2-1 問題釐清 搜尋幾個關鍵字其實滿快就找到相關的解法了,基本上分成使用 AndroidManifest.xml 的 activity-alias 更換 App 進入點的參數,或是使用 App Shortcuts。 ...

Published on November 23, 2023 · 7 min · 1349 words · Daniel Huang