WIFI网络连接受限问题/RRO(Runtime Resource Overlay)修改 Captive Portals 的完整指南
1. Captive Portal 机制简介
Captive Portal 是 Android 用于检测网络是否需要认证的机制。设备连接 Wi-Fi 后,会访问以下默认 URL:
- HTTP: http://connectivitycheck.gstatic.com/generate_204
- HTTPS: https://www.google.com/generate_204
若返回 HTTP 204,则认为网络畅通;否则会弹出认证页面。但在国内,Android原生系统中这些域名可能无法访问,导致 Wi-Fi 图标显示感叹号(实际网络可用)。
- HTTP 204:网络畅通
- HTTP 302/200:触发认证页面
2. Log分析
- WIFI网络连接受限问题,利用Wireshark抓包或者查看Mobilelog
ag "NetworkMonitor"
15056:04-11 08:32:36.499468 1444 9227 D NetworkMonitor/102: PROBE_DNS connectivitycheck.gstatic.com 352ms OK 203.208.49.130
15061:04-11 08:32:36.518254 1444 9226 D NetworkMonitor/102: PROBE_DNS www.google.com 372ms OK 31.13.106.4
15121:04-11 08:32:36.951950 1444 9227 D NetworkMonitor/102: PROBE_HTTP http://connectivitycheck.gstatic.com/generate_204 time=446ms ret=204 request={Connection=[close], User-Agent=[Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.32 Safari/537.36]} headers={null=[HTTP/1.1 204 No Content], Connection=[close], Content-Length=[0], Cross-Origin-Resource-Policy=[cross-origin], Date=[Fri, 11 Apr 2025 12:32:36 GMT], X-Android-Received-Millis=[1744374756950], X-Android-Response-Source=[NETWORK 204], X-Android-Selected-Protocol=[http/1.1], X-Android-Sent-Millis=[1744374756703]}
15238:04-11 08:32:46.544874 1444 9226 D NetworkMonitor/102: PROBE_HTTPS https://www.google.com/generate_204 Probe failed with exception java.net.SocketTimeoutException: failed to connect to www.google.com/31.13.106.4 (port 443) from /172.20.10.4 (port 54094) after 10000ms
15456:04-11 08:32:59.177387 1444 9225 D NetworkMonitor/102: PROBE_FALLBACK http://www.google.com/gen_204 Probe failed with exception java.net.SocketTimeoutException: failed to connect to www.google.com/2001::1 (port 80) from /240e:47e:30f0:e052:c163:d155:32b1:92ee (port 44626) after 10000ms
15457:04-11 08:32:59.182609 1444 9225 D NetworkMonitor/102: isCaptivePortal: isSuccessful()=false isPortal()=false RedirectUrl=null isPartialConnectivity()=true Time=23051ms
15668:04-11 08:33:00.231170 1444 9292 D NetworkMonitor/102: PROBE_DNS www.google.com 30ms OK 31.13.106.4,2001::1
15673:04-11 08:33:00.245307 1444 9293 D NetworkMonitor/102: PROBE_DNS connectivitycheck.gstatic.com 41ms OK 2401:3800:4001:14::1002,203.208.49.130
15710:04-11 08:33:00.607278 1444 9293 D NetworkMonitor/102: PROBE_HTTP http://connectivitycheck.gstatic.com/generate_204 time=357ms ret=204 request={Connection=[close], User-Agent=[Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.32 Safari/537.36]} headers={null=[HTTP/1.1 204 No Content], Connection=[close], Content-Length=[0], Cross-Origin-Resource-Policy=[cross-origin], Date=[Fri, 11 Apr 2025 12:33:00 GMT], X-Android-Received-Millis=[1744374780606], X-Android-Response-Source=[NETWORK 204], X-Android-Selected-Protocol=[http/1.1], X-Android-Sent-Millis=[1744374780474]}
可以看到访问的都是外网地址,这也就解释通了为什么连接国内网络会出现网络受限的问题。
解决方案:
- 修改全局设置(captive_portal_http_url/captive_portal_https_url):添加国内地址用于检测
- 关闭captive_portal_mode:禁用网络检测功能检测
- RRO重写:利用Overlay机制,修改系统暴露的Overlay接口,直接替换NetworkStack的资源(健壮性强)
3. 修改 Captive Portal 检测(临时方案)
方法 1:在默认地址上新添(ADB 命令)
源码分析:
m11_a11/frameworks/base/services/core/java/com/android/server/ConnectivityService.java
private static final String DEFAULT_CAPTIVE_PORTAL_HTTP_URL ="http://connectivitycheck.gstatic.com/generate_204";@Overridepublic String getCaptivePortalServerUrl() {enforceNetworkStackOrSettingsPermission();String settingUrl = mContext.getResources().getString(R.string.config_networkCaptivePortalServerUrl);if (!TextUtils.isEmpty(settingUrl)) {return settingUrl;}settingUrl = Settings.Global.getString(mContext.getContentResolver(),Settings.Global.CAPTIVE_PORTAL_HTTP_URL);if (!TextUtils.isEmpty(settingUrl)) {return settingUrl;}return DEFAULT_CAPTIVE_PORTAL_HTTP_URL;}
这段代码重写了 getCaptivePortalServerUrl 方法,功能是获取 Captive Portal 服务器 URL。有三级优先级获取逻辑,先检查权限,接着依次从应用资源文件( res/values/config.xml 中覆盖默认值)、系统全局设置(captive_portal_http_url/captive_portal_https_url)中获取 URL,若前两者获取的 URL 不为空则返回,若都未获取到有效 URL 则返回默认 URL。
了解之后,根据获取URL的优先级,我们可以对此优先级来进行更改代码。
但是在Android11,NetworkStack被集成进了Google Mainline
意味着我们不能更改上述代码的逻辑,但是我可以参考此逻辑来在不修改此代码的前提下,修改Captive Portals URLs
无需 root,直接覆盖系统设置:
# 替换为小米的检测地址(国内可访问)
adb shell settings put global captive_portal_http_url http://connect.rom.miui.com/generate_204
adb shell settings put global captive_portal_https_url https://connect.rom.miui.com/generate_204# 可选:禁用检测(不推荐)
adb shell settings put global captive_portal_mode 0
生效条件:重启网络(如切换飞行模式)。
方法 2:设置captive_portal_mode
源码分析:
package\modules\NetworkStack\src\com\android\server\connectivity\NetworkMonitor.java
private boolean getIsCaptivePortalCheckEnabled() {
1776 String symbol = CAPTIVE_PORTAL_MODE;
1777 int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT;
1778 int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
1779 return mode != CAPTIVE_PORTAL_MODE_IGNORE;
1780 }private String getSettingFromResource(@NonNull final Context context,
2141 @StringRes int configResource, @NonNull String symbol, @NonNull String defaultValue) {
2142 final Resources res = context.getResources();
2143 String setting = res.getString(configResource);
2144
2145 if (!TextUtils.isEmpty(setting)) return setting;
2146
2147 setting = mDependencies.getSetting(context, symbol, null);
2148
2149 if (!TextUtils.isEmpty(setting)) return setting;
2150
2151 return defaultValue;
2152 }
实际起作用的就是一个Settings属性,默认就是校验的情况。我们可以禁用检测(企业不推荐,个人使用的情况下无关紧要),就不会报网络限制的问题
mode的取值如下:
- 0: 彻底禁用检测(Don’t attempt to detect captive portals.)
- 1: 检测到需要登录则弹窗提醒(默认值)(When detecting a captive portal, display a notification that prompts the user to sign in.)
- 2:检测到需要登录则自动断开此热点并不再自动连接(When detecting a captive portal, immediately disconnect from the network and do not reconnect to that network in the future.)
连接设备,执行如下命令
adb shell settings put global captive_portal_mode 0
如果想在源码修改则可以跳转到
frameworks\base\packages\SettingsProvider\src\com\android\providers\settings\DatabaseHelper.java
DatabaseHelper.java是 Android 系统中 设置属性(System/Secure/Global Settings)的数据库管理类,属于核心系统服务的一部分。它的主要职责是创建和维护存储设备全局配置的"数据库"
loadBooleanSetting(stmt, Settings.Global.CAPTIVE_PORTAL_MODE, 0);
//上一调修改同理
loadStringSetting(stmt, Settings.Global.captive_portal_http_url "http://connect.rom.miui.com/generate_204")
loadStringSetting(stmt, Settings.Global.captive_portal_https_url "https://connect.rom.miui.com/generate_204")
4. 源码修改
源码位置:
下面列出了因重构而移至网络堆栈(NetworkStack)模块的路径。
- 强制门户检测和登录 - 在 frameworks/base/ 中:
- core/java/android/net/captiveportal/
- services/core/java/com/android/server/connectivity/NetworkMonitor.java
- packages/CaptivePortalLogin/
移动的代码的新位置是在 packages/modules/NetworkStack、packages/modules/CaptivePortalLogin 以及其他一些共享位置。上述路径是指在为 Mainline 将文件移至这些新位置之前的文件位置。packages/modules/NetworkStack 和 packages/modules/CaptivePortalLogin 中的文件是 Mainline 模块的一部分,不能修改。
这意味着不能通过修改源码,来进行网络门户检测网址的修改,所以我们要通过RRO来进行修改
从以下链接查看,可以发现Google在Android10之后把网络堆栈(NetworkStack)集成进了Mainline
https://source.android.com/docs/core/ota/modular-system/networking?hl=zh-cn
https://source.android.com/docs/core/ota/modular-system?hl=zh-cn
5. 利用RRO机制(RRO 覆盖)
RRO(运行时资源叠加层)是 Android 提供的一种动态替换应用资源的机制,可用于修改 Captive Portal 检测的默认行为(如替换 Google 的检测服务器为国内可用地址)。
Android5.0引入了RRO,主要依靠一个叫做overlay apk的应用实现的, overlay apk和普通的应用相比最大的区别就是overlay apk不含任何代码(java或者C++),它的一般结构仅包含一个AndroidManifest.xml和res目录,当然因为需要在源码下编译,还包含一个Android.mk或者Android.bp(也可以用AS编译,前提是AS编译出来的apk需要有平台签名)。
叠加层资源(原始APP软件包)
叠加层的工作原理是将叠加层软件包中定义的资源映射到目标软件包中定义的资源。当应用尝试解析目标软件包中资源的值时,系统转而会返回目标资源映射到的叠加层资源的值
定义资源映射
在 Android 11 或更高版本中,用于定义叠加层资源映射的推荐机制是,在叠加层软件包的 res/xml 目录中创建一个文件,枚举应覆盖的目标资源及其替换值,然后将 overlay 清单标记的 android:resourcesMap 属性的值设置为对资源映射文件的引用。
以下代码显示了NetworkStack res/values/overlays.xml 文件。
1 <?xml version="1.0" encoding="utf-8" ?>16 <resources xmlns:android="http://schemas.android.com/apk/res/android">
17 <overlayable name="NetworkStackConfig">
18 <policy type="product|system|vendor">
19 <!-- Configuration values for NetworkMonitor -->
20 <item type="integer" name="config_captive_portal_dns_probe_timeout"/>
...
40 <item type="string" name="config_network_validation_failed_content_regexp"/>
41 <item type="string" name="config_network_validation_success_content_regexp"/>
42 <item type="array" name="config_captive_portal_http_urls"/>
43 <item type="array" name="config_captive_portal_https_urls"/>
44 <item type="array" name="config_captive_portal_fallback_urls"/>
45 <item type="bool" name="config_no_sim_card_uses_neighbor_mcc"/>
...
80 </policy>
81 </overlayable>
82 </resources>
83
1. 定义可覆盖项
通过 overlayable 标签声明 NetworkStackConfig 模块中允许被修改的配置参数,包括:
- Captive Portal(网络连通性检测)相关配置
- DHCP/DNS 参数
- 邻居探测(NUD)设置
- 带宽检测规则
2. 指定覆盖策略
policy type=“product|system|vendor” 表示这些配置仅能在下面这些分区安装才会生效:
- 产品分区 (product)
- 系统分区 (system)
- 厂商分区 (vendor)
叠加层优先级
使用多个叠加层替换同一资源时,必须按适当叠加层顺序进行替换。对叠加层而言,配置越低,优先级越高。叠加层在不同分区的优先级顺序(从最低优先级到最高优先级)如下。
- system
- vendor
- odm
- oem
- product
- system_ext
3. 可被替换的资源
被item包含的均为Overlay可替换资源
设置清单(用于覆盖资源的APP)
如果某个软件包包含overlay标记作为manifest>标记的子项,该软件包将被视为 RRO 软件包。
必要android:targetPackage属性的值用于指明 RRO 想要叠加的软件包的名称。
可选android:targetName属性的值用于指明 RRO 想要叠加的目标软件包的可叠加资源子集的名称。如果目标未定义可叠加资源集,就不应包含此属性。
以下代码展示了一个示例 AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.overlay"><application android:hasCode="false" /><overlay android:targetPackage="com.example.target"android:targetName="OverlayableResources"/>
</manifest>
由于无法叠加代码,因此叠加层无法使用 DEX 文件。此外,必须将清单中 application 标记的 android:hasCode 属性设置为 false。
当应用定义 overlayable 标记时,定位到该应用的叠加层需满足以下条件:
必须指定 targetName。
只能叠加 overlayable 标记中列出的资源。
只能定位到一个 overlayable 名称。
如果某个叠加层定位到的软件包提供可叠加资源,但不使用 android:targetName 定位特定的 overlayable 标记,您无法启用该叠加层。
使用 OverlayConfig
下面是NetworkStack用于Overlay的示例:
/res/config.xml
<?xml version="1.0" encoding="utf-8"?>
<resources><string-array name="config_captive_portal_https_urls" translatable="false"><item>https://connectivitycheck.gstatic.com/generate_204</item><item>https://connect.rom.miui.com/generate_204</item></string-array><bool name="config_no_sim_card_uses_neighbor_mcc">true</bool>
</resources>
这里设置了SIM 卡缺失时的网络策略会使用检测到的 MCC(移动国家代码),自动加载对应国家/地区的网络配,并且HTTPS Captive Portal 检测地址列表首选 Google 官方检测地址(国际通用)备选小米服务器地址(国内可用)
使用清单属性/静态 RRO
在 Android 10 或更低版本中,使用以下清单属性配置叠加层的不可变性和优先级。
/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.google.android.networkstack.overlay_DL"><overlayandroid:priority="1"android:targetPackage="com.google.android.networkstack"android:targetName="NetworkStackConfig"android:isStatic="true"/><applicationandroid:label="GoogleNetworkStackResOverlay"android:hasCode="false"></application>
</manifest>
- 这里设置目标对象
- targetPackage: com.google.android.networkstack (Google 网络堆栈模块)
- targetName: NetworkStackConfig (覆盖 overlayable 名称为 NetworkStackConfig 的资源
- 覆盖特性
- isStatic=“true”: 当此布尔值属性的值设置为 true 时,叠加层会默认处于启用状态并且不可变,这会导致叠加层无法停用。
- priority=“1”: 当多个静态叠加层以相同的资源值为替换目标时,此数字属性的值(仅影响静态叠加层)将配置叠加层的优先级。数值越大表示优先级越高。
编译APK文件
/Android.bp
//RRO for GoogleNetworkStack.apk
android_app {name: "GoogleNetworkStackResOverlay_DL",proprietary: true,owner: "mtk",certificate: "platform",manifest: "AndroidManifest_Google.xml",sdk_version: "system_current",
}
来到原码根目录
source build/envsetup.sh
lunch
m GoogleNetworkStackResOverlay_DL
安装APK
接下来就是安装了,安装分2种情况:
- 如果没有指定android:isStatic=“true”,你是可以直接adb install安装的,但是默认overlay是不会生效。可以通过下面这个命令来验证:adb shell cmd overlay list 查看是否有你的overlay的应用。然后你可以通过命令来启用:adb shell cmd overlay enable com.sample.app.overlay。
- 如果你指定了android:isStatic=“true”, 你通过 adb install 时会报错:Overlay com.ecarx.eas.daemon.overlay is static but not pre-installed。所以你需要将你的overlay的Apk push 到/system/vendor/overlay目录下重启。
- 关于如何push到system、vendor这些只读文件中,详情可以参考:
如何将 APK 内置为系统应用(适用于编辑设置属性)
验证测试
第 1 步:列出 RRO
如需列出 RRO,请执行以下操作:
- 运行以下命令:
adb shell cmd overlay list --user current
系统会显示类似如下文本的输出:
com.android.systemui
[ ] com.android.theme.icon_pack.rounded.systemui
[ ] com.android.theme.icon_pack.filled.systemui
[ ] com.android.theme.icon_pack.circular.systemui
com.android.providers.settings
--- com.mediatek.SettingsProviderResOverlaycom.google.android.networkstack
[x] com.google.android.networkstack.overlay
[x] com.google.android.networkstack.overlay_DLcom.android.networkstack.tethering
--- com.android.networkstack.tethering.overlay
- 验证您的 RRO 是否显示在列表中。以下指标可指明 RRO 状态:
指标 | RRO 状态 |
---|---|
[ ] | 已安装,待激活。 |
[X] | 已安装,已激活。 |
— | 已安装,但包含错误。 |
如果相应 RRO 未在叠加目标的软件包名称下方列出,则表明该 RRO 尚未安装。如果有包含错误,仔细检查你的包名和target:name是否正确
第 2 步:启用和停用 RRO
如果已安装 RRO,请执行以下操作:
- 使用以下命令启用(或停用)RRO:
adb shell cmd overlay [enable/disable] --user current [your RRO package name]https://blog.csdn.net/qq_41140324/article/details/135976646
第 3 步:确认是否已安装 RRO
如需确认设备上是否已安装某个 RRO,或要找出该 RRO 未启用的原因,请执行以下操作:
- 运行以下命令:
adb shell cmd overlay dump [your RRO package name]
会显示如下输出:
com.google.android.networkstack.tethering.overlay:0 {mPackageName...........: com.google.android.networkstack.tethering.overlaymUserId................: 0mTargetPackageName.....: com.google.android.networkstack.tetheringmTargetOverlayableName.: TetheringConfigmBaseCodePath..........: /vendor/app/GoogleTetheringResOverlay/GoogleTetheringResOverlay.apkmState.................: STATE_ENABLEDmIsEnabled.............: truemIsMutable.............: falsemPriority..............: 19mCategory..............: null
}
mIsEnabled
- 布尔值,
true
表示该覆盖层已被启用,与mState
字段的信息相呼应。
mPriority
- 覆盖层的优先级,值为
19
。当存在多个覆盖层作用于同一个目标资源时,系统会根据优先级来决定使用哪个覆盖层的资源,优先级越高越优先被采用。
当你的 mState mIsEnabled 都显示true和STATE_ENABLED,并且你的RRO 状态是[X]那就代表你的叠加层覆盖成功了,下面是一些排错的步骤,也是我踩过的吭。
排错: 如需查看 mState 的值,请执行以下操作:
STATE_ENABLED 和 STATE_ENABLED_IMMUTABLE 。RRO 已启用,并已应用于您的目标。
-
STATE_MISSING_TARGET 。您的目标尚未安装。
-
STATE_NO_IDMAP 。 AndroidManifest.xml 、 overlays.xml 或 overlayable.xml 文件的设置方式存在问题。您可以使用 adb logcat 运行日志,并搜索关键字“ idmap ”,找出错误所在。请参阅第 4 步和第 5 步第 6 步。
-
STATE_UNKNOWN。OverlayManagerService 出现了问题。
第 4 步(排错):检查 AndroidManifest.xml
为验证 AndroidManifest.xml,请执行以下操作:
- 检查 targetName 和 targetPackage。
-
android:targetName 的值应与目标应用中定义的可叠加组的值相同。只有在定位叠加层时,才需满足此要求。
-
android:targetPackage 始终是必需项,并且应包含目标应用的软件包名称。
-
检查您的 RRO 是否处于静态。默认情况下,系统在启动时会启用静态 RRO。默认情况下,系统在启动时不会启用动态 RRO。在运行时更改应用资源的值中提供了启用动态 RRO 的其他方法。
-
检查静态 RRO 的优先级(动态 RRO 的优先级始终设为 Integer.MAX_VALUE,并且它们的应用顺序取决于它们的启用时间)。
- 可以为同一目标应用多个 RRO。RRO 的优先级越高,应用顺序越靠后。优先级范围为 0 到 10,其中 10 代表最高,0 代表最低。
第 5 步(排错):检查 overlays.xml
- 请检查 overlays.xml,确认该文件中是否已定义您要叠加的所有资源。例如,请考虑以下 overlays.xml:
<overlay><item target="string/app_name" value="@string/overlaid_app_name" />
</overlay>
- 您必须确保:
- 目标应用中存在名为 app_name 的 string 资源。
- 您的 RRO 中存在名为 overlaid_app_name 的 string 资源。
- 如果您的目标包含 overlayable.xml 文件,请确保该文件中包含 app_name。请务必在 AndroidManifest.xml 文件中使用正确的 targetName(第 4 步)。
注意:如果您叠加布局文件,请确保 overlays.xml 和 overlayable.xml 中均包含所有 ID 和应用命名空间属性。
例如:
<overlay>
<item target="layout/car_ui_base_layout_toolbar" value="@layout/car_ui_base_layout_toolbar" />
<item target="id/car_ui_toolbar_background" value="@id/car_ui_toolbar_background" />
<item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf" />
</overlay>
第 6 步(排错):确保应用安装在约定目录
上面我们的原始APP里的policy如下:
<policy type="product|system|vendor">
使用policy标记可对可叠加资源施加限制。type属性指定叠加层必须满足哪些政策的要求才能替换包含的资源。受支持的类型如下。
- public : 任何叠加层均可替换相应资源。
- system : 系统分区上的任何叠加层均可替换相应资源。
- vendor : vendor 分区上的任何叠加层均可替换相应资源。
- product : product 分区上的任何叠加层均可替换相应资源。
- oem。oem 分区上的任何叠加层均可替换相应资源。
- odm。odm 分区上的任何叠加层均可替换相应资源。
- signature : 使用与目标 APK 相同的签名进行签名的任何叠加层均可替换相应资源。
- actor。使用与 actor APK 相同的签名进行签名的任何叠加层均可替换相应资源。actor 在系统配置中的 named-actor 标记中声明。
- config_signature。使用与 overlay-config APK 相同的签名进行签名的任何叠加层均可替换相应资源。overlay-config 在系统配置中的 overlay-config-signature 标记中声明。
说明覆盖app必须安装在system,vendor或者product分区上,而我们通过adb install xxx.apk 是安装在data目录下的,所以不符合要求。
现在是把apk放到/system/app目录下,主要解决下权限的问题,这个默认只有read权限。关于如何push到system、vendor这些只读文件中,详情可以参考:
如何将 APK 内置为系统应用(适用于编辑设置属性)
参考文献
https://blog.csdn.net/wenzhi20102321/article/details/141533064
https://source.android.com/docs/automotive/hmi/car_ui/appendix?hl=zh-cn
https://source.android.com/docs/core/runtime/rros?hl=zh-cn
https://source.android.com/docs/core/runtime/rro-troubleshoot?hl=zh-cn
https://source.android.com/docs/automotive/hmi/car_ui/customize?hl=zh-cn
https://juejin.cn/post/7319181157502205979
https://blog.csdn.net/qq_34211365/article/details/119678722
https://blog.csdn.net/zhangjin501/article/details/131702199
https://blog.csdn.net/qq_40731414/article/details/131054277
https://blog.csdn.net/qq_41140324/article/details/135976646