diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt index 9084d710..e36f1a0d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt @@ -13,6 +13,7 @@ import androidx.core.content.ContextCompat import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.contracts.ServiceControl +import com.v2ray.ang.dto.OutboundTrafficStat import com.v2ray.ang.dto.entities.ProfileItem import com.v2ray.ang.enums.EConfigType import com.v2ray.ang.extension.toast @@ -257,7 +258,7 @@ object CoreServiceManager { } MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "") - NotificationManager.startSpeedNotification(currentConfig) + NotificationManager.startSpeedNotification() LogUtil.i(AppConfig.TAG, "StartCore-Manager: Core started successfully") } @@ -299,13 +300,32 @@ object CoreServiceManager { } /** - * Queries the statistics for a given tag and link. - * @param tag The tag to query. - * @param link The link to query. - * @return The statistics value. + * Queries and resets all outbound traffic counters in one core call. + * Go side format: tag,direction,value;tag,direction,value; */ - fun queryStats(tag: String, link: String): Long { - return coreController.queryStats(tag, link) + fun queryAllOutboundTrafficStats(): List { + val payload = coreController.queryAllOutboundTrafficStats() + + val result = ArrayList() + + payload.split(';').forEach { entry -> + if (entry.isBlank()) return@forEach + + val parts = entry.split(',', limit = 3) + if (parts.size != 3) return@forEach + + val value = parts[2].toLongOrNull() ?: return@forEach + + result.add( + OutboundTrafficStat( + tag = parts[0], + direction = parts[1], + value = value, + ) + ) + } +// LogUtil.d(AppConfig.TAG, "Queried outbound traffic stats: $result") + return result } /** @@ -488,12 +508,12 @@ object CoreServiceManager { when (intent?.action) { Intent.ACTION_SCREEN_OFF -> { LogUtil.i(AppConfig.TAG, "StartCore-Manager: Screen off") - NotificationManager.stopSpeedNotification(currentConfig) + NotificationManager.stopSpeedNotification() } Intent.ACTION_SCREEN_ON -> { LogUtil.i(AppConfig.TAG, "StartCore-Manager: Screen on") - NotificationManager.startSpeedNotification(currentConfig) + NotificationManager.startSpeedNotification() } } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/OutboundTrafficStat.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/OutboundTrafficStat.kt new file mode 100644 index 00000000..6a1318c8 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/OutboundTrafficStat.kt @@ -0,0 +1,7 @@ +package com.v2ray.ang.dto + +data class OutboundTrafficStat( + val tag: String, + val direction: String, + val value: Long, +) \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt index 67315474..d2a81fab 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt @@ -76,10 +76,6 @@ data class ProfileItem( } } - fun getAllOutboundTags(): MutableList { - return mutableListOf(AppConfig.TAG_PROXY, AppConfig.TAG_DIRECT, AppConfig.TAG_BLOCKED) - } - fun getServerAddressAndPort(): String { if (server.isNullOrEmpty() && configType == EConfigType.CUSTOM) { return "${AppConfig.LOOPBACK}:${AppConfig.PORT_SOCKS}" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/NotificationManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/NotificationManager.kt index c460810c..7a4e4751 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/NotificationManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/NotificationManager.kt @@ -43,53 +43,15 @@ object NotificationManager { * Starts the speed notification. * @param currentConfig The current profile configuration. */ - fun startSpeedNotification(currentConfig: ProfileItem?) { + fun startSpeedNotification() { if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) return if (speedNotificationJob != null || CoreServiceManager.isRunning() == false) return var lastZeroSpeed = false - val outboundTags = currentConfig?.getAllOutboundTags() - outboundTags?.remove(AppConfig.TAG_DIRECT) speedNotificationJob = CoroutineScope(Dispatchers.IO).launch { while (isActive) { - val queryTime = System.currentTimeMillis() - val sinceLastQueryIn = (queryTime - lastQueryTime) - - // If the query interval is too short, skip this round to avoid excessive CPU usage - if (sinceLastQueryIn < QUERY_INTERVAL_MS) { - LogUtil.w(AppConfig.TAG, "Query interval too short: ${sinceLastQueryIn}ms, skipping") - lastQueryTime = queryTime - delay(QUERY_INTERVAL_MS) - continue - } - val sinceLastQueryInSeconds = sinceLastQueryIn / 1000.0 - - var proxyTotal = 0L - val text = StringBuilder() - outboundTags?.forEach { - val up = CoreServiceManager.queryStats(it, AppConfig.UPLINK) - val down = CoreServiceManager.queryStats(it, AppConfig.DOWNLINK) - if (up + down > 0) { - appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds) - proxyTotal += up + down - } - } - val directUplink = CoreServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK) - val directDownlink = CoreServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.DOWNLINK) - val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L - if (!zeroSpeed || !lastZeroSpeed) { - if (proxyTotal == 0L) { - appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0) - } - appendSpeedString( - text, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds, - directDownlink / sinceLastQueryInSeconds - ) - updateNotification(text.toString(), proxyTotal, directDownlink + directUplink) - } - lastZeroSpeed = zeroSpeed - lastQueryTime = queryTime + lastZeroSpeed = updateSpeedNotificationOnce(lastZeroSpeed) delay(QUERY_INTERVAL_MS) } } @@ -168,13 +130,12 @@ object NotificationManager { /** * Stops the speed notification. - * @param currentConfig The current profile configuration. */ - fun stopSpeedNotification(currentConfig: ProfileItem?) { + fun stopSpeedNotification() { speedNotificationJob?.let { it.cancel() speedNotificationJob = null - updateNotification(currentConfig?.remarks, 0, 0) + updateNotification("", 0, 0) } } @@ -247,6 +208,69 @@ object NotificationManager { text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n") } + /** + * Updates the speed notification once. + * Queries traffic stats, separates proxy and direct, and updates the notification. + * @param lastZeroSpeed The previous zero speed state. + * @return The current zero speed state. + */ + private fun updateSpeedNotificationOnce(lastZeroSpeed: Boolean): Boolean { + val queryTime = System.currentTimeMillis() + val sinceLastQueryIn = (queryTime - lastQueryTime) + + // If the query interval is too short, skip this round to avoid excessive CPU usage + if (sinceLastQueryIn < QUERY_INTERVAL_MS) { + LogUtil.w(AppConfig.TAG, "Query interval too short: ${sinceLastQueryIn}ms, skipping") + lastQueryTime = queryTime + return lastZeroSpeed + } + val sinceLastQueryInSeconds = sinceLastQueryIn / 1000.0 + + var proxyUplink = 0L + var proxyDownlink = 0L + var directUplink = 0L + var directDownlink = 0L + + CoreServiceManager.queryAllOutboundTrafficStats().forEach { stat -> + when { + stat.tag == AppConfig.TAG_DIRECT -> { + when (stat.direction) { + AppConfig.UPLINK -> directUplink += stat.value + AppConfig.DOWNLINK -> directDownlink += stat.value + } + } + + stat.tag.startsWith(AppConfig.TAG_PROXY) -> { + when (stat.direction) { + AppConfig.UPLINK -> proxyUplink += stat.value + AppConfig.DOWNLINK -> proxyDownlink += stat.value + } + } + } + } + + val proxyTotal = proxyUplink + proxyDownlink + val directTotal = directUplink + directDownlink + val zeroSpeed = proxyTotal + directTotal == 0L + if (!zeroSpeed || !lastZeroSpeed) { + val text = StringBuilder() + appendSpeedString( + text, AppConfig.TAG_PROXY, + proxyUplink / sinceLastQueryInSeconds, + proxyDownlink / sinceLastQueryInSeconds + ) + + appendSpeedString( + text, AppConfig.TAG_DIRECT, + directUplink / sinceLastQueryInSeconds, + directDownlink / sinceLastQueryInSeconds + ) + updateNotification(text.toString(), proxyTotal, directTotal) + } + lastQueryTime = queryTime + return zeroSpeed + } + /** * Gets the service instance. * @return The service instance.