一、需求背景与核心目标
在嵌入式 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 网卡/内置以太网接口选择 -->
<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 功能验证
-
接口识别:插入 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 网卡芯片)和系统定制需求灵活调整。