Wi-Fi和数据共存怎么实现

0 阅读12分钟

我来详细分析 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 会:

  1. 查询 AccessNetworksManager 获取该能力的首选传输
  2. 如果首选是 WLAN,则通过 IWLAN 建立连接
  3. 如果首选是 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 流程:

  1. QNS 检测到需要切换传输类型(例如 Wi-Fi 信号变弱)
  2. AccessNetworksManager 更新首选传输
  3. DataNetworkController 评估是否需要 handover
  4. 在目标传输上建立新的数据连接
  5. 切换数据流,然后断开源传输
    /**
     * 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 和移动数据同时连接

  1. Wi-Fi 连接成功 → WifiNetworkAgent 向 ConnectivityService 注册(score=60)
  2. 移动数据保持连接 → DataNetwork 向 ConnectivityService 注册(score=50)
  3. ConnectivityService 选择 Wi-Fi 作为默认网络(因为 score 更高)
  4. 移动数据网络保持激活但不作为默认,可供特定应用或 multinetwork API 使用

场景 2: VoWiFi (IWLAN) + 普通上网

  1. IMS APN 通过 IWLAN 传输(在 Wi-Fi 上)
  2. Internet APN 仍然通过移动数据传输
  3. 两者同时存在,分别在不同的物理网络上

场景 3: 网络切换 (Handover)

  1. 用户从室外(4G)走进室内(Wi-Fi 覆盖)
  2. QNS 检测到 Wi-Fi 信号良好,决定 IMS 切换到 IWLAN
  3. AccessNetworksManager 更新首选传输为 WLAN
  4. IMS 数据连接从 4G handover 到 Wi-Fi(IWLAN)
  5. 过程中语音通话不会中断(无缝切换)

关键点总结

  1. Wi-Fi 和移动数据可以同时激活,但通常只有一个作为系统默认网络
  2. NetworkScore 决定默认网络,Wi-Fi(60) 通常高于移动数据(50)
  3. IWLAN 是特殊的移动数据传输,承载在 Wi-Fi 物理链路上
  4. AccessNetworksManager 管理 APN 级别的传输选择,不同 APN 可以走不同传输
  5. Handover 机制实现了 IWLAN 和 Cellular 之间的无缝切换
  6. 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" />

注意事项

  1. 计费问题: 同时使用移动数据和 Wi-Fi 会消耗移动数据流量,需要向用户明确告知并提供开关选项

  2. 电量消耗: 同时维持两个网络连接会增加电量消耗

  3. 网络策略: 需要检查 getMultipathPreference() 来确认系统是否允许使用多路径传输(例如在计费网络上可能被限制)

  4. 生命周期管理: 必须正确管理 NetworkCallback 的注册和注销,避免内存泄漏

  5. MPTCP 支持: 并非所有 Android 设备和服务器都支持 MPTCP,需要做好降级处理

总结

通过以上方法,Android 应用可以同时使用 Wi-Fi 和移动数据传输数据,主要有两种途径:

  1. MPTCP(内核层): 透明地在多条路径上传输,对应用透明
  2. Multi-Network API(应用层): 显式请求多个网络,分别创建 socket 并绑定到不同网络

这些机制既可以用于带宽聚合(提高速度),也可以用于冗余备份(提高可靠性)。