Pavlo Sharhan
Pavlo Sharhan

Reputation: 194

Android: cellular network affects socket read_buffer_size of WiFi connections

I am investigating an issue where Wi-Fi transfers between my android app and GoPro are inconsistent across different cellular network conditions. Initially, I noticed that GoPro Quik (Official GoPro app) was able to transfer data efficiently even on 2G networks which wasn’t the case for my app. After some digging into their source code, I realized that Quik overrides OkHttp’s read_buffer_size to 512kb instead of value the okhttp uses depending on Cellular network quality. Once I made the same adjustment in my app, it started transferring data well on 2G, which improved performance in those cases. However, this solution wasn’t perfect. Sometimes, transfers slowed down even on 4G, and I noticed that GoPro Quik also experienced slow transfer speeds in those instances. Interestingly, the speeds improved significantly once the cellular connection was disabled, suggesting there might be some interaction between the cellular and Wi-Fi networks affecting performance. I’m looking for ways of logging buffer sizes to investigate further and exploring how Android or the underlying Linux kernel might be influencing these behaviors.

Any suggestions or theories or where exactly the quality of cellular network may contribute are appreciated.

Here is my code for creating Okhttp client with a custom socket factory that overrides read_buffer_size:

class CustomBufferSocketFactory(
    private val sendBufferSizeBytes: Int,
    private val receiveBufferSizeBytes: Int,
    private val tcpNoDelayEnabled: Boolean = false,
    private val socketFactory: SocketFactory = SocketFactory.getDefault()
) : SocketFactory() {

    override fun createSocket(): Socket {
        return configureSocket(socketFactory.createSocket())
    }

    override fun createSocket(host: String, port: Int): Socket {
        return configureSocket(socketFactory.createSocket(host, port))
    }

    override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket {
        return configureSocket(socketFactory.createSocket(host, port, localHost, localPort))
    }

    override fun createSocket(address: InetAddress, port: Int): Socket {
        return configureSocket(socketFactory.createSocket(address, port))
    }

    override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket {
        return configureSocket(socketFactory.createSocket(address, port, localAddress, localPort))
    }

    private fun configureSocket(socket: Socket): Socket {
        socket.sendBufferSize = sendBufferSizeBytes
        socket.receiveBufferSize = receiveBufferSizeBytes
        socket.tcpNoDelay = tcpNoDelayEnabled
        return socket
    }
}



suspend fun connectToLocalNetwork(
    context: Context,
    ssid: String,
    password: String
): OkHttpClient? {
    return withTimeoutOrNull(15000L) { // Timeout after 15 seconds
        suspendCancellableCoroutine { continuation ->
            var isResumed = false

            val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

            // Use WifiNetworkSpecifier to build a request for the desired Wi-Fi network
            val wifiSpecifier = WifiNetworkSpecifier.Builder()
                .setSsid(ssid)
                .setWpa2Passphrase(password)
                .build()

            // Build the network request
            val networkRequest = NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .removeTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                .setNetworkSpecifier(wifiSpecifier)
                .build()

            // Define the network callback
            val networkCallback = object : ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network) {
                    if (!isResumed && !continuation.isCancelled) {
                        isResumed = true

                        // Bind your app's network traffic to the specific network
                        connectivityManager.bindProcessToNetwork(network)

                        Logger.getLogger(OkHttpClient::class.java.name).setLevel(Level.FINE)

                        // Use OkHttpClientFactory for large file downloads
                        val client =
                            OkHttpClient.Builder()
                                .connectTimeout(60, TimeUnit.SECONDS)
                                .readTimeout(60, TimeUnit.SECONDS)
                                .writeTimeout(60, TimeUnit.SECONDS)
                                .socketFactory(CustomBufferSocketFactory(
                                    524288, 524288, true, network.socketFactory))
                                .build()

                        continuation.resume(client)
                    }
                }

                override fun onUnavailable() {
                    if (!isResumed && !continuation.isCancelled) {
                        isResumed = true
                        continuation.resume(null)
                    }
                }

                override fun onLost(network: Network) {
                    if (!isResumed && !continuation.isCancelled) {
                        isResumed = true
                        continuation.resume(null)
                    }
                }
            }

            connectivityManager.requestNetwork(networkRequest, networkCallback)
        }
    }
}

Upvotes: 1

Views: 55

Answers (0)

Related Questions