Improve V2Ray config/error handling and startup

Add richer error reporting and simplify control flow across core components. Key changes:

- Add errorMessage to ConfigResult and populate it on failures in CoreConfigManager.getV2rayConfig/getV2rayConfig4Speedtest.
- Replace many nullable/boolean error returns in CoreConfigManager with explicit errors (error()/exceptions) and non-null return types; tighten validation (invalid servers, missing assets, parsing errors) and simplify inbound/dns/routing/outbound setup logic.
- Make getInbounds/getRouting/getDns/getOutbounds/getCustomLocalDns/getRoutingUserRule/getBalance non-failing (throw on error) and remove silent try/catch patterns; improve logging and fallback behaviors.
- CoreServiceManager: wrap startContextService calls with try/catch to surface errors to UI; startContextService now throws on invalid state/config and moves startup loop into doStartCoreLoop which registers receivers, starts core loop, and reports detailed failures to UI.
- Show notification when VPN service starts (CoreVpnService) to ensure foreground visibility.
- MainViewModel: display detailed start failure message from intent when available instead of only a generic toast.

These changes improve diagnostics, reduce silent failures, and centralize error handling during startup.
This commit is contained in:
2dust
2026-05-05 18:45:47 +08:00
parent 19ad248cfe
commit 209679f098
5 changed files with 456 additions and 493 deletions
File diff suppressed because it is too large Load Diff
@@ -62,7 +62,13 @@ object CoreServiceManager {
context.toast(R.string.app_tile_first_use)
return false
}
startContextService(context)
try {
startContextService(context)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: ${e.message}", e)
context.toast(e.message ?: e.javaClass.simpleName)
return false
}
return true
}
@@ -78,7 +84,12 @@ object CoreServiceManager {
MmkvManager.setSelectServer(guid)
}
startContextService(context)
try {
startContextService(context)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: ${e.message}", e)
context.toast(e.message ?: e.javaClass.simpleName)
}
}
/**
@@ -106,7 +117,11 @@ object CoreServiceManager {
* Starts the context service for V2Ray.
* Chooses between VPN service or Proxy-only service based on user settings.
* @param context The context from which the service is started.
* @throws IllegalStateException if the core is already running, no server is selected,
* server config cannot be decoded, or server configuration is invalid.
* @throws Exception if the foreground service fails to start.
*/
@Throws(Exception::class)
private fun startContextService(context: Context) {
if (coreController.isRunning) {
LogUtil.w(AppConfig.TAG, "StartCore-Manager: Core already running")
@@ -114,16 +129,16 @@ object CoreServiceManager {
}
val guid = MmkvManager.getSelectServer()
if (guid == null) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: No server selected")
return
}
?: run {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: No server selected")
error(context.getString(R.string.app_tile_first_use))
}
val config = MmkvManager.decodeServerConfig(guid)
if (config == null) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to decode server config")
return
}
?: run {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to decode server config")
error(context.getString(R.string.toast_config_file_invalid))
}
if (config.configType != EConfigType.CUSTOM
&& config.configType != EConfigType.POLICYGROUP
@@ -132,13 +147,14 @@ object CoreServiceManager {
&& !Utils.isPureIpAddress(config.server.orEmpty())
) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Invalid server configuration")
return
error(context.getString(R.string.toast_config_file_invalid))
}
// refresh socks port when enabled dynamic socks port
SettingsManager.refreshRuntimeSocksPort()
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
// if (!result.status) return
// if (!result.status) error(result.errorMessage.ifBlank { "Failed to get V2Ray config" })
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING)) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
@@ -155,11 +171,7 @@ object CoreServiceManager {
Intent(context.applicationContext, CoreProxyOnlyService::class.java)
}
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to start service", e)
}
ContextCompat.startForegroundService(context, intent)
}
/**
@@ -179,36 +191,35 @@ object CoreServiceManager {
return false
}
val guid = MmkvManager.getSelectServer()
if (guid == null) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: No server selected")
try {
doStartCoreLoop(service, vpnInterface)
return true
} catch (e: Exception) {
val message = e.message?.takeUnless { it.isBlank() } ?: e.javaClass.simpleName
LogUtil.e(AppConfig.TAG, "StartCore-Manager: $message", e)
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, message)
NotificationManager.cancelNotification()
return false
}
}
val config = MmkvManager.decodeServerConfig(guid)
if (config == null) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to decode server config")
return false
}
@Throws(Exception::class)
private fun doStartCoreLoop(service: Service, vpnInterface: ParcelFileDescriptor?) {
val guid = MmkvManager.getSelectServer() ?: error("No server selected")
val config = MmkvManager.decodeServerConfig(guid) ?: error("Failed to decode server config")
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Starting core loop for ${config.remarks}")
val result = CoreConfigManager.getV2rayConfig(service, guid)
LogUtil.d(AppConfig.TAG, result.content)
if (!result.status) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to get V2Ray config")
return false
error(result.errorMessage.ifBlank { "Failed to get V2Ray config" })
}
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to register receiver", e)
return false
}
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
currentConfig = config
var tunFd = vpnInterface?.fd ?: 0
@@ -216,30 +227,16 @@ object CoreServiceManager {
tunFd = 0
}
try {
NotificationManager.showNotification(currentConfig)
coreController.startLoop(result.content, tunFd)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to start core loop", e)
return false
NotificationManager.showNotification(currentConfig)
coreController.startLoop(result.content, tunFd)
if (!coreController.isRunning) {
error("Core failed to start")
}
if (coreController.isRunning == false) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Core failed to start")
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
NotificationManager.cancelNotification()
return false
}
try {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
NotificationManager.startSpeedNotification(currentConfig)
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Core started successfully")
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to complete startup", e)
return false
}
return true
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
NotificationManager.startSpeedNotification(currentConfig)
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Core started successfully")
}
/**
@@ -406,7 +403,7 @@ object CoreServiceManager {
//LogUtil.d(AppConfig.TAG, "ProcessFinder: Find $network connection from $srcIP:$srcPort to $destIP:$destPort, uid=$uid,${PackageUidResolver.uidToPackageName(uid.toString())}")
uid
} catch (e: Exception) {
} catch (_: Exception) {
-1L
}
}
@@ -4,5 +4,6 @@ data class ConfigResult(
var status: Boolean,
var guid: String? = null,
var content: String = "",
var errorMessage: String = "",
)
@@ -113,6 +113,7 @@ class CoreVpnService : VpnService(), ServiceControl {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtil.i(AppConfig.TAG, "StartCore-VPN: Service command received")
NotificationManager.showNotification(null)
setupVpnService()
startService()
return START_STICKY
@@ -457,7 +457,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
AppConfig.MSG_STATE_START_FAILURE -> {
getApplication<AngApplication>().toastError(R.string.toast_services_failure)
val errorMessage = intent.getStringExtra("content")
if (!errorMessage.isNullOrBlank()) {
getApplication<AngApplication>().toastError(errorMessage)
} else {
getApplication<AngApplication>().toastError(R.string.toast_services_failure)
}
isRunning.value = false
}