From fc4ab733c84f343931055c6e1b8ace1dfa3d559e Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 3 May 2026 16:46:40 +0800 Subject: [PATCH] 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. --- .../java/com/v2ray/ang/dto/RealPingEvent.kt | 14 ++ .../com/v2ray/ang/service/CoreTestService.kt | 135 +++++++++--------- .../ang/service/RealPingWorkerService.kt | 15 +- .../com/v2ray/ang/viewmodel/MainViewModel.kt | 5 +- 4 files changed, 91 insertions(+), 78 deletions(-) create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/dto/RealPingEvent.kt diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/RealPingEvent.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/RealPingEvent.kt new file mode 100644 index 00000000..62a0366c --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/RealPingEvent.kt @@ -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() +} + diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreTestService.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreTestService.kt index 0a348ebe..1ac3f03e 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreTestService.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreTestService.kt @@ -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() + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/RealPingWorkerService.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/service/RealPingWorkerService.kt index 9523575b..355400c7 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/service/RealPingWorkerService.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/RealPingWorkerService.kt @@ -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, - 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()) } } - diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt index 23762284..620c010f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt @@ -470,9 +470,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> { - val resultPair = intent.serializable>("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 -> {