基于Room+RESTful的雙權限Android開機時間監控方案

概述

以下是使用Kotlin實現的商業級Android開機時間記錄功能,包含現代Android開發最佳實踐。

系統架構

  1. 組件設計
// BootReceiver - 接收開機廣播
class BootReceiver : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent?) {if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {BootTimeLogger.logBootTime(context)}}
}// BootTimeLogger - 核心日志記錄類
object BootTimeLogger {private const val TAG = "BootTimeLogger"fun logBootTime(context: Context) {val bootTime = System.currentTimeMillis()// 記錄到本地存儲saveBootTimeLocally(context, bootTime)// 異步上傳到服務器uploadBootTimeToServer(context, bootTime)}private fun saveBootTimeLocally(context: Context, bootTime: Long) {// 實現本地存儲}private fun uploadBootTimeToServer(context: Context, bootTime: Long) {// 實現服務器上傳}
}

完整實現代碼

  1. AndroidManifest.xml 配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.example.boottimelogger"><uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.WAKE_LOCK" /><applicationandroid:name=".BootTimeApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><!-- 開機廣播接收器 --><receiverandroid:name=".receiver.BootReceiver"android:enabled="true"android:exported="true"tools:ignore="ExportedReceiver"><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /><action android:name="android.intent.action.QUICKBOOT_POWERON" /><action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></receiver><!-- 上傳服務 --><serviceandroid:name=".service.BootTimeUploadService"android:exported="false" /><!-- WorkManager配置 --><providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge"><meta-dataandroid:name="androidx.work.WorkManagerInitializer"android:value="androidx.startup" /></provider></application>
</manifest>
  1. Application類
class BootTimeApplication : Application() {override fun onCreate() {super.onCreate()initWorkManager()initCrashReporting()}private fun initWorkManager() {// WorkManager配置val config = Configuration.Builder().setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.ERROR).build()WorkManager.initialize(this, config)}private fun initCrashReporting() {// 初始化錯誤報告(可選)if (!BuildConfig.DEBUG) {// FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)}}
}
  1. 數據模型
data class BootTimeRecord(val id: Long = 0,val timestamp: Long,val uploaded: Boolean = false,val deviceId: String = "",val osVersion: String = Build.VERSION.RELEASE,val appVersion: String = BuildConfig.VERSION_NAME
)@Entity(tableName = "boot_times")
data class BootTimeEntity(@PrimaryKey(autoGenerate = true) val id: Long = 0,@ColumnInfo(name = "timestamp") val timestamp: Long,@ColumnInfo(name = "uploaded") val uploaded: Boolean = false,@ColumnInfo(name = "device_id") val deviceId: String = "",@ColumnInfo(name = "os_version") val osVersion: String = Build.VERSION.RELEASE,@ColumnInfo(name = "app_version") val appVersion: String = BuildConfig.VERSION_NAME,@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
)
  1. Room數據庫
@Dao
interface BootTimeDao {@Insertsuspend fun insert(record: BootTimeEntity): Long@Query("SELECT * FROM boot_times WHERE uploaded = 0 ORDER BY timestamp ASC")suspend fun getPendingRecords(): List<BootTimeEntity>@Query("UPDATE boot_times SET uploaded = 1 WHERE id = :id")suspend fun markAsUploaded(id: Long)@Query("SELECT COUNT(*) FROM boot_times WHERE uploaded = 0")suspend fun getPendingCount(): Int
}@Database(entities = [BootTimeEntity::class], version = 1, exportSchema = true)
abstract class BootTimeDatabase : RoomDatabase() {abstract fun bootTimeDao(): BootTimeDaocompanion object {@Volatileprivate var INSTANCE: BootTimeDatabase? = nullfun getInstance(context: Context): BootTimeDatabase {return INSTANCE ?: synchronized(this) {val instance = Room.databaseBuilder(context.applicationContext,BootTimeDatabase::class.java,"boot_time_database").apply {if (BuildConfig.DEBUG) {fallbackToDestructiveMigration()}}.build()INSTANCE = instanceinstance}}}
}
  1. 核心管理類
class BootTimeManager private constructor(context: Context) {private val database: BootTimeDatabase = BootTimeDatabase.getInstance(context)private val dao: BootTimeDao = database.bootTimeDao()private val prefs: SharedPreferences = context.getSharedPreferences("boot_time_prefs", Context.MODE_PRIVATE)private val deviceId: String by lazy {prefs.getString("device_id", "") ?: run {val newId = UUID.randomUUID().toString()prefs.edit { putString("device_id", newId) }newId}}companion object {@Volatileprivate var INSTANCE: BootTimeManager? = nullfun getInstance(context: Context): BootTimeManager {return INSTANCE ?: synchronized(this) {val instance = BootTimeManager(context.applicationContext)INSTANCE = instanceinstance}}}suspend fun recordBootTime(timestamp: Long = System.currentTimeMillis()): Long {return withContext(Dispatchers.IO) {val record = BootTimeEntity(timestamp = timestamp,deviceId = deviceId)dao.insert(record)}}suspend fun getPendingRecords(): List<BootTimeEntity> {return withContext(Dispatchers.IO) {dao.getPendingRecords()}}suspend fun markAsUploaded(id: Long) {withContext(Dispatchers.IO) {dao.markAsUploaded(id)}}suspend fun getPendingCount(): Int {return withContext(Dispatchers.IO) {dao.getPendingCount()}}fun getLastBootTime(): Long {return prefs.getLong("last_boot_time", 0)}private fun setLastBootTime(timestamp: Long) {prefs.edit { putLong("last_boot_time", timestamp) }}
}
  1. 廣播接收器(Kotlin優化版)
class BootReceiver : BroadcastReceiver() {private val tag = "BootReceiver"override fun onReceive(context: Context, intent: Intent?) {if (intent == null) returnval action = intent.actionlogDebug("Received broadcast: $action")when (action) {Intent.ACTION_BOOT_COMPLETED,"android.intent.action.QUICKBOOT_POWERON","android.intent.action.LOCKED_BOOT_COMPLETED" -> {handleBootCompleted(context)}}}private fun handleBootCompleted(context: Context) {if (!isAppInitialized(context)) {logDebug("App not initialized, scheduling delayed logging")scheduleDelayedLogging(context)return}recordBootTime(context)scheduleUploadWork(context)}private fun isAppInitialized(context: Context): Boolean {// 檢查必要的組件是否已初始化return try {BootTimeManager.getInstance(context)true} catch (e: Exception) {false}}private fun recordBootTime(context: Context) {CoroutineScope(Dispatchers.IO).launch {try {val bootTime = System.currentTimeMillis()val recordId = BootTimeManager.getInstance(context).recordBootTime(bootTime)logDebug("Boot time recorded successfully: $recordId")} catch (e: Exception) {logError("Failed to record boot time", e)}}}private fun scheduleDelayedLogging(context: Context) {val workRequest = OneTimeWorkRequestBuilder<DelayedBootWorker>().setInitialDelay(1, TimeUnit.MINUTES).setBackoffCriteria(BackoffPolicy.LINEAR,OneTimeWorkRequest.MIN_BACKOFF_MILLIS,TimeUnit.MILLISECONDS).build()WorkManager.getInstance(context).enqueue(workRequest)}private fun scheduleUploadWork(context: Context) {val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).setRequiresBatteryNotLow(true).build()val workRequest = OneTimeWorkRequestBuilder<BootTimeUploadWorker>().setConstraints(constraints).setBackoffCriteria(BackoffPolicy.EXPONENTIAL,30, TimeUnit.SECONDS).build()WorkManager.getInstance(context).enqueue(workRequest)}private fun logDebug(message: String) {if (BuildConfig.DEBUG) {Log.d(tag, message)}}private fun logError(message: String, e: Exception? = null) {Log.e(tag, message, e)// 可集成錯誤報告系統}
}
  1. WorkManager Worker
class BootTimeUploadWorker(context: Context,params: WorkerParameters
) : CoroutineWorker(context, params) {override suspend fun doWork(): Result {return withContext(Dispatchers.IO) {try {logDebug("Starting boot time upload work")val manager = BootTimeManager.getInstance(applicationContext)val pendingRecords = manager.getPendingRecords()if (pendingRecords.isEmpty()) {logDebug("No pending records to upload")return@withContext Result.success()}logDebug("Found ${pendingRecords.size} records to upload")var successCount = 0pendingRecords.forEach { record ->if (uploadRecord(record)) {manager.markAsUploaded(record.id)successCount++}}logDebug("Upload completed: $successCount/${pendingRecords.size} successful")if (successCount == pendingRecords.size) {Result.success()} else {Result.retry()}} catch (e: Exception) {logError("Upload work failed", e)Result.retry()}}}private suspend fun uploadRecord(record: BootTimeEntity): Boolean {return try {// 實現實際的上傳邏輯val apiService = createApiService()val response = apiService.uploadBootTime(record.toDto())response.isSuccessful} catch (e: Exception) {logError("Failed to upload record ${record.id}", e)false}}private fun BootTimeEntity.toDto(): BootTimeDto {return BootTimeDto(timestamp = timestamp,deviceId = deviceId,osVersion = osVersion,appVersion = appVersion)}private fun createApiService(): BootTimeApiService {// 創建Retrofit服務實例TODO("Implement API service creation")}private fun logDebug(message: String) {if (BuildConfig.DEBUG) {Log.d("BootTimeUploadWorker", message)}}private fun logError(message: String, e: Exception) {Log.e("BootTimeUploadWorker", message, e)}
}class DelayedBootWorker(context: Context,params: WorkerParameters
) : CoroutineWorker(context, params) {override suspend fun doWork(): Result {return withContext(Dispatchers.IO) {try {logDebug("Executing delayed boot recording")BootTimeManager.getInstance(applicationContext).recordBootTime()Result.success()} catch (e: Exception) {logError("Delayed boot recording failed", e)Result.retry()}}}
}
  1. API相關類
data class BootTimeDto(val timestamp: Long,val deviceId: String,val osVersion: String,val appVersion: String,val timezone: String = TimeZone.getDefault().id
)interface BootTimeApiService {@POST("boot-times")suspend fun uploadBootTime(@Body dto: BootTimeDto): Response<Unit>@POST("boot-times/batch")suspend fun uploadBootTimes(@Body dtos: List<BootTimeDto>): Response<Unit>
}class ApiClient private constructor() {companion object {fun createBootTimeService(baseUrl: String): BootTimeApiService {val retrofit = Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(MoshiConverterFactory.create()).client(createHttpClient()).build()return retrofit.create(BootTimeApiService::class.java)}private fun createHttpClient(): OkHttpClient {return OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).addInterceptor(LoggingInterceptor()).addInterceptor(AuthInterceptor()).build()}}
}class LoggingInterceptor : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val request = chain.request()// 添加日志邏輯return chain.proceed(request)}
}class AuthInterceptor : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val request = chain.request().newBuilder().addHeader("Authorization", "Bearer your_token_here").addHeader("Content-Type", "application/json").build()return chain.proceed(request)}
}
  1. 依賴注入(可選,使用Hilt)
@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideBootTimeDatabase(@ApplicationContext context: Context): BootTimeDatabase {return BootTimeDatabase.getInstance(context)}@Provides@Singletonfun provideBootTimeDao(database: BootTimeDatabase): BootTimeDao {return database.bootTimeDao()}@Provides@Singletonfun provideBootTimeManager(@ApplicationContext context: Context): BootTimeManager {return BootTimeManager.getInstance(context)}@Provides@Singletonfun provideBootTimeApiService(): BootTimeApiService {return ApiClient.createBootTimeService("https://your-api-url.com/")}
}@HiltAndroidApp
class BootTimeApplication : Application()

Gradle依賴配置

// build.gradle.kts (Module)
dependencies {// Roomimplementation("androidx.room:room-runtime:2.6.0")implementation("androidx.room:room-ktx:2.6.0")ksp("androidx.room:room-compiler:2.6.0")// WorkManagerimplementation("androidx.work:work-runtime-ktx:2.8.1")// Coroutinesimplementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")// Retrofitimplementation("com.squareup.retrofit2:retrofit:2.9.0")implementation("com.squareup.retrofit2:converter-moshi:2.9.0")implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")// Hilt (可選)implementation("com.google.dagger:hilt-android:2.48.1")ksp("com.google.dagger:hilt-compiler:2.48.1")// Timber (日志)implementation("com.jakewharton.timber:timber:5.0.1")
}

商業級特性

  1. 性能優化: 使用協程異步處理,避免阻塞主線程
  2. 穩定性: WorkManager自動重試,處理網絡異常
  3. 數據持久化: Room數據庫確保數據不丟失
  4. 電量友好: 批量上傳,智能調度
  5. 隱私合規: 設備ID匿名化處理
  6. 錯誤處理: 完善的異常捕獲和重試機制
  7. 可擴展性: 模塊化設計,易于擴展功能

這個Kotlin實現采用了現代Android開發的最佳實踐,包括協程、Room、WorkManager等組件,確保了代碼的簡潔性、可維護性和商業級的可靠性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921802.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921802.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921802.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

水庫大壩安全監測系統的作用

水庫大壩作為重要的水利基礎設施&#xff0c;承擔著防洪、供水、發電、灌溉等多重功能&#xff0c;其安全性直接關系到人民生命財產安全和社會經濟發展。然而&#xff0c;由于自然環境變化、材料老化、荷載作用以及人為因素的影響&#xff0c;大壩在長期運行過程中可能出現裂縫…

《Kubernetes 構建 MySQL MGR 集群實戰教程》

#### 一、前言 MySQL Group Replication (MGR) 是 MySQL 官方提供的高可用集群方案&#xff0c;基于 Paxos 協議實現多節點數據強一致性。本教程將指導如何在 Kubernetes 上部署 MySQL MGR 集群&#xff0c;適用于生產級高可用場景。---#### 二、環境準備 1. **Kubernetes 集…

影視APP源碼 SK影視 安卓+蘋果雙端APP 反編譯詳細視頻教程+源碼

內容目錄一、詳細介紹二、效果展示1.部分代碼2.效果圖展示三、學習資料下載一、詳細介紹 影視APP源碼 SK影視 安卓蘋果雙端APP 反編譯詳細視頻教程源碼 自帶對接優效SDK廣告&#xff08;已失效&#xff09;。域名和IP都可以搭建。 自帶一起看和短劇頁面功能&#xff0c;三種…

pyqt+python之二進制生肖占卜

目錄 一、引言 二、GUI界面設計 1.效果演示 2.相關提示 3.界面設計.py 三、主要程序詳解 1.導入相關模塊 2.初始化設置 3.組內判斷 4.猜測過程 四、總程序代碼 一、引言 在數字時代&#xff0c;傳統文化與編程語言的碰撞總能迸發奇妙火花。本項目以PyQtPython為技術…

人工智能-python-深度學習-經典網絡模型-LeNets5

文章目錄LeNet-5&#xff08;詳解&#xff09;—— 從原理到 PyTorch 實現&#xff08;含訓練示例&#xff09;簡介LeNet-5 的核心思想LeNet-5 逐層結構詳解逐層計算舉例&#x1f4cc; 輸入層&#x1f4cc; C1 卷積層&#x1f4cc; S2 池化層&#x1f4cc; C3 卷積層&#x1f4…

機器視覺的手機柔性屏貼合應用

在智能手機制造領域&#xff0c;柔性屏逐漸成為智能手機的主流選擇&#xff0c;柔性屏因其輕便、易于彎曲的特性&#xff0c;已成為現代電子設備的重要組成部分&#xff0c;但同時也帶來了前所未有的制造挑戰。柔性屏與傳統剛性玻璃屏有本質區別&#xff0c;它容易形變&#xf…

貪心算法應用:數字孿生同步問題詳解

Java中的貪心算法應用&#xff1a;數字孿生同步問題詳解 貪心算法是一種在每一步選擇中都采取在當前狀態下最好或最優&#xff08;即最有利&#xff09;的選擇&#xff0c;從而希望導致結果是全局最好或最優的算法。下面我將全面詳細地講解貪心算法在數字孿生同步問題中的應用。…

UOS20系統安裝與 SSH/XRDP 遠程訪問功能配置指南

UOS20系統安裝與 SSH/XRDP 遠程訪問功能配置指南 一、UOS 20 系統安裝? ?1. 下載系統鏡像? 訪問統信官網下載 UOS 20 專業版鏡像&#xff08;推薦適配當前硬件的版本&#xff09;&#xff1a; https://www.chinauos.com/resource/download-professional 2. 系統安裝與硬件配…

【Python】S1 基礎篇 P5 字典模塊指南

目錄字典的本質與底層實現基礎語法結構使用字典訪問字典中的值添加鍵值對修改字典中的值刪除鍵值對使用 get() 來訪問值遍歷字典遍歷所有鍵值對遍歷字典中的所有鍵遍歷字典中的所有值嵌套字典列表在字典中存儲列表字典&#xff08;Dictionary&#xff09;是Python中靈活且強大的…

計算機視覺之多模板匹配

簡介 計算機視覺第一課opencv&#xff08;四&#xff09;保姆級教學 之前說過模糊匹配只是對于單個目標進行匹配&#xff0c;今天我們就來學習一下如何對多個目標進行匹配 一、多目標匹配 對于這個圖片我們要匹配下面那個箭頭&#xff0c;我們可以發現圖中是有兩個位置相同的…

封裝日期選擇器組件,帶有上周,下周按鈕

ui圖組件代碼如下&#xff1a; <template><div><el-date-pickerv-model"dateRange"type"daterange"align"right"size"mini":editable"false"unlink-panelsrange-separator"至"start-placeholder&q…

基于SpringBoot+MYSQL開發的AI智能大數據醫療診斷平臺

角色&#xff1a; 管理員、醫生、居民 技術&#xff1a; SpringBoot、MyBatis、MySQL、Shiro、Beetl、Swagger、jQuery、Bootstrap 核心功能&#xff1a; 這是一個基于SpringBoot的社區醫療管理平臺&#xff0c;旨在為管理員提供用戶、角色、部門、菜單、日志等系統管理功能&am…

【MFC 小白日記】對話框編輯器里“原型圖像”到底要不要勾?3 分鐘看懂!

摘要&#xff1a;本文解析了MFC中Picture Control的"原型圖像(Prototype Image)"屬性的真實作用。該屬性僅在設計時提供可視化的占位圖預覽&#xff0c;方便UI布局&#xff0c;運行時不會影響程序表現。文章通過對比實驗驗證&#xff0c;勾選后會在對話框編輯器中顯示…

微信開放平臺第三方平臺,可以管理多個微信小程序

大家好&#xff0c;我是小悟。 這個系統可以幫助服務商更好地管理多個商家小程序&#xff0c;無需管理多個商家小程序的賬號密碼或者appId和secret&#xff0c;大大提升效率。 不需要頻繁登錄小程序后臺就能完成上傳代碼、認證、備案、提交代碼審核、發布小程序等操作。 這里錄…

Java全棧學習筆記32

-- drop table t_stu;-- unique 唯一約束的列允許為null-- 如果在之后的操作中。需要某列必須為key&#xff0c;才能做一些操作的情況下。也可以使用唯一約束代替主鍵約束-- create table t_stu(-- studId int,-- tel varchar(11) unique,-- sex varchar(1),-- addr va…

linux升級系統,重啟出現Minimal BASH-like line editingis supported

文章目錄一.問題背景二.解決步驟2.1確認系統分區2.2手動引導2.3 重建grub引導2.4 還原軟件包 一.問題背景 閑來無事&#xff0c;把ubuntu25.04通過sudo do-release-upgrade命令升級到了ubuntu25.10.在升級的過程會出現以下問題 1.自動替換flatpak程序為snap2.請求是否清除舊依賴…

type(類型別名)和 interface的區別和最佳實踐

核心結論在大多數情況下&#xff0c;它們可以互換使用&#xff0c;都能描述對象的結構。它們的區別更多在于設計和擴展能力上。主要區別總結表特性interface (接口)type (類型別名)擴展方式使用 extends 繼承interface A extends B {}使用 & 交叉類型type A B & C合并…

vscode中使用git、githup的基操

一、git提交 配置賬戶密碼 查看用戶信息 git config --global user.name # 查看用戶名 git config --global user.email # 查看郵箱賬戶配置 # 設置全局用戶名和郵箱 git config --global user.name "你的用戶名" git config --global user.email "你的郵箱&q…

jsBridge接入流程

import deviceInfo from ./deviceInfo import { setRefreshToken } from ./token// 設備判斷 const u navigator.userAgent export const isAndroid u.indexOf(Android) > -1 || u.indexOf(Adr) > -1 export const isIOS !!u.match(/\(i[^;];( U;)? CPU.Mac OS X/)…

【C++】19. 封裝紅?樹實現set和map

文章目錄一、源碼及框架分析二、模擬實現map和set1、insert的實現2、iterator的實現3、map?持[ ]4、模擬實現的完整源代碼1&#xff09;RBTree.h2&#xff09;Myset.h3&#xff09;Mymap.h4&#xff09;Test.cpp一、源碼及框架分析 SGI-STL30版本源代碼&#xff0c;map和set的…