3 Commits

Author SHA1 Message Date
zarazaex69 f58a4f8f6f style: fix indentation and mark non-translatable strings 2026-04-11 00:37:14 +03:00
zarazaex69 fdce3ea2c1 fix(service): prevent concurrent operations with synchronization locks 2026-04-11 00:31:28 +03:00
zarazaex69 f0d620676d chore(gradle): downgrade Kotlin version to 2.1.0 2026-04-10 18:41:54 +03:00
5 changed files with 113 additions and 44 deletions
@@ -30,6 +30,8 @@ object V2RayServiceManager {
private val coreController: CoreController = V2RayNativeManager.newCoreController(CoreCallback())
private val mMsgReceive = ReceiveMessageHandler()
private var currentConfig: ProfileItem? = null
private val operationLock = Any()
@Volatile private var isOperationInProgress = false
var serviceControl: SoftReference<ServiceControl>? = null
set(value) {
@@ -57,13 +59,27 @@ object V2RayServiceManager {
* @param guid The GUID of the server configuration to use (optional).
*/
fun startVService(context: Context, guid: String? = null) {
Log.i(AppConfig.TAG, "StartCore-Manager: startVService from ${context::class.java.simpleName}")
if (guid != null) {
MmkvManager.setSelectServer(guid)
synchronized(operationLock) {
if (isOperationInProgress) {
Log.w(AppConfig.TAG, "StartCore-Manager: Operation already in progress")
return
}
isOperationInProgress = true
}
startContextService(context)
try {
Log.i(AppConfig.TAG, "StartCore-Manager: startVService from ${context::class.java.simpleName}")
if (guid != null) {
MmkvManager.setSelectServer(guid)
}
startContextService(context)
} finally {
synchronized(operationLock) {
isOperationInProgress = false
}
}
}
/**
@@ -71,8 +87,21 @@ object V2RayServiceManager {
* @param context The context from which the service is stopped.
*/
fun stopVService(context: Context) {
//context.toast(R.string.toast_services_stop)
MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "")
synchronized(operationLock) {
if (isOperationInProgress) {
Log.w(AppConfig.TAG, "StartCore-Manager: Operation already in progress")
return
}
isOperationInProgress = true
}
try {
MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "")
} finally {
synchronized(operationLock) {
isOperationInProgress = false
}
}
}
/**
@@ -385,11 +414,17 @@ object V2RayServiceManager {
AppConfig.MSG_STATE_STOP -> {
Log.i(AppConfig.TAG, "StartCore-Manager: Stop service")
synchronized(operationLock) {
isOperationInProgress = false
}
serviceControl.stopService()
}
AppConfig.MSG_STATE_RESTART -> {
Log.i(AppConfig.TAG, "StartCore-Manager: Restart service")
synchronized(operationLock) {
isOperationInProgress = false
}
serviceControl.stopService()
Thread.sleep(500L)
startVService(serviceControl.getService())
@@ -1311,7 +1311,7 @@ object V2rayConfigManager {
if (start != null && end != null) {
val minStart = maxOf(5, start)
val minEnd = maxOf(minStart, end)
"$minStart-$minEnd"
"$minStart-$minEnd"
} else {
"30"
}
@@ -42,9 +42,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelectedListener {
private val binding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
private val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}
private var isLiteTesting = false
private var easterEggClickCount = 0
private var isEasterEggActive = false
@@ -52,6 +50,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val mainViewModel: MainViewModel by viewModels()
private lateinit var groupPagerAdapter: GroupPagerAdapter
private var tabMediator: TabLayoutMediator? = null
@Volatile private var isFabOperationInProgress = false
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
@@ -201,12 +200,24 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun handleFabAction() {
if (isFabOperationInProgress) {
return
}
isFabOperationInProgress = true
applyRunningState(isLoading = true, isRunning = false)
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this)
} else {
startV2RayWithPermission()
lifecycleScope.launch {
try {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this@MainActivity)
} else {
startV2RayWithPermission()
}
} finally {
delay(1000)
isFabOperationInProgress = false
}
}
}
@@ -220,29 +231,42 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun handleLiteAction() {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this)
if (isFabOperationInProgress) {
return
}
showStatus("Обновление профилей...")
showLoading()
isLiteTesting = true
lifecycleScope.launch(Dispatchers.IO) {
val result = mainViewModel.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (result.configCount > 0) {
mainViewModel.reloadServerList()
showStatus("Обновлено ${result.configCount} профилей. Запуск теста...")
} else {
showStatus("Запуск теста...")
isFabOperationInProgress = true
lifecycleScope.launch {
try {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this@MainActivity)
delay(1000)
}
hideLoading()
delay(500L)
showStatus("Выполняется замер задержки. Ожидаем завершения...")
mainViewModel.testAllRealPing()
showStatus("Обновление профилей...")
showLoading()
isLiteTesting = true
launch(Dispatchers.IO) {
val result = mainViewModel.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (result.configCount > 0) {
mainViewModel.reloadServerList()
showStatus("Обновлено ${result.configCount} профилей. Запуск теста...")
} else {
showStatus("Запуск теста...")
}
hideLoading()
delay(500L)
showStatus("Выполняется замер задержки. Ожидаем завершения...")
mainViewModel.testAllRealPing()
}
}
} finally {
delay(1000)
isFabOperationInProgress = false
}
}
}
@@ -269,12 +293,22 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
fun restartV2Ray() {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this)
if (isFabOperationInProgress) {
return
}
isFabOperationInProgress = true
lifecycleScope.launch {
delay(500)
startV2Ray()
try {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this@MainActivity)
}
delay(1000)
startV2Ray()
} finally {
delay(500)
isFabOperationInProgress = false
}
}
}
+3 -3
View File
@@ -7,8 +7,8 @@
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">Data migration success!</string>
<string name="drawer_forked_text">forked from <a href="https://github.com/2dust/v2rayng">V2RayNG</a></string>
<string name="drawer_developed_text">developed by developers from <a href="https://t.me/openlibrecommunity">Olc</a></string>
<string name="drawer_forked_text" translatable="false">forked from <a href="https://github.com/2dust/v2rayng">V2RayNG</a></string>
<string name="drawer_developed_text" translatable="false">developed by developers from <a href="https://t.me/openlibrecommunity">Olc</a></string>
<string name="action_stop_service">Stop service</string>
<string name="migration_fail">Data migration failed!</string>
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
@@ -249,7 +249,7 @@
<string name="summary_pref_tg_group">Join Telegram Group</string>
<string name="toast_tg_app_not_found">Telegram app not found</string>
<string name="title_privacy_policy">Privacy policy</string>
<string name="title_qr_code">QR code</string>
<string name="title_qr_code" translatable="false">QR code</string>
<string name="title_about">About</string>
<string name="title_source_code">Source code</string>
<string name="title_oss_license">Open Source licenses</string>
+1 -1
View File
@@ -2,7 +2,7 @@
agp = "9.1.0"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.3.10"
kotlin = "2.1.0"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"