From 780d194213424b7173eb8cc01ad0005095609e39 Mon Sep 17 00:00:00 2001 From: eliotcougar Date: Sun, 16 Aug 2026 10:26:58 +0300 Subject: [PATCH] Make explicit service restart daemon-authoritative (#6072) * Make service restarts daemon-authoritative * Update CoreServiceManager.kt --------- Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com> --- .../com/v2ray/ang/core/CoreServiceManager.kt | 19 +++++-- .../com/v2ray/ang/core/LauncherManager.kt | 12 +++++ .../com/v2ray/ang/helper/MessageHelper.kt | 49 ++++++++++++++++--- .../com/v2ray/ang/ui/main/MainActivity.kt | 34 ++++++------- 4 files changed, 86 insertions(+), 28 deletions(-) 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 eecfbced..8d1eb567 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 @@ -1,5 +1,6 @@ package com.v2ray.ang.core +import android.app.Activity import android.app.Service import android.content.BroadcastReceiver import android.content.Context @@ -30,6 +31,7 @@ import com.v2ray.ang.util.LogUtil import com.v2ray.ang.util.Utils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import libv2ray.CoreCallbackHandler import libv2ray.CoreController @@ -474,9 +476,20 @@ object CoreServiceManager { AppConfig.MSG_STATE_RESTART -> { LogUtil.i(AppConfig.TAG, "StartCore-Manager: Restart service") - serviceControl.stopService() - Thread.sleep(500L) - LauncherManager.startService(serviceControl.getService()) + // The UI and daemon run in separate processes, so acknowledge the active + // daemon before stopping it instead of relying on possibly stale UI state. + if (isOrderedBroadcast) resultCode = Activity.RESULT_OK + + val pendingResult = goAsync() + CoroutineScope(Dispatchers.Default).launch { + try { + serviceControl.stopService() + delay(500L) + LauncherManager.startService(serviceControl.getService()) + } finally { + pendingResult.finish() + } + } } AppConfig.MSG_MEASURE_DELAY -> { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/core/LauncherManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/core/LauncherManager.kt index ff09b77b..1ca5477a 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/core/LauncherManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/core/LauncherManager.kt @@ -56,6 +56,18 @@ object LauncherManager { MessageHelper.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "") } + /** Restarts the active daemon without starting a stopped service. */ + fun restartService(context: Context) { + MessageHelper.sendMsg2Service(context, AppConfig.MSG_STATE_RESTART, "") + } + + /** Restarts the active daemon, or delegates to the caller's permission-aware start flow. */ + fun restartServiceOrStart(context: Context, startIfStopped: () -> Unit) { + MessageHelper.sendMsg2ServiceForResult(context, AppConfig.MSG_STATE_RESTART, "") { handled -> + if (!handled) startIfStopped() + } + } + @Throws(Exception::class) private fun startContextService(context: Context) { // Note: isRunning check is removed here to avoid loading Native libraries in the UI process. diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MessageHelper.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MessageHelper.kt index 0c92ddf2..e1c4f2b2 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MessageHelper.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MessageHelper.kt @@ -1,5 +1,7 @@ package com.v2ray.ang.helper +import android.app.Activity +import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent @@ -27,6 +29,37 @@ object MessageHelper { sendMsg(ctx, AppConfig.BROADCAST_ACTION_SERVICE, what, content) } + /** + * Sends an ordered service message and reports whether a daemon receiver handled it. + * With no running daemon, the initial canceled result reaches [onResult] unchanged. + */ + internal fun sendMsg2ServiceForResult( + ctx: Context, + what: Int, + content: Serializable, + onResult: (handled: Boolean) -> Unit, + ) { + val resultReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + onResult(resultCode == Activity.RESULT_OK) + } + } + try { + ctx.sendOrderedBroadcast( + messageIntent(AppConfig.BROADCAST_ACTION_SERVICE, what, content), + null, + resultReceiver, + null, + Activity.RESULT_CANCELED, + null, + null, + ) + } catch (e: Exception) { + LogUtil.e(AppConfig.TAG, "Failed to send ordered message to service", e) + onResult(false) + } + } + /** * Sends a message to the UI. * @@ -115,14 +148,16 @@ object MessageHelper { */ private fun sendMsg(ctx: Context, action: String, what: Int, content: Serializable) { try { - val intent = Intent() - intent.action = action - intent.`package` = AppConfig.ANG_PACKAGE - intent.putExtra("key", what) - intent.putExtra("content", content) - ctx.sendBroadcast(intent) + ctx.sendBroadcast(messageIntent(action, what, content)) } catch (e: Exception) { LogUtil.e(AppConfig.TAG, "Failed to send message with action: $action", e) } } -} \ No newline at end of file + + private fun messageIntent(action: String, what: Int, content: Serializable): Intent = + Intent(action).apply { + `package` = AppConfig.ANG_PACKAGE + putExtra("key", what) + putExtra("content", content) + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt index eac2aa23..fb8187f1 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt @@ -75,10 +75,10 @@ class MainActivity : HelperBaseComponentActivity() { val restartService = data.getBooleanExtra( ProfileEditorResult.EXTRA_RESTART_SERVICE, false ) + val selectedProfileSaved = action == ProfileEditorResult.ACTION_SAVED && + data.getStringExtra(ProfileEditorResult.EXTRA_GUID) == mainViewModel.uiState.value.selectedGuid mainViewModel.onAction(MainAction.RefreshGroups) - if (restartService && mainViewModel.uiState.value.isRunning) { - restartV2Ray() - } + if (restartService || selectedProfileSaved) LauncherManager.restartService(this) } private val settingsActivityLauncher = @@ -87,7 +87,7 @@ class MainActivity : HelperBaseComponentActivity() { val refreshGroups = SettingsChangeManager.consumeSetupGroupTab() mainViewModel.refreshUiSettings() if (refreshGroups) mainViewModel.onAction(MainAction.RefreshGroups) - if (restartService && mainViewModel.uiState.value.isRunning) restartV2Ray() + if (restartService) LauncherManager.restartService(this) } override fun onCreate(savedInstanceState: Bundle?) { @@ -110,7 +110,7 @@ class MainActivity : HelperBaseComponentActivity() { MainAction.ImportClipboard -> importClipboard() MainAction.ImportConfigLocal -> importConfigLocal() is MainAction.ImportManually -> importManually(action.type) - MainAction.RestartService -> restartV2Ray() + MainAction.RestartService -> LauncherManager.restartServiceOrStart(this, ::requestServiceStart) MainAction.LocateSelectedServer -> mainViewModel.triggerLocateSelectedServer() is MainAction.SelectServer -> setSelectServer(action.guid) is MainAction.EditServer -> editServer(action.guid, action.profile) @@ -161,14 +161,20 @@ class MainActivity : HelperBaseComponentActivity() { private fun handleFabAction() { if (mainViewModel.uiState.value.isRunning) { LauncherManager.stopService(this) - } else if (SettingsManager.isVpnMode()) { - val intent = VpnService.prepare(this) - if (intent == null) startV2Ray() else requestVpnPermission.launch(intent) } else { - startV2Ray() + requestServiceStart() } } + private fun requestServiceStart() { + if (!SettingsManager.isVpnMode()) { + startV2Ray() + return + } + val intent = VpnService.prepare(this) + if (intent == null) startV2Ray() else requestVpnPermission.launch(intent) + } + private fun handleLayoutTestClick() { if (mainViewModel.uiState.value.isRunning) { mainViewModel.testCurrentServerRealPing() @@ -188,14 +194,6 @@ class MainActivity : HelperBaseComponentActivity() { LauncherManager.startService(this) } - private fun restartV2Ray() { - if (mainViewModel.uiState.value.isRunning) LauncherManager.stopService(this) - lifecycleScope.launch { - kotlinx.coroutines.delay(500) - startV2Ray() - } - } - private fun importManually(createConfigType: Int) { val intent = when (createConfigType) { EConfigType.POLICYGROUP.value -> Intent(this, ServerGroupActivity::class.java) @@ -275,7 +273,7 @@ class MainActivity : HelperBaseComponentActivity() { val selected = mainViewModel.uiState.value.selectedGuid if (guid != selected) { mainViewModel.updateSelectedGuid(guid) - if (mainViewModel.uiState.value.isRunning) restartV2Ray() + LauncherManager.restartService(this) } }