improve long polling service reliability
This commit is contained in:
@@ -137,7 +137,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(longPollStateToApply) {
|
LifecycleResumeEffect(longPollStateToApply) {
|
||||||
|
Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply")
|
||||||
if (longPollStateToApply != LongPollState.Background) {
|
if (longPollStateToApply != LongPollState.Background) {
|
||||||
if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched()
|
if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched()
|
||||||
&& longPollCurrentState != longPollStateToApply
|
&& longPollCurrentState != longPollStateToApply
|
||||||
@@ -151,6 +152,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
inBackground = longPollStateToApply == LongPollState.Background
|
inBackground = longPollStateToApply == LongPollState.Background
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPauseOrDispose {}
|
||||||
}
|
}
|
||||||
|
|
||||||
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
|
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
|
||||||
@@ -299,12 +302,19 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val longPollingServiceIntent by lazy {
|
||||||
|
Intent(this, LongPollingService::class.java)
|
||||||
|
}
|
||||||
|
private val onlineServiceIntent by lazy {
|
||||||
|
Intent(this, OnlineService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
private fun toggleLongPollService(
|
private fun toggleLongPollService(
|
||||||
enable: Boolean,
|
enable: Boolean,
|
||||||
inBackground: Boolean = AppSettings.Experimental.longPollInBackground
|
inBackground: Boolean = AppSettings.Experimental.longPollInBackground
|
||||||
) {
|
) {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
val longPollIntent = Intent(this, LongPollingService::class.java)
|
val longPollIntent = longPollingServiceIntent
|
||||||
|
|
||||||
if (inBackground) {
|
if (inBackground) {
|
||||||
ContextCompat.startForegroundService(this, longPollIntent)
|
ContextCompat.startForegroundService(this, longPollIntent)
|
||||||
@@ -312,15 +322,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
startService(longPollIntent)
|
startService(longPollIntent)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stopService(Intent(this, LongPollingService::class.java))
|
stopService(longPollingServiceIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleOnlineService(enable: Boolean) {
|
private fun toggleOnlineService(enable: Boolean) {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
startService(Intent(this, OnlineService::class.java))
|
startService(onlineServiceIntent)
|
||||||
} else {
|
} else {
|
||||||
stopService(Intent(this, OnlineService::class.java))
|
stopService(onlineServiceIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,13 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class LongPollingService : Service() {
|
class LongPollingService : Service() {
|
||||||
|
|
||||||
@@ -42,22 +44,11 @@ class LongPollingService : Service() {
|
|||||||
|
|
||||||
private val job = SupervisorJob()
|
private val job = SupervisorJob()
|
||||||
|
|
||||||
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
private val exceptionHandler =
|
||||||
Log.e(TAG, "error: $throwable")
|
CoroutineExceptionHandler { _, throwable ->
|
||||||
|
handleError(throwable)
|
||||||
if (throwable !is NoAccessTokenException) {
|
|
||||||
throwable.printStackTrace()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throwable is LongPollException) {
|
|
||||||
// TODO: 23-Mar-25, Danil Nikolaev: restart LongPoll
|
|
||||||
return@CoroutineExceptionHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
longPollController.updateCurrentState(LongPollState.Exception)
|
|
||||||
longPollController.setStateToApply(LongPollState.Exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val coroutineContext: CoroutineContext
|
private val coroutineContext: CoroutineContext
|
||||||
get() = Dispatchers.IO + job + exceptionHandler
|
get() = Dispatchers.IO + job + exceptionHandler
|
||||||
|
|
||||||
@@ -68,6 +59,8 @@ class LongPollingService : Service() {
|
|||||||
|
|
||||||
private var currentJob: Job? = null
|
private var currentJob: Job? = null
|
||||||
|
|
||||||
|
private val inBackground get() = AppSettings.Experimental.longPollInBackground
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Log.d(STATE_TAG, "onCreate()")
|
Log.d(STATE_TAG, "onCreate()")
|
||||||
@@ -81,21 +74,12 @@ class LongPollingService : Service() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (startId > 1) return START_STICKY
|
if (startId > 1) return START_STICKY
|
||||||
|
|
||||||
val inBackground = AppSettings.Experimental.longPollInBackground
|
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
STATE_TAG,
|
STATE_TAG,
|
||||||
"onStartCommand: asForeground: $inBackground; flags: $flags; startId: $startId;\ninstance: $this"
|
"onStartCommand: asForeground: $inBackground; flags: $flags; startId: $startId;\ninstance: $this"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (currentJob != null) {
|
startJob()
|
||||||
currentJob?.cancel()
|
|
||||||
currentJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
currentJob = startPolling().also { it.join() }
|
|
||||||
}
|
|
||||||
|
|
||||||
val openCategorySettingsIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val openCategorySettingsIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||||
@@ -113,11 +97,6 @@ class LongPollingService : Service() {
|
|||||||
PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
longPollController.updateCurrentState(
|
|
||||||
if (inBackground) LongPollState.Background
|
|
||||||
else LongPollState.InApp
|
|
||||||
)
|
|
||||||
|
|
||||||
if (inBackground) {
|
if (inBackground) {
|
||||||
val notification =
|
val notification =
|
||||||
NotificationsUtils.createNotification(
|
NotificationsUtils.createNotification(
|
||||||
@@ -139,17 +118,33 @@ class LongPollingService : Service() {
|
|||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startJob() {
|
||||||
|
if (currentJob != null) {
|
||||||
|
currentJob?.cancel()
|
||||||
|
currentJob = null
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
currentJob = startPolling().also { it.join() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startPolling(): Job {
|
private fun startPolling(): Job {
|
||||||
if (job.isCompleted || job.isCancelled) {
|
if (job.isCompleted || job.isCancelled) {
|
||||||
Log.d(STATE_TAG, "job is completed or cancelled")
|
Log.d(STATE_TAG, "Job is completed or cancelled")
|
||||||
throw Exception("Job is over")
|
throw Exception("Job is over")
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(STATE_TAG, "job started")
|
Log.d(STATE_TAG, "Starting job...")
|
||||||
|
|
||||||
return coroutineScope.launch(coroutineContext) {
|
return coroutineScope.launch(coroutineContext) {
|
||||||
|
longPollController.updateCurrentState(
|
||||||
|
if (inBackground) LongPollState.Background
|
||||||
|
else LongPollState.InApp
|
||||||
|
)
|
||||||
|
|
||||||
if (UserConfig.accessToken.isEmpty()) {
|
if (UserConfig.accessToken.isEmpty()) {
|
||||||
throw NoAccessTokenException
|
throw NoAccessTokenException()
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverInfo = getServerInfo()
|
var serverInfo = getServerInfo()
|
||||||
@@ -251,10 +246,24 @@ class LongPollingService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleError(throwable: Throwable) {
|
||||||
|
Log.e(TAG, "error: $throwable")
|
||||||
|
|
||||||
|
if (throwable !is NoAccessTokenException) {
|
||||||
|
throwable.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
delay(5.seconds)
|
||||||
|
startJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
longPollController.updateCurrentState(LongPollState.Exception)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(STATE_TAG, "onDestroy")
|
Log.d(STATE_TAG, "onDestroy")
|
||||||
longPollController.updateCurrentState(LongPollState.Stopped)
|
longPollController.updateCurrentState(LongPollState.Stopped)
|
||||||
updatesParser.clearListeners()
|
|
||||||
try {
|
try {
|
||||||
AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) }
|
AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) }
|
||||||
job.cancel()
|
job.cancel()
|
||||||
@@ -281,4 +290,4 @@ class LongPollingService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private data class LongPollException(override val message: String) : Throwable()
|
private data class LongPollException(override val message: String) : Throwable()
|
||||||
private data object NoAccessTokenException : Throwable()
|
private class NoAccessTokenException : Throwable()
|
||||||
|
|||||||
Reference in New Issue
Block a user