1、采用4G模块实现上网,本质上就是把4G模块虚拟成网卡,然后让内核识别网卡,然后进行上网,这期间需要ril库实现android上层与模块的通信,但是我这边因为部分原因,实现不了,只能退而求其次,作为有线网卡,设备名称识别为eth1,则自动上网了,所有4G部分的逻辑全部舍弃掉,但是现有有一个问题,我本身自带一个eth0,系统网卡,一个wifi,然后是我的4G模块,正常逻辑的优先级逻辑是有线网卡> wifi > 4G网卡,现在因为4G网卡作为有线网卡挂载到系统,这个逻辑打破了,所以需要更改android的系统源码实现该逻辑,
1、找到有线网卡的系统评分文件
packages\modules\Connectivity\service-t\src\com\android\server\ethernet\EthernetNetworkFactory.java
cpp
private static NetworkScore getBestNetworkScore() {
int score = 60; // 未知网口默认评分
if ("eth0".equals(name)) {
// 原生网口eth0:满分100,保持最高优先级
score = 100;
} else if ("eth1".equals(name)) {
// 4G模块网口eth1:低分50,降低优先级(可根据需求调整,如40/60)
score = 30;
}
return new NetworkScore.Builder().setLegacyScore(score).build();
}
默认4G模块的网络评分是30,有线网卡的评分是100,这时候就只需要该WiFi的评分了
wifi的评分文件在次
packages\modules\Wifi\service\java\com\android\server\wifi\WifiScoreCard.java和
packages\modules\Wifi\service\java\com\android\server\wifi\ClientModeImpl .java和
现在有两个问题,第一个wifi的评分和公网有关,是什么
cpp
/**
* Updates the score card after IP configuration
*
* @param wifiInfo object holding relevant values
*/
public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) {
IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo);
updatePerNetwork(Event.IP_CONFIGURATION_SUCCESS, wifiInfo.getSSID(), wifiInfo.getRssi(),
wifiInfo.getTxLinkSpeedMbps(), UNKNOWN_REASON, ifaceInfo);
PerNetwork perNetwork = lookupNetwork(wifiInfo.getSSID());
perNetwork.initBandwidthFilter(wifiInfo);
ifaceInfo.attemptingSwitch = false;
doWrites();
}
cpp
/**
* Estimates the probability of getting internet access, based on the
* device experience.
*
* @return a probability, expressed as a percentage in the range 0 to 100
*/
public int estimatePercentInternetAvailability() {
// Initialize counts accoring to Laplace's rule of succession
int trials = 2;
int successes = 1;
// Aggregate over all of the frequencies
for (PerSignal s : mSignalForEventAndFrequency.values()) {
switch (s.event) {
case IP_CONFIGURATION_SUCCESS:
if (s.elapsedMs != null) {
trials += s.elapsedMs.count;
}
break;
case VALIDATION_SUCCESS:
if (s.elapsedMs != null) {
successes += s.elapsedMs.count;
}
break;
default:
break;
}
}
// Note that because of roaming it is possible to count successes
// without corresponding trials.
return Math.min(Math.max(Math.round(successes * 100.0f / trials), 0), 100);
}
主要是这两个函数,下面是ai分析
你提供的是两段 Android WiFi 相关的 Java 代码,核心是WiFi 网络 IP 配置完成后的计分卡更新 ,以及基于设备使用经验估算 WiFi 网络的互联网可用概率,整体属于 WiFi 网络质量 / 可用性评估的核心逻辑,大概率来自 Android 系统的 WiFi 评分 / 网络诊断相关模块。
下面为你逐段解析代码的功能、核心逻辑、关键变量和设计思路,同时补充相关背景让你更好理解:
一、noteIpConfiguration 方法解析
核心功能
WiFi 设备完成IP 配置(获取到 IP 地址、网关、DNS 等网络参数)后,更新网络评分卡(score card)的核心方法,会同步更新 BSSID、网络(SSID)维度的统计信息,并重置接口切换状态、持久化数据。
逐行 / 关键逻辑解析
java
运行
public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) {
// 1. 根据WiFi接口名,获取对应的接口信息(包含接口状态、切换标记等)
IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
// 2. 按BSSID维度更新事件:记录IP配置成功的事件
updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo);
// 3. 按网络(SSID)维度更新事件:传入IP配置成功事件+WiFi核心参数
updatePerNetwork(Event.IP_CONFIGURATION_SUCCESS, wifiInfo.getSSID(), wifiInfo.getRssi(),
wifiInfo.getTxLinkSpeedMbps(), UNKNOWN_REASON, ifaceInfo);
// 4. 根据SSID查找该网络的持久化统计对象,初始化带宽过滤器(用于后续带宽统计)
PerNetwork perNetwork = lookupNetwork(wifiInfo.getSSID());
perNetwork.initBandwidthFilter(wifiInfo);
// 5. 重置接口的「正在切换」标记:IP配置完成,说明接口切换(如有)已结束
ifaceInfo.attemptingSwitch = false;
// 6. 执行数据持久化:将本次更新的评分卡数据写入存储(避免进程重启丢失)
doWrites();
}
关键参数 / 类说明
ExtendedWifiInfo:系统扩展的 WiFi 信息类,继承自原生WifiInfo,新增了接口名、BSSID、自定义统计参数等;IfaceInfo:WiFi网络接口信息类 ,记录接口名、是否正在切换(attemptingSwitch)、接口状态等;Event.IP_CONFIGURATION_SUCCESS:枚举事件,代表「IP 配置成功」,是网络可用性评估的重要前置事件;- 核心更新维度:
PerBssid:按BSSID(WiFi 热点的物理地址,一个 SSID 可能对应多个 BSSID,如双频路由器)统计;PerNetwork:按SSID(WiFi 名称)统计,是用户感知的「网络维度」;
- 传入的 WiFi 参数:
Rssi:信号强度(负值,越接近 0 信号越好);TxLinkSpeedMbps:传输链路速度(Mbps);UNKNOWN_REASON:默认原因常量,代表本次事件无明确触发原因。
二、estimatePercentInternetAvailability 方法解析
核心功能
基于设备对当前 WiFi 网络的历史使用经验 ,估算互联网访问的可用概率 ,返回0-100 的百分比(0 = 完全不可用,100 = 百分百可用),是 WiFi 网络质量评估的核心指标(比如系统选择 WiFi / 蜂窝网络、判断 WiFi 是否可用时会用到)。
设计核心:拉普拉斯继起法则(Laplace's rule of succession)
方法开头初始化的trials=2、successes=1是拉普拉斯平滑 的经典应用,解决小样本 / 无样本时的概率估算偏差问题:
- 若网络无任何历史数据,默认概率为
1/2 = 50%,避免出现0/0的除零错误,也符合「未知网络默认有一定可用性」的实际场景; - 若有历史数据,平滑值的影响会随样本量增加而逐渐降低,最终概率趋近于真实的
成功数/总尝试数。
逐行 / 关键逻辑解析
java
运行
public int estimatePercentInternetAvailability() {
// 1. 拉普拉斯平滑初始化:避免无样本时除零,默认赋予基础尝试数和成功数
int trials = 2; // 总尝试数(分母)
int successes = 1;// 成功数(分子)
// 2. 遍历所有「事件+频率」对应的信号统计对象,聚合统计数据
for (PerSignal s : mSignalForEventAndFrequency.values()) {
switch (s.event) {
// 2.1 匹配「IP配置成功」事件:计入「总尝试数」(IP配置是联网的前提)
case IP_CONFIGURATION_SUCCESS:
if (s.elapsedMs != null) { // 非空校验:确保有统计计数
trials += s.elapsedMs.count; // 累加IP配置成功的次数
}
break;
// 2.2 匹配「验证成功」事件:计入「成功数」(验证成功=实际能访问互联网)
case VALIDATION_SUCCESS:
if (s.elapsedMs != null) {
successes += s.elapsedMs.count; // 累加互联网验证成功的次数
}
break;
default:
break;
}
}
// 3. 注释说明:因漫游(roaming)可能出现「成功数>尝试数」(漫游后直接验证成功,无新IP配置)
// 4. 计算概率并做边界限制:确保结果在0-100之间,四舍五入为整数
return Math.min(Math.max(Math.round(successes * 100.0f / trials), 0), 100);
}
关键参数 / 类说明
mSignalForEventAndFrequency:存储 **「事件 + 频率」与信号统计对象 ** 的映射集合,key 是「事件类型 + WiFi 频率(2.4G/5G/6G)」,value 是PerSignal;PerSignal:WiFi 信号的统计类,记录对应事件的计数(count)、耗时(elapsedMs)等核心统计数据;s.elapsedMs.count:elapsedMs是耗时统计对象,其count属性代表该事件发生的总次数;- 核心事件映射:
IP_CONFIGURATION_SUCCESS:联网尝试的前提 → 计入总尝试数trials;VALIDATION_SUCCESS:互联网验证成功 (系统会通过 ping / 访问校验地址等方式验证)→ 计入成功数successes;
- 边界处理:
Math.max(...,0)避免负数,Math.min(...,100)避免超过 100,符合「百分比」的定义。
概率计算示例
- 无历史数据 :
trials=2,successes=1→1*100/2 = 50%; - 有历史数据 :假设 IP 配置成功 10 次,验证成功 8 次 →
trials=2+10=12,successes=1+8=9→9*100/12=75%; - 漫游导致成功数 > 尝试数 :假设 IP 配置成功 2 次,验证成功 5 次 →
trials=4,successes=6→6*100/4=150%→ 被Math.min限制为100%。
三、两段代码的关联关系
这两段代码是同一 WiFi 评分卡模块的上下游逻辑,关联紧密:
noteIpConfiguration是数据采集层 :IP 配置成功后,向评分卡中写入IP_CONFIGURATION_SUCCESS事件的计数,为后续概率计算提供「总尝试数」的数据源;estimatePercentInternetAvailability是数据计算层 :从评分卡中读取IP_CONFIGURATION_SUCCESS(总尝试数)和VALIDATION_SUCCESS(成功数)的统计数据,结合拉普拉斯平滑计算互联网可用概率;- 补充:
VALIDATION_SUCCESS事件的写入逻辑未在代码中展示,其触发时机是IP 配置成功后,系统完成互联网访问验证(如 ping 通网关、访问 Google / 国内校验地址)。
四、核心设计亮点 & 业务背景
五、注意事项
-
多维度统计:按 BSSID/SSID/ 接口 / 频率分层统计,兼顾 WiFi 的物理特性(BSSID / 频率)和用户感知(SSID);
-
拉普拉斯平滑:解决小样本概率估算的经典方案,符合工程实践;
-
数据持久化 :
doWrites()保证统计数据不丢失,让概率估算基于长期使用经验; -
边界安全:概率计算做 0-100 的限制,避免异常数据导致结果失效;
-
漫游兼容 :代码注释明确考虑了 WiFi 漫游的场景,兼容漫游时的统计偏差。
要让内网通过
ClientModeImpl的网络验证,核心是绕过外网检测逻辑,直接标记网络为「已验证有效」,以下是具体实现方案(基于代码逻辑推导):一、核心原理
Android 网络验证的关键判断是
NetworkAgent.VALIDATION_STATUS_VALID(验证有效),内网验证失败的本质是系统检测不到外网连通性,因此只需强制触发「验证成功」的回调流程,跳过外网检测即可。二、具体实现方案(3 种优先级,从简单到彻底)
方案 1:直接调用验证成功核心代码(最便捷)
在
L3ConnectedState状态的enter()方法中,添加验证成功的触发逻辑,内网连接后自动标记为已验证:java
运行
class L3ConnectedState extends State { @Override public void enter() { // 原有代码不变... registerConnected(); mTargetWifiConfiguration = null; mWifiScoreReport.reset(); mLastSignalLevel = -1; mIsAutoRoaming = false; mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID; mWifiLastResortWatchdog.connectedStateTransition(true); mWifiStateTracker.updateState(mInterfaceName, WifiStateTracker.CONNECTED); mWifiLockManager.updateWifiClientConnected(mClientModeManager, true); WifiConfiguration config = getConnectedWifiConfigurationInternal(); mWifiScoreReport.startConnectedNetworkScorer( mNetworkAgent.getNetwork().getNetId(), isRecentlySelectedByTheUser(config)); updateLinkLayerStatsRssiAndScoreReport(); mWifiScoreCard.noteIpConfiguration(mWifiInfo); mWifiMetrics.noteFirstL3ConnectionAfterBoot(true); // 新增:内网验证强制通过(核心代码) if (config != null) { // 1. 标记网络已验证可访问互联网(持久化到配置) mWifiConfigManager.setNetworkValidatedInternetAccess(config.networkId, true); // 2. 启用自动连接 mWifiConfigManager.updateNetworkSelectionStatus( config.networkId, WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE); // 3. 记录验证成功(更新评分卡,避免后续重复验证) mWifiScoreCard.noteValidationSuccess(mWifiInfo); // 4. 从黑名单移除(若之前被标记为无效) mWifiBlocklistMonitor.handleNetworkValidationSuccess(mLastBssid, mWifiInfo.getSSID()); // 5. 通知监控器验证成功 mCmiMonitor.onInternetValidated(mClientModeManager); Log.d(getTag(), "内网强制通过网络验证"); } } }方案 2:修改验证状态回调(更彻底)
在
WifiNetworkAgentCallback的onValidationStatus方法中,强制将状态改为VALID,无视系统检测结果:java
运行
private class WifiNetworkAgentCallback implements WifiNetworkAgent.Callback { @Override public void onValidationStatus(int status, @Nullable Uri redirectUri) { if (!isThisCallbackActive()) return; if (status == mLastNetworkStatus) return; // 新增:内网场景强制设为验证有效 boolean isIntranet = true; // 可根据内网IP段(如192.168.x.x、10.x.x.x)判断,这里简化为true if (isIntranet) { status = NetworkAgent.VALIDATION_STATUS_VALID; // 强制标记为有效 redirectUri = null; // 清除门户网络标记 } mLastNetworkStatus = status; if (status == NetworkAgent.VALIDATION_STATUS_NOT_VALID) { unwantedNetwork(NETWORK_STATUS_UNWANTED_VALIDATION_FAILED); } else if (status == NetworkAgent.VALIDATION_STATUS_VALID) { mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK); doNetworkStatus(status); // 触发验证成功流程 } // 原有代码不变... } }方案 3:禁用网络验证检测(适合完全不需要验证的场景)
直接注释 / 删除验证失败的处理逻辑,让系统无法触发「验证失败」的禁用流程:
-
找到
CMD_UNWANTED_NETWORK分支中「验证失败」的处理代码,注释如下: -
java
运行
case CMD_UNWANTED_NETWORK: { if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) { // 原有逻辑不变... } else if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN || message.arg1 == NETWORK_STATUS_UNWANTED_VALIDATION_FAILED) { Log.d(getTag(), "忽略验证失败处理(内网模式)"); // 注释掉以下验证失败的核心逻辑,避免网络被禁用 /* WifiConfiguration config = getConnectedWifiConfigurationInternal(); if (config != null) { if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN) { mWifiConfigManager.setNetworkValidatedInternetAccess(config.networkId, false); // ... 原有禁用逻辑 ... } else { mWifiConfigManager.incrementNetworkNoInternetAccessReports(config.networkId); // ... 原有临时禁用逻辑 ... } } */ } break; }三、关键补充:内网 IP 段判断(可选,让逻辑更严谨)
可在方案中添加内网 IP 判断,避免影响外网连接,示例代码:
java
运行
// 判断当前IP是否为内网 private boolean isIntranetIp() { InetAddress inetAddress = mWifiInfo.getInetAddress(); if (inetAddress == null) return false; String ip = inetAddress.getHostAddress(); // 内网IP段:10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 return ip.startsWith("10.") || ip.startsWith("172.") && Integer.parseInt(ip.split("\\.")[1]) >=16 && Integer.parseInt(ip.split("\\.")[1]) <=31 || ip.startsWith("192.168."); }将方案 1/2 中的
isIntranet = true替换为isIntranet = isIntranetIp()即可。四、验证生效标志
修改后,内网连接成功后满足以下条件,说明验证通过:
-
mWifiConfigManager.getNetworkValidatedInternetAccess(config.networkId)返回true; -
网络不会被标记为
DISABLED_NO_INTERNET_TEMPORARY(临时禁用); -
状态栏 WiFi 图标正常显示(无「无互联网」提示)。
-
仅修改
ClientModeImpl即可,无需改动其他模块(如ConnectivityService); -
方案 1/2 不会影响外网连接(外网仍会走正常验证流程),方案 3 会完全禁用验证失败处理,仅适合纯内网场景;
-
若内网有门户认证(Captive Portal),需额外清除
mTermsAndConditionsUrl(方案 1 已包含)。
onValidationStatus 方法解析(内网适配核心)
该方法是 WifiNetworkAgentCallback 的核心回调,负责接收 NetworkAgent 上报的网络验证状态(有效 / 无效 / 门户网络),是内网绕过验证的关键改造点。
一、方法核心逻辑
| 验证状态 | 触发动作 | 内网适配关键 |
|---|---|---|
VALIDATION_STATUS_NOT_VALID(验证无效) |
调用 unwantedNetwork 标记网络废弃,触发禁用 / 黑名单逻辑 |
需阻止该分支执行,避免内网被误判为无效 |
VALIDATION_STATUS_VALID(验证有效) |
记录埋点、调用 doNetworkStatus 执行成功逻辑(启用自动连接、清除黑名单) |
内网需强制走该分支,直接标记验证通过 |
检测到门户网络(redirectUri 非空) |
标记门户状态,不触发成功 / 失败逻辑 | 内网无门户时可忽略,有内网门户需额外处理 |
二、内网适配改造方案(直接可用)
在原方法基础上添加 内网 IP 判断,强制让内网走验证成功逻辑,改造后代码如下:
java
运行
public void onValidationStatus(int status, @Nullable Uri redirectUri) {
if (!isThisCallbackActive()) return;
if (status == mLastNetworkStatus) return;
// 新增:内网IP判断(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)
boolean isIntranet = isCurrentIpIntranet();
if (isIntranet) {
status = NetworkAgent.VALIDATION_STATUS_VALID; // 强制标记为验证有效
redirectUri = null; // 清除门户网络标记(避免误判)
}
mLastNetworkStatus = status;
if (status == NetworkAgent.VALIDATION_STATUS_NOT_VALID) {
if (mVerboseLoggingEnabled) {
logd("WifiNetworkAgent -> Wifi networkStatus invalid, score="
+ mWifiInfo.getScore());
}
unwantedNetwork(NETWORK_STATUS_UNWANTED_VALIDATION_FAILED);
} else if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
if (mVerboseLoggingEnabled) {
logd("WifiNetworkAgent -> Wifi networkStatus valid, score= "
+ mWifiInfo.getScore());
}
mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK);
doNetworkStatus(status); // 核心:执行验证成功逻辑
}
boolean captivePortalDetected = redirectUri != null
&& redirectUri.toString() != null
&& redirectUri.toString().length() > 0;
if (captivePortalDetected) {
Log.i(getTag(), "Captive Portal detected, status=" + status
+ ", redirectUri=" + redirectUri);
mWifiConfigManager.noteCaptivePortalDetected(mWifiInfo.getNetworkId());
mCmiMonitor.onCaptivePortalDetected(mClientModeManager);
mCurrentConnectionDetectedCaptivePortal = true;
}
}
// 新增:判断当前IP是否为内网(辅助方法)
private boolean isCurrentIpIntranet() {
InetAddress inetAddress = mWifiInfo.getInetAddress();
if (inetAddress == null) return false;
String ip = inetAddress.getHostAddress();
// 内网IP段匹配
return ip.startsWith("10.")
|| (ip.startsWith("172.") && Integer.parseInt(ip.split("\\.")[1]) >= 16 && Integer.parseInt(ip.split("\\.")[1]) <= 31)
|| ip.startsWith("192.168.");
}
三、改造关键说明
强制验证有效 :通过 isCurrentIpIntranet() 判断内网 IP 后,直接将 status 设为 VALIDATION_STATUS_VALID,跳过无效逻辑; 清除门户标记 :内网无门户时,置空 redirectUri 避免触发门户网络处理; 不影响外网:外网 IP 会走原逻辑,仅内网触发强制通过,兼容性拉满;
四、验证生效标志
改造后内网连接时,该方法会输出日志 WifiNetworkAgent -> Wifi networkStatus valid,且:
mWifiConfigManager.getNetworkValidatedInternetAccess(config.networkId)返回true;- 网络不会被标记为
DISABLED_NO_INTERNET_TEMPORARY; - 状态栏 WiFi 无「无互联网」提示,内网可正常使用。
- 自动联动成功逻辑 :
doNetworkStatus会调用后续验证成功流程(启用自动连接、清除黑名单、持久化验证状态),无需额外修改。
- 自动联动成功逻辑 :