解决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
相关产品推荐
相关产品推荐

