我来详细分析 Android 中 Wi-Fi 和移动数据共存的实现机制。
好的,我现在详细为你分析 Android 中 Wi-Fi 和移动数据共存的实现机制。
Wi-Fi 和移动数据共存机制概述
在 Android 系统中,Wi-Fi 和移动数据可以同时存在,但通常情况下系统会选择一个作为默认网络。Android 通过以下核心机制实现共存:
1. 多网络并存架构
Android 使用 ConnectivityService 统一管理所有网络连接,支持多个网络同时激活:
- Wi-Fi 通过
WifiNetworkAgent注册为TRANSPORT_WIFI - 移动数据 通过
DataNetwork注册为TRANSPORT_CELLULAR - IWLAN(Wi-Fi Calling) 作为特殊的移动数据,在 Wi-Fi 上承载
2. 网络评分与选择机制
ConnectivityService 通过 NetworkScore 来决定哪个网络作为默认网络:
mScore = new NetworkScore.Builder().setLegacyInt(100).build();
break;
case TRANSPORT_ETHERNET:
mScore = new NetworkScore.Builder().setLegacyInt(70).build();
break;
case TRANSPORT_WIFI:
mScore = new NetworkScore.Builder().setLegacyInt(60).build();
break;
case TRANSPORT_CELLULAR:
mScore = new NetworkScore.Builder().setLegacyInt(50).build();
break;
case TRANSPORT_WIFI_AWARE:
mScore = new NetworkScore.Builder().setLegacyInt(20).build();
break;
case TRANSPORT_TEST:
mScore = new NetworkScore.Builder().build();
break;
case TRANSPORT_VPN:
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
// VPNs deduce the SUSPENDED capability from their underlying networks and there
// is no public API to let VPN services set it.
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
mScore = new NetworkScore.Builder().setLegacyInt(101).build();
默认评分优先级:
- VPN: 101
- 以太网: 70
- Wi-Fi: 60
- 移动数据: 50
- Wi-Fi Aware: 20
因此,当 Wi-Fi 和移动数据同时可用时,Wi-Fi 通常成为默认网络。
3. IWLAN 与移动数据的特殊共存 (AP-Assisted Mode)
Android 支持 IWLAN(IP-based Wireless LAN),用于 VoWiFi 和 Wi-Fi Calling:
/**
* Get current data network type.
*
* Note that for IWLAN AP-assisted mode device, which is reporting both camped access networks
* (cellular RAT and IWLAN)at the same time, this API is simulating the old legacy mode device
* behavior,
*
* @return Current data network type
* @hide
*/
@TestApi
public @NetworkType int getDataNetworkType() {
final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
final NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
// For legacy mode device, or AP-assisted mode device but IWLAN is out of service, use
// the RAT from cellular.
if (iwlanRegInfo == null || !iwlanRegInfo.isInService()) {
return (wwanRegInfo != null) ? wwanRegInfo.getAccessNetworkTechnology()
: TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
// At this point, it must be an AP-assisted mode device and IWLAN is in service. We should
// use the RAT from IWLAN service is cellular is out of service, or when both are in service
// and any APN type of data is preferred on IWLAN.
if (!wwanRegInfo.isInService() || mIsIwlanPreferred) {
return iwlanRegInfo.getAccessNetworkTechnology();
}
// If both cellular and IWLAN are in service, but no APN is predferred on IWLAN, still use
// the RAT from cellular.
return wwanRegInfo.getAccessNetworkTechnology();
}
在 AP-Assisted 模式下,设备可以同时注册在蜂窝网络和 IWLAN 上,系统根据 QNS(Qualified Networks Service) 的策略决定数据走哪个通道。
4. AccessNetworksManager - 动态传输选择
AccessNetworksManager 负责管理不同 APN 类型在 WWAN (移动数据) 和 WLAN (IWLAN) 之间的动态切换:
private static int getTransportFromAccessNetwork(int accessNetwork) {
return accessNetwork == AccessNetworkType.IWLAN
? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
: AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
}
private void setPreferredTransports(
@NonNull List<QualifiedNetworks> networksList, boolean forceReconnect) {
for (QualifiedNetworks networks : networksList) {
if (networks.qualifiedNetworks.length > 0) {
int transport = getTransportFromAccessNetwork(networks.qualifiedNetworks[0]);
if (getPreferredTransport(networks.apnType) != transport) {
mPreferredTransports.put(networks.apnType, transport);
mAccessNetworksManagerCallbacks.forEach(callback ->
callback.invokeFromExecutor(() ->
callback.onPreferredTransportChanged(
DataUtils.apnTypeToNetworkCapability(networks.apnType),
forceReconnect)));
logl("setPreferredTransports: apnType="
+ ApnSetting.getApnTypeString(networks.apnType) + ", transport="
+ AccessNetworkConstants.transportTypeToString(transport)
+ (forceReconnect ? ", forceReconnect:true" : ""));
}
}
}
}
/**
* Get the preferred transport.
*
* @param apnType APN type
* @return The preferred transport.
*/
@TransportType
public int getPreferredTransport(@ApnType int apnType) {
return mPreferredTransports.get(apnType) == null
? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mPreferredTransports.get(apnType);
}
/**
* Get the preferred transport by network capability.
*
* @param networkCapability The network capability. (Note that only APN-type capabilities are
* supported.)
* @return The preferred transport.
*/
@TransportType
public int getPreferredTransportByNetworkCapability(@NetCapability int networkCapability) {
int apnType = DataUtils.networkCapabilityToApnType(networkCapability);
// For non-APN type capabilities, always route to WWAN.
if (apnType == ApnSetting.TYPE_NONE) {
return AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
}
return getPreferredTransport(apnType);
}
核心功能:
- QNS(Qualified Networks Service) 根据网络质量、漫游状态、运营商策略等动态决定首选传输类型
- 不同 APN 类型(如 IMS、Internet、MMS)可以同时使用不同的传输
- 例如:IMS 可能在 IWLAN 上,而普通上网在移动数据上
5. TelephonyNetworkFactory - 移动数据的网络请求处理
private int getTransportTypeFromNetworkRequest(TelephonyNetworkRequest networkRequest) {
int transport = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
int capability = networkRequest.getApnTypeNetworkCapability();
if (capability >= 0) {
transport = mAccessNetworksManager
.getPreferredTransportByNetworkCapability(capability);
}
return transport;
}
当 ConnectivityService 发出网络请求时,TelephonyNetworkFactory 会:
- 查询
AccessNetworksManager获取该能力的首选传输 - 如果首选是 WLAN,则通过 IWLAN 建立连接
- 如果首选是 WWAN,则通过移动数据建立连接
6. 数据网络的 Handover 机制
当网络条件变化时,数据连接可以在 Wi-Fi(IWLAN) 和移动数据(Cellular) 之间无缝切换:
/**
* The handover state. This is the state when data network handover between IWLAN and cellular.
*
* @see DataNetwork for the state machine diagram.
*/
private final class HandoverState extends State {
@Override
public void enter() {
removeMessages(EVENT_CANCEL_HANDOVER_NO_RESPONSE);
sendMessageDelayed(EVENT_STUCK_IN_TRANSIENT_STATE,
mDataConfigManager.getNetworkHandoverTimeoutMs());
notifyPreciseDataConnectionState();
updateNetworkScore(true /* keepConnectedForHandover */);
Handover 流程:
- QNS 检测到需要切换传输类型(例如 Wi-Fi 信号变弱)
AccessNetworksManager更新首选传输DataNetworkController评估是否需要 handover- 在目标传输上建立新的数据连接
- 切换数据流,然后断开源传输
/**
* Update the network score and report to connectivity service if necessary.
*
* @param keepConnectedForHandover indicate handover is in progress or not.
*/
private void updateNetworkScore(boolean keepConnectedForHandover) {
int connectedReason = keepConnectedForHandover
? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER : NetworkScore.KEEP_CONNECTED_NONE;
if (mNetworkScore.getKeepConnectedReason() != connectedReason) {
mNetworkScore = new NetworkScore.Builder()
.setKeepConnectedReason(connectedReason).build();
mNetworkAgent.sendNetworkScore(mNetworkScore);
}
}
在 handover 期间,网络会被标记为 KEEP_CONNECTED_FOR_HANDOVER,防止被 ConnectivityService 断开。
7. 应用层的多网络使用
虽然系统只有一个默认网络,但应用可以通过 ConnectivityManager API 同时使用多个网络:
requestNetwork(): 请求特定类型的网络bindProcessToNetwork(): 将进程绑定到特定网络Network.openConnection(): 通过特定网络建立连接
public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
int userId, String callingPackageName, @Nullable String callingAttributionTag) {
// The basic principle is: if an app's traffic could possibly go over a
// network, without the app doing anything multinetwork-specific,
// (hence, by "default"), then include that network's capabilities in
// the array.
//
// In the normal case, app traffic only goes over the system's default
// network connection, so that's the only network returned.
//
// With a VPN in force, some app traffic may go into the VPN, and thus
// over whatever underlying networks the VPN specifies, while other app
// traffic may go over the system default network (e.g.: a split-tunnel
// VPN, or an app disallowed by the VPN), so the set of networks
// returned includes the VPN's underlying networks and the system
// default.
enforceAccessPermission();
HashMap<Network, NetworkCapabilities> result = new HashMap<>();
for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
if (!nri.isBeingSatisfied()) {
continue;
}
final NetworkAgentInfo nai = nri.getSatisfier();
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
if (null != nc
&& nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
&& !result.containsKey(nai.network)) {
result.put(
nai.network,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
典型场景
场景 1: Wi-Fi 和移动数据同时连接
- Wi-Fi 连接成功 → WifiNetworkAgent 向 ConnectivityService 注册(score=60)
- 移动数据保持连接 → DataNetwork 向 ConnectivityService 注册(score=50)
- ConnectivityService 选择 Wi-Fi 作为默认网络(因为 score 更高)
- 移动数据网络保持激活但不作为默认,可供特定应用或 multinetwork API 使用
场景 2: VoWiFi (IWLAN) + 普通上网
- IMS APN 通过 IWLAN 传输(在 Wi-Fi 上)
- Internet APN 仍然通过移动数据传输
- 两者同时存在,分别在不同的物理网络上
场景 3: 网络切换 (Handover)
- 用户从室外(4G)走进室内(Wi-Fi 覆盖)
- QNS 检测到 Wi-Fi 信号良好,决定 IMS 切换到 IWLAN
AccessNetworksManager更新首选传输为 WLAN- IMS 数据连接从 4G handover 到 Wi-Fi(IWLAN)
- 过程中语音通话不会中断(无缝切换)
关键点总结
- Wi-Fi 和移动数据可以同时激活,但通常只有一个作为系统默认网络
- NetworkScore 决定默认网络,Wi-Fi(60) 通常高于移动数据(50)
- IWLAN 是特殊的移动数据传输,承载在 Wi-Fi 物理链路上
- AccessNetworksManager 管理 APN 级别的传输选择,不同 APN 可以走不同传输
- Handover 机制实现了 IWLAN 和 Cellular 之间的无缝切换
- ConnectivityService 统一管理所有网络,支持多网络并存和动态切换
这种架构既保证了用户体验(自动选择最优网络),又提供了灵活性(特定应用可使用特定网络),还支持了运营商需求(VoWiFi、Wi-Fi Calling 等高级功能)。
Wi-Fi和数据怎么同时传输某个应用的数据 我来详细解释 Android 中如何让 Wi-Fi 和移动数据同时传输同一个应用的数据。
好的,我来详细解释如何让 Wi-Fi 和移动数据同时传输同一个应用的数据。
核心方法
Android 提供了两种主要机制让应用同时使用多个网络:
1. Multipath TCP (MPTCP) - 协议层同时使用多路径
MPTCP 是内核支持的协议,可以让单个 TCP 连接同时使用多条网络路径(例如 Wi-Fi + 移动数据):
IPPROTO_MPTCP = 262, /* Multipath TCP connection */
#define IPPROTO_MPTCP IPPROTO_MPTCP
工作原理:
- 应用创建 MPTCP socket(协议类型为
IPPROTO_MPTCP) - 内核会自动在可用的多个网络接口上建立子流(subflow)
- 数据可以同时在 Wi-Fi 和移动数据上传输,提高带宽和可靠性
- 对应用层透明,就像使用普通 TCP 一样
Multipath 偏好设置:
/**
* It is acceptable to use small amounts of multipath data on an ongoing basis to provide
* a backup channel for traffic that is primarily going over another network.
*
* An example might be maintaining backup connections to peers or servers for the purpose of
* fast fallback if the default network is temporarily unresponsive or disconnects. The traffic
* on backup paths should be negligible compared to the traffic on the main path.
*/
public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1;
/**
* It is acceptable to use metered data to improve network latency and performance.
*/
public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2;
/**
* Return value to use for unmetered networks. On such networks we currently set all the flags
* to true.
* @hide
*/
public static final int MULTIPATH_PREFERENCE_UNMETERED =
MULTIPATH_PREFERENCE_HANDOVER |
MULTIPATH_PREFERENCE_RELIABILITY |
MULTIPATH_PREFERENCE_PERFORMANCE;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
MULTIPATH_PREFERENCE_HANDOVER,
MULTIPATH_PREFERENCE_RELIABILITY,
MULTIPATH_PREFERENCE_PERFORMANCE,
})
public @interface MultipathPreference {
}
/**
* Provides a hint to the calling application on whether it is desirable to use the
* multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.)
* for multipath data transfer on this network when it is not the system default network.
* Applications desiring to use multipath network protocols should call this method before
* each such operation.
*
* @param network The network on which the application desires to use multipath data.
* If {@code null}, this method will return a preference that will generally
* apply to metered networks.
* @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public @MultipathPreference int getMultipathPreference(@Nullable Network network) {
try {
return mService.getMultipathPreference(network);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
应用可以调用 getMultipathPreference() 查询系统是否建议使用多路径传输。
2. Multi-Network API - 应用层显式使用多个网络
应用可以通过 Android 的 Multi-Network API 主动请求和使用多个网络:
方法 A: 请求多个网络并分别绑定 Socket
// 1. 请求 Wi-Fi 网络
NetworkRequest wifiRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
cm.requestNetwork(wifiRequest, new NetworkCallback() {
@Override
public void onAvailable(Network wifiNetwork) {
// 2. 创建 socket 并绑定到 Wi-Fi
Socket wifiSocket = new Socket();
wifiNetwork.bindSocket(wifiSocket);
// 3. 使用 wifiSocket 进行数据传输
wifiSocket.connect(new InetSocketAddress("example.com", 443));
// ... 发送/接收数据
}
});
// 4. 同时请求移动数据网络
NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
cm.requestNetwork(cellRequest, new NetworkCallback() {
@Override
public void onAvailable(Network cellNetwork) {
// 5. 创建另一个 socket 并绑定到移动数据
Socket cellSocket = new Socket();
cellNetwork.bindSocket(cellSocket);
// 6. 使用 cellSocket 进行数据传输
cellSocket.connect(new InetSocketAddress("example.com", 443));
// ... 发送/接收数据
}
});
关键 API - Network.bindSocket():
/**
* Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
* will be sent on this {@code Network}, irrespective of any process-wide network binding set by
* {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
*/
public void bindSocket(Socket socket) throws IOException {
// Query a property of the underlying socket to ensure that the socket's file descriptor
// exists, is available to bind to a network and is not closed.
socket.getReuseAddress();
// ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and
// the dup share the underlying socket in the kernel. The socket is never truly closed
// until the last fd pointing to the socket being closed. Try and eventually close the dup
// one after binding the socket to control the lifetime of the dup fd.
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
bindSocket(pfd.getFileDescriptor());
}
}
/**
* Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the
* socket represented by this file descriptor will be sent on this {@code Network},
* irrespective of any process-wide network binding set by
* {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
*/
public void bindSocket(FileDescriptor fd) throws IOException {
try {
final SocketAddress peer = Os.getpeername(fd);
final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress();
if (!inetPeer.isAnyLocalAddress()) {
// Apparently, the kernel doesn't update a connected UDP socket's
// routing upon mark changes.
throw new SocketException("Socket is connected");
}
} catch (ErrnoException e) {
// getpeername() failed.
if (e.errno != OsConstants.ENOTCONN) {
throw e.rethrowAsSocketException();
}
} catch (ClassCastException e) {
// Wasn't an InetSocketAddress.
throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
}
final int err = NetworkUtils.bindSocketToNetwork(fd, netId);
if (err != 0) {
// bindSocketToNetwork returns negative errno.
throw new ErrnoException("Binding socket to network " + netId, -err)
.rethrowAsSocketException();
}
}
通过 bindSocket(),可以强制指定 socket 使用特定网络,不受系统默认网络影响。
方法 B: 使用 Network-specific API
// 获取 Wi-Fi 网络
Network wifiNetwork = getWifiNetwork(); // 通过 NetworkCallback 获取
// 使用 Wi-Fi 网络打开 HTTP 连接
URL url = new URL("https://example.com/data");
HttpURLConnection wifiConnection = (HttpURLConnection) wifiNetwork.openConnection(url);
// 同时获取移动数据网络
Network cellNetwork = getCellNetwork();
// 使用移动数据网络打开另一个 HTTP 连接
HttpURLConnection cellConnection = (HttpURLConnection) cellNetwork.openConnection(url);
// 可以同时从两个连接下载数据
InputStream wifiStream = wifiConnection.getInputStream();
InputStream cellStream = cellConnection.getInputStream();
完整示例:应用同时使用 Wi-Fi 和移动数据下载文件
public class MultiNetworkDownloader {
private ConnectivityManager cm;
private Network wifiNetwork;
private Network cellNetwork;
public void startMultiNetworkDownload(String url) {
cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
// 请求 Wi-Fi 网络
NetworkRequest wifiRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
cm.requestNetwork(wifiRequest, new NetworkCallback() {
@Override
public void onAvailable(Network network) {
wifiNetwork = network;
startDownloadOnNetwork(network, url, "wifi");
}
});
// 请求移动数据网络
NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
cm.requestNetwork(cellRequest, new NetworkCallback() {
@Override
public void onAvailable(Network network) {
cellNetwork = network;
startDownloadOnNetwork(network, url, "cellular");
}
});
}
private void startDownloadOnNetwork(Network network, String urlString, String tag) {
new Thread(() -> {
try {
URL url = new URL(urlString);
// 使用特定网络打开连接
HttpURLConnection conn = (HttpURLConnection) network.openConnection(url);
InputStream in = conn.getInputStream();
byte[] buffer = new byte[8192];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = in.read(buffer)) != -1) {
totalBytes += bytesRead;
// 写入文件或处理数据
Log.d("Download", tag + " downloaded: " + totalBytes + " bytes");
}
in.close();
} catch (IOException e) {
Log.e("Download", tag + " error", e);
}
}).start();
}
}
实际应用场景
场景 1: 视频流媒体应用
- **主流(高质量视频)**通过 Wi-Fi 下载
- **备份流(低质量或音频)**通过移动数据下载
- 当 Wi-Fi 卡顿时,立即切换到移动数据流,保证播放连续性
场景 2: 大文件下载器
- 同时使用 Wi-Fi 和移动数据下载不同的文件分片
- 下载完成后合并
- 提高下载速度(带宽叠加)
场景 3: 实时通信应用
- 使用 MPTCP 建立连接
- 数据同时通过 Wi-Fi 和移动数据发送
- 一条路径失败时自动切换到另一条,保证通话质量
权限要求
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
注意事项
-
计费问题: 同时使用移动数据和 Wi-Fi 会消耗移动数据流量,需要向用户明确告知并提供开关选项
-
电量消耗: 同时维持两个网络连接会增加电量消耗
-
网络策略: 需要检查
getMultipathPreference()来确认系统是否允许使用多路径传输(例如在计费网络上可能被限制) -
生命周期管理: 必须正确管理
NetworkCallback的注册和注销,避免内存泄漏 -
MPTCP 支持: 并非所有 Android 设备和服务器都支持 MPTCP,需要做好降级处理
总结
通过以上方法,Android 应用可以同时使用 Wi-Fi 和移动数据传输数据,主要有两种途径:
- MPTCP(内核层): 透明地在多条路径上传输,对应用透明
- Multi-Network API(应用层): 显式请求多个网络,分别创建 socket 并绑定到不同网络
这些机制既可以用于带宽聚合(提高速度),也可以用于冗余备份(提高可靠性)。