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 网卡芯片)和系统定制需求灵活调整。

相关推荐
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android