Introduce RealPingEvent and refactor ping flow
Add RealPingEvent sealed class and refactor real-ping flow to use event-driven communication. RealPingWorkerService now emits Progress/Result/Finish events instead of direct callbacks/MessageUtil calls. CoreTestService reorganized into handlers (handleMeasureStart/handleMeasureCancel/handleWorkerEvent) to process events, update notifications, persist delays via MmkvManager, and notify UI. MainViewModel adjusted to read the result content as a string when handling success messages. Overall change decouples worker logic from service responsibilities and simplifies progress/result handling.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
sealed class RealPingEvent {
|
||||
|
||||
/** Periodic progress update while the batch is still running. */
|
||||
data class Progress(val text: String) : RealPingEvent()
|
||||
|
||||
/** A single server result is available. */
|
||||
data class Result(val guid: String, val delayMillis: Long) : RealPingEvent()
|
||||
|
||||
/** The entire batch has finished or been cancelled. */
|
||||
data class Finish(val status: String) : RealPingEvent()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.RealPingEvent
|
||||
import com.v2ray.ang.dto.TestServiceMessage
|
||||
import com.v2ray.ang.extension.serializable
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
@@ -65,73 +66,75 @@ class CoreTestService : Service() {
|
||||
}
|
||||
|
||||
when (message.key) {
|
||||
AppConfig.MSG_MEASURE_CONFIG_START -> {
|
||||
LogUtil.i(AppConfig.TAG, "CoreTestService starting worker subscription ${message.subscriptionId}")
|
||||
|
||||
// start foreground immediately to satisfy startForegroundService timing requirement
|
||||
NotificationHelper.startForeground(
|
||||
this,
|
||||
NotificationChannelType.CORE_TEST,
|
||||
getString(R.string.app_name),
|
||||
getString(R.string.title_real_ping_all_server)
|
||||
)
|
||||
|
||||
val guidsList = if (message.serverGuids.isNotEmpty()) {
|
||||
message.serverGuids
|
||||
} else if (message.subscriptionId.isNotEmpty()) {
|
||||
MmkvManager.decodeServerList(message.subscriptionId)
|
||||
} else {
|
||||
MmkvManager.decodeAllServerList()
|
||||
}
|
||||
|
||||
if (guidsList.isNotEmpty()) {
|
||||
lateinit var worker: RealPingWorkerService
|
||||
worker = RealPingWorkerService(
|
||||
context = this,
|
||||
guids = guidsList,
|
||||
onProgress = { progress ->
|
||||
NotificationHelper.updateNotification(
|
||||
channelType = NotificationChannelType.CORE_TEST,
|
||||
context = this,
|
||||
content = getString(R.string.connection_runing_task_left, progress)
|
||||
)
|
||||
// notify UI about progress updates
|
||||
MessageUtil.sendMsg2UI(this@CoreTestService, AppConfig.MSG_MEASURE_CONFIG_NOTIFY, progress)
|
||||
},
|
||||
onFinish = { status ->
|
||||
// notify UI and remove the worker from active list when finished
|
||||
MessageUtil.sendMsg2UI(this@CoreTestService, AppConfig.MSG_MEASURE_CONFIG_FINISH, status)
|
||||
activeWorkers.remove(worker)
|
||||
if (activeWorkers.isEmpty()) {
|
||||
NotificationHelper.stopForeground(this@CoreTestService)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
activeWorkers.add(worker)
|
||||
worker.start()
|
||||
} else {
|
||||
NotificationHelper.stopForeground(this)
|
||||
stopSelf(startId)
|
||||
}
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_CANCEL -> {
|
||||
LogUtil.i(AppConfig.TAG, "CoreTestService received cancel message, cancelling ${activeWorkers.size} active workers")
|
||||
// cancel all running batch workers independently
|
||||
val snapshot = ArrayList(activeWorkers)
|
||||
snapshot.forEach { it.cancel() }
|
||||
activeWorkers.clear()
|
||||
NotificationHelper.stopForeground(this)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
else -> {
|
||||
NotificationHelper.stopForeground(this)
|
||||
stopSelf(startId)
|
||||
}
|
||||
AppConfig.MSG_MEASURE_CONFIG_START -> handleMeasureStart(message, startId)
|
||||
AppConfig.MSG_MEASURE_CONFIG_CANCEL -> handleMeasureCancel()
|
||||
else -> { NotificationHelper.stopForeground(this); stopSelf(startId) }
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun handleMeasureStart(message: TestServiceMessage, startId: Int) {
|
||||
LogUtil.i(AppConfig.TAG, "CoreTestService starting worker subscription ${message.subscriptionId}")
|
||||
|
||||
NotificationHelper.startForeground(
|
||||
this,
|
||||
NotificationChannelType.CORE_TEST,
|
||||
getString(R.string.app_name),
|
||||
getString(R.string.title_real_ping_all_server)
|
||||
)
|
||||
|
||||
val guidsList = when {
|
||||
message.serverGuids.isNotEmpty() -> message.serverGuids
|
||||
message.subscriptionId.isNotEmpty() -> MmkvManager.decodeServerList(message.subscriptionId)
|
||||
else -> MmkvManager.decodeAllServerList()
|
||||
}
|
||||
|
||||
if (guidsList.isNotEmpty()) {
|
||||
lateinit var worker: RealPingWorkerService
|
||||
worker = RealPingWorkerService(
|
||||
context = this,
|
||||
guids = guidsList,
|
||||
onEvent = { event -> handleWorkerEvent(event) { activeWorkers.remove(worker) } }
|
||||
)
|
||||
activeWorkers.add(worker)
|
||||
worker.start()
|
||||
} else {
|
||||
NotificationHelper.stopForeground(this)
|
||||
stopSelf(startId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWorkerEvent(event: RealPingEvent, onWorkerDone: () -> Unit) {
|
||||
when (event) {
|
||||
is RealPingEvent.Progress -> {
|
||||
NotificationHelper.updateNotification(
|
||||
channelType = NotificationChannelType.CORE_TEST,
|
||||
context = this,
|
||||
content = getString(R.string.connection_runing_task_left, event.text)
|
||||
)
|
||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_MEASURE_CONFIG_NOTIFY, event.text)
|
||||
}
|
||||
is RealPingEvent.Result -> {
|
||||
MmkvManager.encodeServerTestDelayMillis(event.guid, event.delayMillis)
|
||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_MEASURE_CONFIG_SUCCESS, event.guid)
|
||||
}
|
||||
is RealPingEvent.Finish -> {
|
||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_MEASURE_CONFIG_FINISH, event.status)
|
||||
onWorkerDone()
|
||||
if (activeWorkers.isEmpty()) {
|
||||
NotificationHelper.stopForeground(this)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMeasureCancel() {
|
||||
LogUtil.i(AppConfig.TAG, "CoreTestService received cancel message, cancelling ${activeWorkers.size} active workers")
|
||||
val snapshot = ArrayList(activeWorkers)
|
||||
snapshot.forEach { it.cancel() }
|
||||
activeWorkers.clear()
|
||||
NotificationHelper.stopForeground(this)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.content.Context
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.RealPingEvent
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.core.CoreNativeManager
|
||||
import com.v2ray.ang.core.CoreConfigManager
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -23,8 +22,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||
class RealPingWorkerService(
|
||||
private val context: Context,
|
||||
private val guids: List<String>,
|
||||
private val onProgress: (progress: String) -> Unit = {},
|
||||
private val onFinish: (status: String) -> Unit = {}
|
||||
private val onEvent: (RealPingEvent) -> Unit = {}
|
||||
) {
|
||||
private val job = SupervisorJob()
|
||||
private val concurrency = SettingsManager.getRealPingConcurrency()
|
||||
@@ -41,13 +39,13 @@ class RealPingWorkerService(
|
||||
runningCount.incrementAndGet()
|
||||
try {
|
||||
val result = startRealPing(guid)
|
||||
MessageUtil.sendMsg2UI(context, AppConfig.MSG_MEASURE_CONFIG_SUCCESS, Pair(guid, result))
|
||||
onEvent(RealPingEvent.Result(guid, result))
|
||||
} catch (_: Throwable) {
|
||||
// ignore
|
||||
} finally {
|
||||
val count = totalCount.decrementAndGet()
|
||||
val left = runningCount.decrementAndGet()
|
||||
onProgress("$left / $count")
|
||||
onEvent(RealPingEvent.Progress("$left / $count"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,9 +53,9 @@ class RealPingWorkerService(
|
||||
scope.launch {
|
||||
try {
|
||||
joinAll(*jobs.toTypedArray())
|
||||
onFinish("0")
|
||||
onEvent(RealPingEvent.Finish("0"))
|
||||
} catch (_: CancellationException) {
|
||||
onFinish("-1")
|
||||
onEvent(RealPingEvent.Finish("-1"))
|
||||
} finally {
|
||||
close()
|
||||
}
|
||||
@@ -85,4 +83,3 @@ class RealPingWorkerService(
|
||||
return CoreNativeManager.measureOutboundDelay(configResult.content, SettingsManager.getDelayTestUrl())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -470,9 +470,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||
val resultPair = intent.serializable<Pair<String, Long>>("content") ?: return
|
||||
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||
updateListAction.value = getPosition(resultPair.first)
|
||||
val content = intent.getStringExtra("content")
|
||||
updateListAction.value = getPosition(content?: "")
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_NOTIFY -> {
|
||||
|
||||
Reference in New Issue
Block a user