fix(player): фон не глохнет — запрос уведомлений + исключение из оптимизации батареи

На OnePlus/ColorOS радио глохло в фоне даже с wake mode. Причины: POST_NOTIFICATIONS
не выдан (медиа-уведомление не показывалось → foreground-сервис хрупкий) и
приложение не в вайтлисте Doze. MainActivity на старте запрашивает POST_NOTIFICATIONS
(13+), затем системный диалог REQUEST_IGNORE_BATTERY_OPTIMIZATIONS (один раз).
v1.4 / versionCode 5 (clean-сборка).
This commit is contained in:
nk
2026-06-07 14:42:00 +03:00
parent 69f48d235e
commit 07f56acf27
3 changed files with 46 additions and 2 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId = "com.radiola"
minSdk = 26
targetSdk = 34
versionCode = 4
versionName = "1.3"
versionCode = 5
versionName = "1.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -16,6 +16,8 @@
<!-- Держать CPU/Wi-Fi активными во время проигрывания при выключенном экране
(иначе поток глохнет в фоне — особенно в машине по Bluetooth). -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Просить исключение из оптимизации батареи (Doze/ColorOS душат фоновое аудио). -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".RadiolaApplication"

View File

@@ -1,5 +1,6 @@
package com.radiola
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -49,6 +50,11 @@ class MainActivity : ComponentActivity() {
@Inject
lateinit var settingsRepository: com.radiola.domain.repository.SettingsRepository
// После ответа на запрос уведомлений — просим исключение из оптимизации батареи.
private val notifPermLauncher = registerForActivityResult(
androidx.activity.result.contract.ActivityResultContracts.RequestPermission()
) { maybeRequestBatteryExemption() }
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
@@ -56,6 +62,7 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
tokenDataStore.preload()
}
ensureBackgroundPlaybackAllowed()
enableEdgeToEdge()
setContent {
// Выбранная цветовая тема (мгновенно перекрашивает всё приложение).
@@ -287,4 +294,39 @@ class MainActivity : ComponentActivity() {
}
}
}
/**
* Условия для стабильного фонового воспроизведения:
* 1) POST_NOTIFICATIONS (Android 13+) — без него не видно медиа-уведомление и
* foreground-сервис легко убивается системой;
* 2) исключение из оптимизации батареи (Doze/ColorOS глушат фон).
*/
private fun ensureBackgroundPlaybackAllowed() {
if (android.os.Build.VERSION.SDK_INT >= 33 &&
checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) !=
android.content.pm.PackageManager.PERMISSION_GRANTED
) {
// После ответа (в колбэке) попросим про батарею — не два диалога разом.
notifPermLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
} else {
maybeRequestBatteryExemption()
}
}
private fun maybeRequestBatteryExemption() {
val pm = getSystemService(Context.POWER_SERVICE) as android.os.PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) return
// Спрашиваем один раз на установку, чтобы не надоедать.
val prefs = getSharedPreferences("radiola_prefs", MODE_PRIVATE)
if (prefs.getBoolean("battery_opt_asked", false)) return
prefs.edit().putBoolean("battery_opt_asked", true).apply()
runCatching {
startActivity(
Intent(
android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
android.net.Uri.parse("package:$packageName")
)
)
}
}
}