Android 13 完整实现 USB 网卡支持与网络优先级配置(USB>WiFi>4G)

一、需求背景与核心目标

在嵌入式 Android 设备(如工业平板、智能车机、机顶盒)开发中,常需通过 USB 转网卡扩展稳定的有线网络,但 Android 原生系统存在两大问题:

  1. 对 USB 网卡的识别与配置支持有限,默认不显示 USB 网卡接口;
  2. 网络优先级默认是 WiFi > 以太网 > 4G,无法满足 "USB 网卡优先" 的场景(如工业场景需稳定有线网络)。

本文基于 Android 13 AOSP 源码 ,从 内核驱动→框架层识别→Settings 可视化配置→网络优先级强制调整 全流程落地,最终实现:

  1. USB 网卡自动识别、热插拔兼容与 DHCP / 静态 IP 配置;
  2. 网络优先级严格遵循 USB 网卡(以太网)> WiFi > 4G
  3. 配置持久化与系统权限适配(含 SELinux 规则)。

二、环境准备

类别 具体要求
硬件 1. 支持 USB Host 的 Android 设备(如 RK3568 开发板);2. USB 转网卡(推荐 Realtek RTL8153/ASIX AX88179,驱动兼容性好)。
软件 1. Android 13 AOSP 源码;2. ADB 工具与内核编译环境;3. 文本编辑器(如 VS Code)。
前置知识 了解 Android 网络框架(ConnectivityService)、Udev 设备命名、SELinux 规则、Binder 通信。

三、Step 1:内核层适配 USB 网卡驱动

USB 网卡需内核驱动支持才能被系统识别,需先确保驱动编译进内核。

1.1 启用 USB 网卡驱动配置

内核配置文件路径(以 RK3568 为例):kernel/arch/arm64/configs/rk3568_defconfig

添加以下配置(根据 USB 网卡芯片选择,可通过 lsusb 查看芯片型号):

bash

复制代码
# 通用 USB 以太网驱动(基础依赖)
CONFIG_USB_NET_CDCETHER=y
# Realtek 芯片(如 RTL8153,常见于 USB 3.0 网卡)
CONFIG_USB_NET_R8152=y
# ASIX 芯片(如 AX88179,常见于千兆 USB 网卡)
CONFIG_USB_NET_AX88179_178A=y
# 启用 USB Host 模式(确保设备能识别 USB 外设)
CONFIG_USB_OHCI_HCD=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_XHCI_HCD=y

1.2 编译内核与验证驱动加载

bash

复制代码
# 编译内核(生成 zImage 和设备树 dtb)
make ARCH=arm64 rk3568_defconfig -j8
make ARCH=arm64 -j8

# 刷入内核后,插入 USB 网卡,通过日志验证驱动是否加载
adb shell dmesg | grep -i "usb ethernet"
# 成功日志示例:usb 1-1: r8152: loaded - eth_usb (USB 网卡接口名)

四、Step 2:框架层修改(核心:让系统识别 USB 网卡)

Android 13 以太网核心逻辑已迁移至 packages/modules/Connectivity,需修改 接口识别网络评分 相关文件,确保 USB 网卡被跟踪且优先级高于其他网络。

2.1 固定 USB 网卡接口名(避免随机变化)

USB 网卡默认接口名(如 eth1/enx001e0630a0b1)可能随插拔变化,需通过 Udev 规则固定为 eth_usb,方便上层统一识别。

2.1.1 创建 Udev 规则文件

文件路径:system/core/rootdir/etc/udev/rules.d/70-persistent-net.rules

添加规则(需替换为你的 USB 网卡 MAC 地址,通过 adb shell cat /sys/class/net/<临时接口名>/address 获取):

bash

复制代码
# USB 网卡固定接口名:SUBSYSTEM=="net"(网络设备),ACTION=="add"(设备插入),匹配 MAC 地址后命名为 eth_usb
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:1e:06:30:a0:b1", NAME="eth_usb"
2.1.2 验证接口名

bash

复制代码
# 插入 USB 网卡后,执行以下命令确认接口名为 eth_usb
adb shell ip link show | grep eth_usb
# 成功输出示例:2: eth_usb: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000

2.2 修改 EthernetTracker:识别 USB 网卡接口

EthernetTracker 负责扫描系统网络接口,需扩展其接口匹配正则,确保 eth_usb 被识别为合法以太网接口。

文件路径:packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java

2.2.1 扩展接口匹配正则

找到 updateIfaceMatchRegexp 方法(负责生成接口匹配规则),修改为:

java

运行

复制代码
private void updateIfaceMatchRegexp() {
    // 从系统资源获取默认以太网正则(通常为 "eth\\d+",匹配内置以太网如 eth0)
    final String defaultMatch = mDeps.getInterfaceRegexFromResource(mContext);
    // 新增 USB 网卡接口模式:eth_usb(固定名)+ enx开头(兼容自动命名场景)
    final String usbIfacePattern = "eth_usb|enx[0-9a-fA-F]+";
    // 组合正则:保留原有 eth 接口,新增 USB 网卡支持
    final String extendedMatch = "(" + defaultMatch + "|" + usbIfacePattern + ")";
    // 结合测试接口配置(原有逻辑不变,测试接口如 tap0 不影响实际功能)
    mIfaceMatch = mIncludeTestInterfaces
            ? "(" + extendedMatch + "|" + TEST_IFACE_REGEXP + ")"
            : extendedMatch;
    Log.d(TAG, "USB 网卡接口匹配正则生效:" + mIfaceMatch);
}
2.2.2 验证识别效果

bash

复制代码
# 重启设备后,通过日志确认 USB 网卡被跟踪
adb logcat -s EthernetTracker | grep "Tracking interface"
# 成功日志示例:Tracking interface in client mode: eth_usb(eth_usb 被识别为客户端模式接口)

2.3 修改 EthernetNetworkFactory:提高 USB 网卡优先级

Android 网络优先级由 网络评分(NetworkScore) 决定,评分越高越优先。需修改 EthernetNetworkFactory 中评分生成逻辑,让 USB 网卡评分高于 WiFi 和 4G。

文件路径:packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java

2.3.1 理解评分调用流程

getBestNetworkScore() 是生成以太网评分的核心方法,仅在以下场景被调用:

  1. USB 网卡链路接通时 :链路从 down 变为 up,调用 registerNetworkOffer 注册网络服务,传入评分;
  2. 网络能力更新时:修改 USB 网卡的网络能力(如支持互联网),重新注册服务时传入评分。

调用链简化:updateLinkState(up=true) / setCapabilities()registerNetworkOffer()getBestNetworkScore() → 评分传递给 ConnectivityService。

2.3.2 修改评分生成逻辑

原代码默认返回空评分(无优先级),需设置具体高分(如 100,假设系统评分范围 0-100):

java

运行

复制代码
// NetworkInterfaceState 类中的 getBestNetworkScore 方法
private static NetworkScore getBestNetworkScore() {
    // 核心修改:设置评分 100(高于 WiFi 默认评分 50、4G 默认评分 40)
    return new NetworkScore.Builder()
            .setScore(100)  // 评分值,越高优先级越高
            .build();
}
2.3.3 验证评分生效

bash

复制代码
# 查看 ConnectivityService 日志,确认以太网评分
adb logcat -s ConnectivityService | grep "score"
# 成功日志示例:Ethernet network score=100, selecting as default(以太网评分 100,被选为默认网络)

五、Step 3:Settings 可视化配置(用户可操作 USB 网卡)

需在系统设置中添加 USB 网卡的 接口选择IP 配置 界面,支持用户切换 DHCP / 静态 IP。

3.1 新增布局文件(接口选择与配置项)

文件路径:packages/apps/Settings/res/xml/ethernet_settings.xml

xml

复制代码
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 1. USB 网卡/内置以太网接口选择 -->
    <ListPreference
        android:key="ethernet_interface_selector"
        android:title="@string/ethernet_interface_title"
        android:summary="@string/ethernet_interface_summary"
        android:entries="@array/ethernet_interface_entries"
        android:entryValues="@array/ethernet_interface_values"
        android:defaultValue="eth0" />

    <!-- 2. IP 模式选择(DHCP/静态) -->
    <ListPreference
        android:key="ethernet_ip_mode"
        android:title="@string/ethernet_ip_mode_title"
        android:summary="@string/ethernet_ip_mode_summary"
        android:entries="@array/ethernet_ip_modes"
        android:entryValues="@array/ethernet_ip_mode_values"
        android:defaultValue="dhcp" />

    <!-- 3. 静态 IP 配置项(动态显示/隐藏,默认隐藏) -->
    <EditTextPreference
        android:key="ethernet_static_ip"
        android:title="@string/ethernet_static_ip_title"
        android:hint="192.168.1.100"  <!-- 默认提示 -->
        android:inputType="textUri"    <!-- 限制 IP 格式输入 -->
        android:visible="false" />
    <EditTextPreference
        android:key="ethernet_static_gateway"
        android:title="@string/ethernet_static_gateway_title"
        android:hint="192.168.1.1"
        android:inputType="textUri"
        android:visible="false" />
    <EditTextPreference
        android:key="ethernet_static_netmask"
        android:title="@string/ethernet_static_netmask_title"
        android:hint="255.255.255.0"
        android:inputType="textUri"
        android:visible="false" />
    <EditTextPreference
        android:key="ethernet_static_dns1"
        android:title="@string/ethernet_static_dns1_title"
        android:hint="8.8.8.8"
        android:inputType="textUri"
        android:visible="false" />
</PreferenceScreen>

3.2 新增字符串资源

文件路径:packages/apps/Settings/res/values/strings.xml

xml

复制代码
<!-- USB 网卡配置相关字符串 -->
<string name="ethernet_interface_title">以太网接口</string>
<string name="ethernet_interface_summary">选择需配置的以太网接口</string>
<string-array name="ethernet_interface_entries">
    <item>内置以太网(eth0)</item>
    <item>USB 网卡(eth_usb)</item>
</string-array>
<string-array name="ethernet_interface_values">
    <item>eth0</item>
    <item>eth_usb</item>
</string-array>

<string name="ethernet_ip_mode_title">IP 配置模式</string>
<string name="ethernet_ip_mode_summary">选择 IP 获取方式</string>
<string-array name="ethernet_ip_modes">
    <item>DHCP(自动获取)</item>
    <item>静态 IP(手动配置)</item>
</string-array>
<string-array name="ethernet_ip_mode_values">
    <item>dhcp</item>
    <item>static</item>
</string-array>

<string name="ethernet_static_ip_title">IP 地址</string>
<string name="ethernet_static_gateway_title">网关</string>
<string name="ethernet_static_netmask_title">子网掩码</string>
<string name="ethernet_static_dns1_title">DNS 服务器 1</string>

3.3 实现逻辑代码(刷新接口与保存配置)

文件路径:packages/apps/Settings/src/com/android/settings/ethernet/EthernetSettings.java

核心代码片段(含接口刷新、配置加载、保存逻辑):

java

运行

复制代码
public class EthernetSettings extends PreferenceFragmentCompat {
    private static final String TAG = "EthernetSettings";
    private ListPreference mInterfaceSelector; // 接口选择器
    private ListPreference mIpModeSelector;    // IP 模式选择器
    private EditTextPreference mStaticIp;      // 静态 IP 输入框
    private EthernetManager mEthManager;
    private String mSelectedInterface;         // 当前选中接口(如 eth_usb)

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.ethernet_settings, rootKey);
        mEthManager = getContext().getSystemService(EthernetManager.class);
        initPreferences(); // 初始化偏好设置
    }

    // 初始化选择器与监听事件
    private void initPreferences() {
        // 1. 接口选择器:刷新可用接口(含 USB 网卡)
        mInterfaceSelector = findPreference("ethernet_interface_selector");
        mInterfaceSelector.setOnPreferenceChangeListener((pref, newValue) -> {
            mSelectedInterface = (String) newValue;
            updateIpConfig(); // 切换接口后加载对应配置
            return true;
        });

        // 2. IP 模式选择器:控制静态 IP 项显示/隐藏
        mIpModeSelector = findPreference("ethernet_ip_mode");
        mIpModeSelector.setOnPreferenceChangeListener((pref, newValue) -> {
            boolean isStatic = "static".equals(newValue);
            showStaticIpItems(isStatic); // 静态模式显示输入框,DHCP 隐藏
            return true;
        });

        // 3. 静态 IP 配置项初始化(网关、子网掩码、DNS 类似)
        mStaticIp = findPreference("ethernet_static_ip");
        // 绑定保存按钮(可在布局中添加 ButtonPreference,此处简化为返回键保存)
        getActivity().getOnBackPressedDispatcher().addCallback(this, () -> {
            saveConfig(); // 返回时保存配置
            getActivity().finish();
        });
    }

    // 刷新可用接口列表(含 USB 网卡)
    private void refreshInterfaceList() {
        try {
            // 从 EthernetManager 获取所有可用以太网接口(含 eth_usb)
            String[] ifaces = mEthManager.getAvailableInterfaces();
            List<CharSequence> entries = new ArrayList<>();
            List<CharSequence> values = new ArrayList<>();
            
            for (String iface : ifaces) {
                // 区分内置以太网与 USB 网卡,显示友好名称
                String label = iface.equals("eth_usb") 
                    ? "USB 网卡(" + iface + ")" 
                    : "内置以太网(" + iface + ")";
                entries.add(label);
                values.add(iface);
            }

            // 更新选择器选项
            mInterfaceSelector.setEntries(entries.toArray(new CharSequence[0]));
            mInterfaceSelector.setEntryValues(values.toArray(new CharSequence[0]));
            // 默认选中第一个接口(优先 USB 网卡)
            if (!values.isEmpty()) {
                mSelectedInterface = (String) values.get(0);
                mInterfaceSelector.setValue(mSelectedInterface);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "获取接口列表失败:" + e.getMessage());
        }
    }

    // 加载当前接口的 IP 配置(DHCP/静态)
    private void updateIpConfig() {
        if (mSelectedInterface == null) return;
        // 从 EthernetManager 获取当前接口的配置
        IpConfiguration config = mEthManager.getConfiguration(mSelectedInterface);
        if (config == null) return;

        // 1. 更新 IP 模式选择器
        String mode = config.getIpAssignment() == IpAssignment.STATIC ? "static" : "dhcp";
        mIpModeSelector.setValue(mode);
        showStaticIpItems(mode.equals("static"));

        // 2. 加载静态 IP 配置到输入框(DHCP 模式无需加载)
        if (config.getStaticIpConfiguration() != null) {
            StaticIpConfiguration staticConfig = config.getStaticIpConfiguration();
            // IP 地址(如 192.168.1.100)
            mStaticIp.setText(staticConfig.getIpAddress().getAddress().getHostAddress());
            // 网关、子网掩码、DNS 加载(类似 mStaticIp,略)
        }
    }

    // 显示/隐藏静态 IP 配置项
    private void showStaticIpItems(boolean show) {
        mStaticIp.setVisible(show);
        findPreference("ethernet_static_gateway").setVisible(show);
        findPreference("ethernet_static_netmask").setVisible(show);
        findPreference("ethernet_static_dns1").setVisible(show);
    }

    // 保存 USB 网卡配置(DHCP/静态 IP)
    private void saveConfig() {
        if (mSelectedInterface == null) return;
        IpConfiguration config = new IpConfiguration();

        // 1. 设置 IP 模式(DHCP/静态)
        if ("dhcp".equals(mIpModeSelector.getValue())) {
            config.setIpAssignment(IpAssignment.DHCP); // DHCP 自动获取
        } else {
            // 2. 构建静态 IP 配置(校验 IP 格式)
            try {
                StaticIpConfiguration staticConfig = new StaticIpConfiguration.Builder()
                    .setIpAddress(new LinkAddress(
                        InetAddress.parseNumericAddress(mStaticIp.getText()), 24 // 子网前缀 24(255.255.255.0)
                    ))
                    .setGateway(InetAddress.parseNumericAddress(
                        findPreference("ethernet_static_gateway").getText()
                    ))
                    .setDnsServers(List.of(
                        InetAddress.parseNumericAddress(
                            findPreference("ethernet_static_dns1").getText()
                        )
                    ))
                    .build();
                config.setIpAssignment(IpAssignment.STATIC);
                config.setStaticIpConfiguration(staticConfig);
            } catch (Exception e) {
                Toast.makeText(getContext(), "静态 IP 配置无效(如格式错误)!", Toast.LENGTH_SHORT).show();
                return;
            }
        }

        // 3. 保存配置到 USB 网卡接口(如 eth_usb)
        try {
            mEthManager.setConfiguration(mSelectedInterface, config);
            Toast.makeText(getContext(), "配置保存成功!", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            Log.e(TAG, "保存配置失败:" + e.getMessage());
            Toast.makeText(getContext(), "保存失败,请检查权限!", Toast.LENGTH_SHORT).show();
        }
    }

    // 页面 resume 时刷新接口列表(兼容热插拔)
    @Override
    public void onResume() {
        super.onResume();
        refreshInterfaceList();
        updateIpConfig();
    }
}

六、Step 4:权限与 SELinux 适配(避免访问被拒)

USB 网卡操作需系统权限与 SELinux 规则支持,否则会出现 "权限不足" 或 "访问被拒" 错误。

6.1 配置 Settings 权限

文件路径:packages/apps/Settings/AndroidManifest.xml

添加网络配置与 USB 访问权限:

xml

复制代码
<manifest ...>
    <!-- 网络配置权限 -->
    <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- USB 设备访问权限 -->
    <uses-permission android:name="android.permission.USB_PERMISSION" />
    <uses-feature android:name="android.hardware.usb.host" required="false" />
</manifest>

6.2 添加 SELinux 规则(允许以太网服务访问 USB 网卡)

SELinux 开启 enforcing 模式时,需允许 ethernet_service 访问 USB 网卡设备节点,否则会出现 avc: denied 错误。

文件路径:device/<厂商>/<设备>/sepolicy/private/ethernet.te(如 device/rockchip/rk3568/sepolicy/private/ethernet.te

添加规则:

te

复制代码
# 允许 ethernet_service 访问 USB 网卡的网络设备(类型为 netdev_usb)
allow ethernet_service netdev_usb:netdev { create open read write ioctl getattr setattr add_name remove_name rename };
# 允许 ethernet_service 访问 USB 设备节点(如 /dev/bus/usb)
allow ethernet_service usb_device:chr_file { read write open ioctl };

编译并刷入 SELinux 规则:

bash

复制代码
# 重新编译 SELinux 策略
make sepolicy -j8
# 刷入设备(或随系统镜像一起刷入)
adb push out/target/product/rk3568/obj/ETC/sepolicy_intermediates/sepolicy /sys/fs/selinux/policy

七、Step 5:全流程测试验证

7.1 功能验证

  1. 接口识别:插入 USB 网卡,进入 "设置→网络与互联网→以太网",下拉框应显示 "USB 网卡(eth_usb)"。

  2. DHCP 测试 :选择 "USB 网卡"+"DHCP 模式",保存后执行 adb shell ifconfig eth_usb,确认自动获取 IP(如 192.168.1.105)。

  3. 静态 IP 测试 :配置静态 IP(如 192.168.1.100)、网关(192.168.1.1)、DNS(8.8.8.8),执行 adb shell ip addr show eth_usb 确认配置生效。

  4. 优先级测试 :同时连接 USB 网卡、WiFi、4G,执行 adb shell dumpsys connectivity,确认默认网络为以太网(score=100);断开 USB 网卡,默认网络切换为 WiFi;断开 WiFi,切换为 4G。

7.2 常见问题与解决方案

问题现象 原因分析 解决方案
USB 网卡不识别,无 eth_usb 1. 内核驱动未加载;2. Udev 规则无效。 1. 检查 `dmesg grep usb确认驱动加载;<br>2. 重新执行adb shell udevadm control --reload-rules` 刷新 Udev 规则。
静态 IP 保存失败 1. 权限不足;2. IP 格式错误。 1. 确认 Settings 已声明 NETWORK_SETTINGS 权限;2. 检查 IP 是否符合格式(如 255.255.255.0)。
SELinux 访问被拒(avc: denied) SELinux 规则未添加 USB 网卡访问权限。 1. 执行 `adb logcat grep "avc: denied"查看缺失权限;<br>2. 在ethernet.te` 中添加对应规则并重新编译。
优先级不生效,WiFi 仍优先 以太网评分未设置或低于 WiFi 评分。 1. 确认 getBestNetworkScore() 已设置 setScore(100);2. 检查 WiFi 评分(通常在 WifiNetworkFactory 中,确保低于 100)。

八、总结

本文基于 Android 13 AOSP 源码,从底层到上层完整实现 USB 网卡支持,核心流程可概括为:

  1. 内核驱动:启用 USB 网卡驱动,确保硬件被识别;
  2. 接口固定 :通过 Udev 规则将 USB 网卡命名为 eth_usb,避免随机变化;
  3. 框架识别 :修改 EthernetTracker 扩展接口匹配正则,让系统跟踪 eth_usb
  4. 优先级提升 :修改 EthernetNetworkFactorygetBestNetworkScore(),设置高分(100)确保 USB 网卡优先;
  5. 可视化配置:在 Settings 中添加接口选择与 IP 配置界面,支持用户操作;
  6. 权限适配:配置系统权限与 SELinux 规则,避免访问被拒。

该方案适用于嵌入式 Android 设备、工业控制、智能车机等场景,可根据实际硬件(如不同 USB 网卡芯片)和系统定制需求灵活调整。

相关推荐
梦终剧2 小时前
【Android之路】界面和状态交互
android·交互
小李飞刀李寻欢2 小时前
kauditd0 病毒/挖矿程序完全清除方法初试
网络·安全·病毒·挖矿
荣光波比3 小时前
Docker(二)—— Docker核心功能全解析:网络、资源控制、数据卷与镜像构建实战
运维·网络·docker·容器·云计算
孙小二3 小时前
Android主题切换
android
帅锅锅0074 小时前
Android.mk 编辑脚本
android
火柴就是我5 小时前
Android 记录View绘制坐标抖动问题
android
余衫马5 小时前
Ubuntu24.04 安卓模拟器安装指南
android·容器·模拟器
诺诺Okami5 小时前
Android Framework-WMS-动画-初步认识
android
用户2018792831675 小时前
Try-Catch-Finally中的Return执行顺序:一个外卖配送的故事
android