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
}
network.onEvent { e ->
network.onEvent { n ->
if (Build.VERSION.SDK_INT in 22..28) @TargetApi(22) {
setUnderlyingNetworks(e.network?.let { arrayOf(it) })
setUnderlyingNetworks(n?.let { arrayOf(it) })
}
false

View File

@ -1,105 +1,127 @@
package com.github.kr328.clash.service.clash.module
import android.app.Service
import android.content.Intent
import android.net.*
import android.os.PowerManager
import android.os.Build
import androidx.core.content.getSystemService
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.service.util.resolveDns
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.withContext
class NetworkObserveModule(service: Service) :
Module<NetworkObserveModule.NetworkChanged>(service) {
data class NetworkChanged(val network: Network?)
class NetworkObserveModule(service: Service) : Module<Network?>(service) {
private data class Action(val type: Type, val network: Network) {
enum class Type { Available, Lost, Changed }
}
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 {
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
}.build()
private val callback = object : ConnectivityManager.NetworkCallback() {
private var network: Network? = null
override fun onAvailable(network: Network) {
if (this.network != network)
networks.trySend(network)
actions.trySendBlocking(Action(Action.Type.Available, network))
}
this.network = network
override fun onLost(network: Network) {
actions.trySendBlocking(Action(Action.Type.Lost, network))
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
if (this.network == 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)
actions.trySendBlocking(Action(Action.Type.Changed, network))
}
}
override suspend fun run() {
val screenToggle = receiveBroadcast(false, Channel.CONFLATED) {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
}
try {
connectivity.registerNetworkCallback(request, callback)
} catch (e: Exception) {
Log.w("Observe network failed: $e", e)
if (service.getSystemService<PowerManager>()?.isInteractive != false) {
register()
return
}
try {
var current: Network? = null
val networks = mutableSetOf<Network>()
while (true) {
val quit = select<Boolean> {
screenToggle.onReceive {
when (it.action) {
Intent.ACTION_SCREEN_ON ->
register().isFailure
Intent.ACTION_SCREEN_OFF ->
unregister().isFailure
else ->
false
val action = actions.receive()
when (action.type) {
Action.Type.Available -> {
networks.add(action.network)
}
Action.Type.Lost -> {
networks.remove(action.network)
}
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)
Log.d("Network changed, system dns = $dns")
enqueueEvent(NetworkChanged(it))
false
continue
}
}
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 {
withContext(NonCancellable) {
unregister()
enqueueEvent(null)
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()
}
}