Refactor: refactor network observer

This commit is contained in:
kr328 2021-11-21 16:55:55 +08:00
parent 7e46d75159
commit 7809c30052
2 changed files with 81 additions and 59 deletions

View File

@ -62,9 +62,9 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
true true
} }
network.onEvent { e -> network.onEvent { n ->
if (Build.VERSION.SDK_INT in 22..28) @TargetApi(22) { if (Build.VERSION.SDK_INT in 22..28) @TargetApi(22) {
setUnderlyingNetworks(e.network?.let { arrayOf(it) }) setUnderlyingNetworks(n?.let { arrayOf(it) })
} }
false false

View File

@ -1,105 +1,127 @@
package com.github.kr328.clash.service.clash.module package com.github.kr328.clash.service.clash.module
import android.app.Service import android.app.Service
import android.content.Intent
import android.net.* import android.net.*
import android.os.PowerManager import android.os.Build
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.github.kr328.clash.common.log.Log import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.core.Clash import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.service.util.resolveDns import com.github.kr328.clash.service.util.resolveDns
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.selects.select import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class NetworkObserveModule(service: Service) : class NetworkObserveModule(service: Service) : Module<Network?>(service) {
Module<NetworkObserveModule.NetworkChanged>(service) { private data class Action(val type: Type, val network: Network) {
data class NetworkChanged(val network: Network?) enum class Type { Available, Lost, Changed }
}
private val connectivity = service.getSystemService<ConnectivityManager>()!! private val connectivity = service.getSystemService<ConnectivityManager>()!!
private val networks: Channel<Network?> = Channel(Channel.CONFLATED) private val actions = Channel<Action>(Channel.UNLIMITED)
private val request = NetworkRequest.Builder().apply { private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
}.build() }.build()
private val callback = object : ConnectivityManager.NetworkCallback() { private val callback = object : ConnectivityManager.NetworkCallback() {
private var network: Network? = null
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
if (this.network != network) actions.trySendBlocking(Action(Action.Type.Available, network))
networks.trySend(network) }
this.network = network override fun onLost(network: Network) {
actions.trySendBlocking(Action(Action.Type.Lost, network))
} }
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
if (this.network == network) actions.trySendBlocking(Action(Action.Type.Changed, network))
networks.trySend(network)
}
}
private fun register(): Result<Unit> {
return runCatching {
connectivity.registerNetworkCallback(request, callback)
}.onFailure {
Log.w("Observe network change: $it", it)
}
}
private fun unregister(): Result<Unit> {
return runCatching {
connectivity.unregisterNetworkCallback(callback)
} }
} }
override suspend fun run() { override suspend fun run() {
val screenToggle = receiveBroadcast(false, Channel.CONFLATED) { try {
addAction(Intent.ACTION_SCREEN_ON) connectivity.registerNetworkCallback(request, callback)
addAction(Intent.ACTION_SCREEN_OFF) } catch (e: Exception) {
} Log.w("Observe network failed: $e", e)
if (service.getSystemService<PowerManager>()?.isInteractive != false) { return
register()
} }
try { try {
var current: Network? = null
val networks = mutableSetOf<Network>()
while (true) { while (true) {
val quit = select<Boolean> { val action = actions.receive()
screenToggle.onReceive {
when (it.action) { when (action.type) {
Intent.ACTION_SCREEN_ON -> Action.Type.Available -> {
register().isFailure networks.add(action.network)
Intent.ACTION_SCREEN_OFF -> }
unregister().isFailure Action.Type.Lost -> {
else -> networks.remove(action.network)
false }
Action.Type.Changed -> {
if (current == action.network) {
val dns = connectivity.resolveDns(action.network)
Clash.notifyDnsChanged(dns)
Log.d("Current network changed: ${action.network}: $dns")
} }
}
networks.onReceive {
val dns = connectivity.resolveDns(it)
Clash.notifyDnsChanged(dns) continue
Log.d("Network changed, system dns = $dns")
enqueueEvent(NetworkChanged(it))
false
} }
} }
if (quit) {
return current = networks.maxByOrNull {
connectivity.getNetworkCapabilities(it)?.let { cap ->
TRANSPORT_PRIORITY.indexOfFirst { cap.hasTransport(it) }
} ?: -1
} }
val dns = connectivity.resolveDns(current)
Clash.notifyDnsChanged(dns)
enqueueEvent(current)
Log.d("Available network changed: $current of $networks: $dns")
} }
} finally { } finally {
withContext(NonCancellable) { withContext(NonCancellable) {
unregister() enqueueEvent(null)
Clash.notifyDnsChanged(emptyList()) Clash.notifyDnsChanged(emptyList())
runCatching {
connectivity.unregisterNetworkCallback(callback)
}
} }
} }
} }
companion object {
private val TRANSPORT_PRIORITY = sequence {
yield(NetworkCapabilities.TRANSPORT_CELLULAR)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
yield(NetworkCapabilities.TRANSPORT_LOWPAN)
}
yield(NetworkCapabilities.TRANSPORT_BLUETOOTH)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
yield(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
}
yield(NetworkCapabilities.TRANSPORT_WIFI)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
yield(NetworkCapabilities.TRANSPORT_USB)
}
yield(NetworkCapabilities.TRANSPORT_ETHERNET)
}.toList()
}
} }