forked from melod1n/fast-messenger
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.isLaunched() && longPollCurrentState.isLaunched()
|
||||
&& longPollCurrentState != longPollStateToApply
|
||||
@@ -151,6 +152,8 @@ class MainActivity : AppCompatActivity() {
|
||||
inBackground = longPollStateToApply == LongPollState.Background
|
||||
)
|
||||
}
|
||||
|
||||
onPauseOrDispose {}
|
||||
}
|
||||
|
||||
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(
|
||||
enable: Boolean,
|
||||
inBackground: Boolean = AppSettings.Experimental.longPollInBackground
|
||||
) {
|
||||
if (enable) {
|
||||
val longPollIntent = Intent(this, LongPollingService::class.java)
|
||||
val longPollIntent = longPollingServiceIntent
|
||||
|
||||
if (inBackground) {
|
||||
ContextCompat.startForegroundService(this, longPollIntent)
|
||||
@@ -312,15 +322,15 @@ class MainActivity : AppCompatActivity() {
|
||||
startService(longPollIntent)
|
||||
}
|
||||
} else {
|
||||
stopService(Intent(this, LongPollingService::class.java))
|
||||
stopService(longPollingServiceIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleOnlineService(enable: Boolean) {
|
||||
if (enable) {
|
||||
startService(Intent(this, OnlineService::class.java))
|
||||
startService(onlineServiceIntent)
|
||||
} else {
|
||||
stopService(Intent(this, OnlineService::class.java))
|
||||
stopService(onlineServiceIntent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,11 +30,13 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class LongPollingService : Service() {
|
||||
|
||||
@@ -42,22 +44,11 @@ class LongPollingService : Service() {
|
||||
|
||||
private val job = SupervisorJob()
|
||||
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||
Log.e(TAG, "error: $throwable")
|
||||
|
||||
if (throwable !is NoAccessTokenException) {
|
||||
throwable.printStackTrace()
|
||||
private val exceptionHandler =
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
handleError(throwable)
|
||||
}
|
||||
|
||||
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
|
||||
get() = Dispatchers.IO + job + exceptionHandler
|
||||
|
||||
@@ -68,6 +59,8 @@ class LongPollingService : Service() {
|
||||
|
||||
private var currentJob: Job? = null
|
||||
|
||||
private val inBackground get() = AppSettings.Experimental.longPollInBackground
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(STATE_TAG, "onCreate()")
|
||||
@@ -81,21 +74,12 @@ class LongPollingService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (startId > 1) return START_STICKY
|
||||
|
||||
val inBackground = AppSettings.Experimental.longPollInBackground
|
||||
|
||||
Log.d(
|
||||
STATE_TAG,
|
||||
"onStartCommand: asForeground: $inBackground; flags: $flags; startId: $startId;\ninstance: $this"
|
||||
)
|
||||
|
||||
if (currentJob != null) {
|
||||
currentJob?.cancel()
|
||||
currentJob = null
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
currentJob = startPolling().also { it.join() }
|
||||
}
|
||||
startJob()
|
||||
|
||||
val openCategorySettingsIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
@@ -113,11 +97,6 @@ class LongPollingService : Service() {
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
longPollController.updateCurrentState(
|
||||
if (inBackground) LongPollState.Background
|
||||
else LongPollState.InApp
|
||||
)
|
||||
|
||||
if (inBackground) {
|
||||
val notification =
|
||||
NotificationsUtils.createNotification(
|
||||
@@ -139,17 +118,33 @@ class LongPollingService : Service() {
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun startJob() {
|
||||
if (currentJob != null) {
|
||||
currentJob?.cancel()
|
||||
currentJob = null
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
currentJob = startPolling().also { it.join() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPolling(): Job {
|
||||
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")
|
||||
}
|
||||
|
||||
Log.d(STATE_TAG, "job started")
|
||||
Log.d(STATE_TAG, "Starting job...")
|
||||
|
||||
return coroutineScope.launch(coroutineContext) {
|
||||
longPollController.updateCurrentState(
|
||||
if (inBackground) LongPollState.Background
|
||||
else LongPollState.InApp
|
||||
)
|
||||
|
||||
if (UserConfig.accessToken.isEmpty()) {
|
||||
throw NoAccessTokenException
|
||||
throw NoAccessTokenException()
|
||||
}
|
||||
|
||||
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() {
|
||||
Log.d(STATE_TAG, "onDestroy")
|
||||
longPollController.updateCurrentState(LongPollState.Stopped)
|
||||
updatesParser.clearListeners()
|
||||
try {
|
||||
AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) }
|
||||
job.cancel()
|
||||
@@ -281,4 +290,4 @@ class LongPollingService : Service() {
|
||||
}
|
||||
|
||||
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