You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决Retrofit中OkHttpClient指定Network时异步回调返回null的问题

问题分析

你的核心问题在于ConnectivityManager.requestNetwork是异步API:onAvailable回调会在系统检测到符合条件的网络后才触发,而你的providesSocketNetwork方法是同步执行的,在回调触发前就直接返回了socketNetwork(此时还是null),所以会抛出空指针异常。

解决方案

根据你的使用场景,这里提供两种可行的实现方式:

方式1:同步等待网络可用(适合后台初始化场景)

用CountDownLatch阻塞当前线程,直到onAvailable回调触发或超时。注意:绝对不要在主线程使用该方法,会导致ANR。

修改后的providesSocketNetwork代码:

@Provides
@PerApplication
@Named("SocketNetwork")
fun providesSocketNetwork(context: Context): Network {
    val latch = CountDownLatch(1)
    var targetNetwork: Network? = null
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
    val networkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()

    val networkCallback = object : ConnectivityManager.NetworkCallback() {
        @RequiresApi(Build.VERSION_CODES.M)
        override fun onAvailable(network: Network) {
            targetNetwork = network
            // 注意:如果不需要全局绑定进程,可移除这行
            connectivityManager.bindProcessToNetwork(network)
            latch.countDown()
        }

        override fun onUnavailable() {
            // 网络不可用时触发,避免无限阻塞
            latch.countDown()
        }
    }

    connectivityManager.requestNetwork(networkRequest, networkCallback)

    try {
        // 设置10秒超时,避免无限等待
        if (!latch.await(10, TimeUnit.SECONDS)) {
            throw IllegalStateException("等待蜂窝网络超时")
        }
    } catch (e: InterruptedException) {
        Thread.currentThread().interrupt()
        throw RuntimeException(e)
    } finally {
        // 拿到网络后立即取消回调,防止内存泄漏
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }

    return targetNetwork ?: throw IllegalStateException("无法获取可用的蜂窝网络")
}

方式2:动态监听网络状态(推荐,适配网络变化)

因为网络可能会断开重连,单例的Network可能失效,更健壮的方式是监听网络状态变化,动态获取有效网络并创建OkHttpClient:

1. 创建网络管理类

class CellularNetworkManager @Inject constructor(private val context: Context) {
    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    private val _currentNetwork = MutableStateFlow<Network?>(null)
    // 对外暴露只读的StateFlow,监听网络变化
    val currentNetwork: StateFlow<Network?> = _currentNetwork

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        @RequiresApi(Build.VERSION_CODES.M)
        override fun onAvailable(network: Network) {
            // 不需要全局绑定进程的话可移除这行
            connectivityManager.bindProcessToNetwork(network)
            _currentNetwork.value = network
        }

        override fun onLost(network: Network) {
            if (_currentNetwork.value == network) {
                _currentNetwork.value = null
            }
        }
    }

    init {
        val networkRequest = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()
        connectivityManager.requestNetwork(networkRequest, networkCallback)
    }

    // 在App销毁时调用,清理回调避免内存泄漏
    fun cleanup() {
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }
}

2. Dagger提供依赖

@Provides
@PerApplication
fun providesCellularNetworkManager(context: Context): CellularNetworkManager {
    return CellularNetworkManager(context)
}

@Provides
@PerApplication
fun providesOkHttpBaseBuilder(context: Context): OkHttpClient.Builder {
    val cacheSize = (5 * 1024 * 1024).toLong()
    val cache = Cache(context.cacheDir, cacheSize)
    val loggingInterceptor = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }

    return OkHttpClient.Builder()
        .cache(cache)
        .addNetworkInterceptor(loggingInterceptor)
        .addInterceptor(TimeHeaderInterceptor())
}

3. 使用时动态创建OkHttpClient

在Repository或ViewModel中,监听网络状态并创建对应的客户端:

class DataRepository @Inject constructor(
    private val okHttpBuilder: OkHttpClient.Builder,
    private val networkManager: CellularNetworkManager
) {
    suspend fun fetchData(): Result<Data> {
        // 等待第一个可用的蜂窝网络
        val network = networkManager.currentNetwork.filterNotNull().firstOrNull()
            ?: return Result.failure(IllegalStateException("无可用蜂窝网络"))
        
        val client = okHttpBuilder.socketFactory(network.socketFactory).build()
        // 用client发起网络请求
        return runCatching {
            val response = client.newCall(Request.Builder().url("your_url").build()).execute()
            // 解析response为Data
        }
    }
}
注意事项
  • 如果只是让OkHttpClient使用指定网络,不需要调用bindProcessToNetwork,该方法会把整个进程绑定到该网络,影响其他网络请求的路由。
  • 记得在Application的onDestroy方法中调用CellularNetworkManager.cleanup(),避免内存泄漏。

内容的提问来源于stack exchange,提问作者MARSH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 11:05:17