23、WiFi与移动网络协同:同时使用WiFi与蜂窝数据(多路径)、NetworkCallback监听网络切换、粘性WiFi策略

这一章,我们来聊聊Android里一个挺有意思的话题——WiFi和蜂窝数据怎么“和平共处”。

说实话,早年做Android开发时,我总觉得这两个东西是“有你没我”的关系。WiFi连上了,蜂窝就歇着;WiFi断了,蜂窝才顶上。但后来发现,用户的需求远不止这么简单。比如你在下载一个大文件,WiFi信号突然变弱了,难道要等断流了再切4G?那体验也太差了。

所以,Android从某个版本开始,就引入了多路径传输、网络监听和粘性WiFi这些机制。说白了,就是让系统更聪明地管理网络资源。

23.1 多路径传输:同时用WiFi和蜂窝

多路径传输,英文叫Multipath。它的核心思想是:不把鸡蛋放在一个篮子里

我参与过一个视频会议App的项目。用户经常抱怨,在办公室走动时,WiFi信号忽强忽弱,视频就卡住了。后来我们用了多路径方案,效果立竿见影。

核心原理:Android允许应用同时绑定WiFi和蜂窝网络。数据包可以走两条路,哪条快就走哪条。

实现方式主要有两种:

  • 系统级多路径:Android 5.0+ 支持多网络同时连接,但默认只路由一个。需要应用主动请求。
  • 应用级多路径:通过 Network 对象手动绑定Socket,自己控制数据流向。

来看一段代码,怎么同时绑定两个网络:

// 获取 ConnectivityManager
ConnectivityManager cm = (ConnectivityManager) 
    context.getSystemService(Context.CONNECTIVITY_SERVICE);

// 请求WiFi网络
NetworkRequest wifiRequest = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    .build();

// 请求蜂窝网络
NetworkRequest cellularRequest = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
    .build();

// 分别绑定
cm.requestNetwork(wifiRequest, wifiCallback);
cm.requestNetwork(cellularRequest, cellularCallback);

嗯,这里要注意:不是所有设备都支持同时绑定。有些厂商的定制ROM会限制这个行为。我在测试时就踩过这个坑——小米某款机型上,蜂窝网络绑定后根本拿不到数据。

避坑指南:我曾经在华为P30上测试多路径,发现蜂窝网络绑定后,Socket发送数据会超时。后来查了日志,发现是系统限制了非活动网络的带宽。解决办法是:在绑定后主动调用 Network.bindSocket() 并设置 SO_PRIORITY

23.2 NetworkCallback:监听网络切换

网络切换,说白了就是WiFi和蜂窝之间“交接班”。但怎么知道什么时候该切换?

Android提供了 NetworkCallback 回调。我个人习惯用这个来监听网络状态变化,比轮询 getActiveNetworkInfo() 优雅多了。

来看一个完整的监听示例:

ConnectivityManager.NetworkCallback callback = 
    new ConnectivityManager.NetworkCallback() {
    
    @Override
    public void onAvailable(Network network) {
        // 网络可用
        Log.d("NetCallback", "网络来了: " + network);
    }

    @Override
    public void onLost(Network network) {
        // 网络断开
        Log.d("NetCallback", "网络丢了: " + network);
    }

    @Override
    public void onCapabilitiesChanged(Network network, 
            NetworkCapabilities capabilities) {
        // 网络能力变化,比如WiFi信号变弱
        boolean hasWifi = capabilities
            .hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
        boolean hasCellular = capabilities
            .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
        Log.d("NetCallback", "WiFi: " + hasWifi + ", 蜂窝: " + hasCellular);
    }

    @Override
    public void onLinkPropertiesChanged(Network network, 
            LinkProperties linkProperties) {
        // 链路属性变化,比如IP地址变了
        Log.d("NetCallback", "链路变了: " + linkProperties);
    }
};

// 注册监听
NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .build();
cm.registerNetworkCallback(request, callback);

你想想看,这个回调能帮我们做什么?

  • onAvailable:网络刚连上,可以开始传输数据了。
  • onLost:网络断了,赶紧切到备用网络。
  • onCapabilitiesChanged:WiFi信号从强变弱,可以提前准备切换。
  • onLinkPropertiesChanged:IP变了,需要重新建立连接。

我的经验:在 onCapabilitiesChanged 里判断WiFi信号强度,比用 WifiManagergetRssi() 更靠谱。因为前者是系统统一管理的,后者可能因为权限问题拿不到。

23.3 粘性WiFi策略:让WiFi“赖着不走”

粘性WiFi,英文叫Sticky WiFi。这个策略很有意思——系统会尽量保持WiFi连接,即使信号已经很差了

为什么会这样?

我记得有一次用户反馈:手机在客厅和卧室之间走动,WiFi信号从-50dBm掉到-80dBm,系统就是不切4G。用户觉得是Bug,但其实这是Google的设计哲学——WiFi优先,因为WiFi通常更便宜、更稳定。

粘性WiFi的实现机制:

  • 信号阈值:系统有一个“粘性阈值”,比如-85dBm。低于这个值才会考虑切蜂窝。
  • 切换延迟:即使信号低于阈值,系统也会等几秒钟,看看信号会不会恢复。
  • 网络评分:Android 7.0+ 引入了网络评分机制。WiFi和蜂窝各有评分,系统选分数高的。

来看一张流程图,展示粘性WiFi的决策逻辑:

粘性WiFi决策流程图 WiFi连接中 信号 < -85dBm? 保持WiFi连接 等待3秒 信号恢复? 切换到蜂窝 使用蜂窝数据

从流程图可以看出,粘性WiFi的核心就是“能忍则忍”。系统会尽量让WiFi多撑一会儿,实在不行了才切蜂窝。

注意:粘性WiFi策略在某些场景下会带来问题。比如用户在看视频,WiFi信号很差但系统不切4G,视频就会一直缓冲。我曾经在某个直播App里遇到过,用户骂我们“卡成PPT”,其实是系统策略的问题。

解决办法是什么?

应用可以主动干预。通过 NetworkCallbackonCapabilitiesChanged 判断信号强度,如果太差,就主动请求蜂窝网络:

@Override
public void onCapabilitiesChanged(Network network, 
        NetworkCapabilities capabilities) {
    int wifiSignalStrength = capabilities
        .getSignalStrength();
    
    // 信号强度低于-80dBm,主动请求蜂窝
    if (wifiSignalStrength < -80) {
        NetworkRequest cellularRequest = 
            new NetworkRequest.Builder()
            .addTransportType(
                NetworkCapabilities.TRANSPORT_CELLULAR)
            .build();
        cm.requestNetwork(cellularRequest, cellularCallback);
    }
}

嗯,这里有个细节:getSignalStrength() 返回的是0-4的整数,不是dBm。需要自己换算。我一般用这个公式:dBm = -100 + (signalStrength * 5)。当然,不同厂商可能有差异,最好在真机上测一下。

23.4 总结一下

这一章我们聊了三个核心点:

  • 多路径传输:同时绑定WiFi和蜂窝,数据走最优路径。
  • NetworkCallback:监听网络状态变化,及时响应。
  • 粘性WiFi:系统优先保持WiFi,信号差也不轻易切。

说实话,这三个机制在实际项目中非常实用。尤其是做视频、直播、下载类App,网络切换的体验直接影响用户留存。我个人建议,不要完全依赖系统默认策略,应用层一定要有自己的网络监控和切换逻辑。

最后,留个思考题:如果你的App需要同时下载大文件和保持低延迟通话,你会怎么设计网络策略?欢迎在实践中摸索。


公众号:蓝海资料掘金营,微信deep3321