Просмотр исходного кода

Refactor: refactor network observer

kr328 4 лет назад
Родитель
Сommit
7809c30052

+ 2 - 2
service/src/main/java/com/github/kr328/clash/service/TunService.kt

@@ -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

+ 77 - 55
service/src/main/java/com/github/kr328/clash/service/clash/module/NetworkObserveModule.kt

@@ -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)
-
-            this.network = network
-        }
-
-        override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
-            if (this.network == network)
-                networks.trySend(network)
+            actions.trySendBlocking(Action(Action.Type.Available, network))
         }
-    }
 
-    private fun register(): Result<Unit> {
-        return runCatching {
-            connectivity.registerNetworkCallback(request, callback)
-        }.onFailure {
-            Log.w("Observe network change: $it", it)
+        override fun onLost(network: Network) {
+            actions.trySendBlocking(Action(Action.Type.Lost, network))
         }
-    }
 
-    private fun unregister(): Result<Unit> {
-        return runCatching {
-            connectivity.unregisterNetworkCallback(callback)
+        override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
+            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
-                        }
-                    }
-                    networks.onReceive {
-                        val dns = connectivity.resolveDns(it)
+                val action = actions.receive()
 
-                        Clash.notifyDnsChanged(dns)
+                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)
 
-                        Log.d("Network changed, system dns = $dns")
+                            Clash.notifyDnsChanged(dns)
 
-                        enqueueEvent(NetworkChanged(it))
+                            Log.d("Current network changed: ${action.network}: $dns")
+                        }
 
-                        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()
+    }
 }