Android 13 完整实现 USB 网卡支持与网络优先级配置(USB>WiFi>4G)
一、需求背景与核心目标
在嵌入式 Android 设备(如工业平板、智能车机、机顶盒)开发中,常需通过 USB 转网卡扩展稳定的有线网络,但 Android 原生系统存在两大问题:
- 对 USB 网卡的识别与配置支持有限,默认不显示 USB 网卡接口;
- 网络优先级默认是 WiFi > 以太网 > 4G,无法满足 “USB 网卡优先” 的场景(如工业场景需稳定有线网络)。
本文基于 Android 13 AOSP 源码,从 内核驱动→框架层识别→Settings 可视化配置→网络优先级强制调整 全流程落地,最终实现:
- USB 网卡自动识别、热插拔兼容与 DHCP / 静态 IP 配置;
- 网络优先级严格遵循 USB 网卡(以太网)> WiFi > 4G;
- 配置持久化与系统权限适配(含 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()
是生成以太网评分的核心方法,仅在以下场景被调用:
- USB 网卡链路接通时:链路从
down
变为up
,调用registerNetworkOffer
注册网络服务,传入评分; - 网络能力更新时:修改 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 网卡/内置以太网接口选择 --><ListPreferenceandroid: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/静态) --><ListPreferenceandroid: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 配置项(动态显示/隐藏,默认隐藏) --><EditTextPreferenceandroid:key="ethernet_static_ip"android:title="@string/ethernet_static_ip_title"android:hint="192.168.1.100" <!-- 默认提示 -->android:inputType="textUri" <!-- 限制 IP 格式输入 -->android:visible="false" /><EditTextPreferenceandroid:key="ethernet_static_gateway"android:title="@string/ethernet_static_gateway_title"android:hint="192.168.1.1"android:inputType="textUri"android:visible="false" /><EditTextPreferenceandroid:key="ethernet_static_netmask"android:title="@string/ethernet_static_netmask_title"android:hint="255.255.255.0"android:inputType="textUri"android:visible="false" /><EditTextPreferenceandroid: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)@Overridepublic 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 时刷新接口列表(兼容热插拔)@Overridepublic 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 功能验证
接口识别:插入 USB 网卡,进入 “设置→网络与互联网→以太网”,下拉框应显示 “USB 网卡(eth_usb)”。
DHCP 测试:选择 “USB 网卡”+“DHCP 模式”,保存后执行
adb shell ifconfig eth_usb
,确认自动获取 IP(如192.168.1.105
)。静态 IP 测试:配置静态 IP(如
192.168.1.100
)、网关(192.168.1.1
)、DNS(8.8.8.8
),执行adb shell ip addr show eth_usb
确认配置生效。优先级测试:同时连接 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 网卡支持,核心流程可概括为:
- 内核驱动:启用 USB 网卡驱动,确保硬件被识别;
- 接口固定:通过 Udev 规则将 USB 网卡命名为
eth_usb
,避免随机变化; - 框架识别:修改
EthernetTracker
扩展接口匹配正则,让系统跟踪eth_usb
; - 优先级提升:修改
EthernetNetworkFactory
的getBestNetworkScore()
,设置高分(100)确保 USB 网卡优先; - 可视化配置:在 Settings 中添加接口选择与 IP 配置界面,支持用户操作;
- 权限适配:配置系统权限与 SELinux 规则,避免访问被拒。
该方案适用于嵌入式 Android 设备、工业控制、智能车机等场景,可根据实际硬件(如不同 USB 网卡芯片)和系统定制需求灵活调整。