Я решил использовать обработчик, работающий в HandlerThread, поскольку я не хочу запускать какие-либо задачи в основном цикле. Уведомления, команды запуска/остановки (см. код TimerService ниже).
Служба запускается моделью представления, которая собирает прошедшее время из службы таймера (см. код TimerViewModel ниже).
В MainActivity проверяются собственные разрешения.
Перед запуском приложения я вручную установил его как «неограниченный» (Разрешить фон). использование).
Он работает правильно (прошедшее время обновляется в уведомлении на переднем плане), пока телефон:
- с приложением на переднем плане или
- в фоновом режиме и заряжается или
- в фоновом режиме, не заряжается, а выполняет беспроводную отладку
/>
На моем Pixel 8 Pro установлена Android 16, приложение compileSdk/targetSdk = 36.
Я был бы признателен, если бы кто-нибудь здесь мог пролить немного света на эту проблему.
Заранее спасибо!
TimerService
class TimerService : Service() {
private var builder: NotificationCompat.Builder? = null
private lateinit var serviceHandler: Handler
private lateinit var handlerThread: HandlerThread
private val timerRunnable: Runnable = object : Runnable {
override fun run() {
serviceHandler.postDelayed(this, ONE_SECOND_MILLIS)
_timerStateFlow.value++
updateNotification(_timerStateFlow.value)
}
}
override fun onCreate() {
super.onCreate()
// Create a new background thread for the handler
handlerThread = HandlerThread("TimerServiceHandlerThread").apply {
start()
}
serviceHandler = Handler(handlerThread.looper)
createNotificationChannel()
_timerStateFlow.value = 0
_isTimerRunning.value = false
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> startTimerService()
ACTION_STOP -> stopTimerService()
}
return START_STICKY
}
private fun startTimerService() {
val notification = createNotification()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(NOTIFICATION_ID, notification)
}
if (!_isTimerRunning.value) {
_timerStateFlow.value = 0
}
startTimer()
}
private fun startTimer() {
if (!_isTimerRunning.value) {
_isTimerRunning.value = true
// Start the timer on the background thread.
// The first tick will be after 1000ms.
serviceHandler.postDelayed(timerRunnable, ONE_SECOND_MILLIS)
}
}
private fun stopTimer() {
// Remove callbacks from the background thread handler.
if (::serviceHandler.isInitialized) {
serviceHandler.removeCallbacks(timerRunnable)
}
_isTimerRunning.value = false
}
private fun stopTimerService() {
stopTimer()
_timerStateFlow.value = 0
stopSelf()
}
private fun createNotification(): Notification {
builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Timer Service Active")
.setSmallIcon(R.drawable.outline_timer_24)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
return builder!!.build()
}
private fun updateNotification(count: Int) {
builder?.let {
val hh = count / 3600
val mm = (count % 3600) / 60
val ss = count % 60
val timeString = String.format("%02d:%02d:%02d", hh, mm, ss)
it.setContentText("timer is running $timeString")
val manager = getSystemService(NotificationManager::class.java)
manager.notify(NOTIFICATION_ID, it.build())
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Timer Service Channel",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
override fun onDestroy() {
stopTimer()
// Quit the background thread.
if (::handlerThread.isInitialized) {
handlerThread.quitSafely()
}
stopForeground(STOP_FOREGROUND_REMOVE)
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
// We don't provide binding, so return null
return null
}
companion object {
const val ACTION_START = "com.example.app.START"
const val ACTION_STOP = "com.example.app.STOP"
const val ACTION_PAUSE = "com.example.app.PAUSE"
const val CHANNEL_ID = "TimerServiceChannel"
const val NOTIFICATION_ID = 1
const val ONE_SECOND_MILLIS = 1000L
private val _timerStateFlow = MutableStateFlow(0)
val timerStateFlow = _timerStateFlow.asStateFlow()
private val _isTimerRunning = MutableStateFlow(false)
val isTimerRunning = _isTimerRunning.asStateFlow()
}
}
TimerViewModel
class TimerViewModel(...) : AndroidViewModel(application) {
private val _state = MutableStateFlow(WorkoutPlayState())
val state by lazy { _state.asStateFlow() }
// Collect the timer value from the service
val elapsedTime: StateFlow = TimerService.timerStateFlow
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = TimerService.timerStateFlow.value // Get current value on init
)
init {
viewModelScope.launch {
launch {
elapsedTime
.collect { newTime ->
Timber.d("Timer tick: $newTime")
}
}
}
}
...
private fun startTimerService() {
val intent = Intent(application.applicationContext, TimerService::class.java).apply {
action = TimerService.ACTION_START
}
startForegroundService(application.applicationContext, intent)
}
}
Манифест
android:value="App background timer"/>
Основное действие
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permissionsToRequest = arrayOf(
Manifest.permission.POST_NOTIFICATIONS
)
if (!hasPermissions(this, *permissionsToRequest)) {
ActivityCompat.requestPermissions(this, permissionsToRequest, 1)
}
}
...
}
...
}
Подробнее здесь: https://stackoverflow.com/questions/797 ... -backgroun
Мобильная версия