web-to-app Hosts 广告拦截深度解析:过滤列表订阅、美化过滤与 APK 内编译规则
【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app
本篇围绕 web-to-app 的 Hosts Ad Blocking 功能展开:它会讲清这个基于 hosts 规则 + 美化(cosmetic)过滤的拦截器如何管理 20 个社区过滤列表、如何解析与匹配 ABP/hosts 规则、如何按页面注入隐藏 CSS 与反反拦截脚本,以及同一套编译规则如何同时服务应用内预览与导出 APK。读完之后,你既能掌握界面层面的订阅管理操作,也能从源码级别理解规则从下载、解析、索引到注入的完整链路。
功能定位与入口
Hosts Ad Blocking 是一个独立的过滤规则管理中心,入口在应用主界面的⋮ → Hosts Ad Blocking(对应文档 主界面 More)。它的职责有两层:
- 规则库管理:下载、导入、启用/禁用、删除过滤列表(预设 20 个 + 用户自定义源),并提供规则搜索与一键清空。
- 拦截引擎供给:宿主级阻断(hosts 规则)、网络级过滤(ABP 语法)、元素级隐藏(cosmetic 过滤),分别服务于应用内 WebView 预览和导出 APK 的运行期。
界面实现在 HostsAdBlockScreen.kt,引擎核心是单例 AdBlocker.kt(约 2800 行),磁盘缓存由 AdBlockFilterCache.kt 承担。
界面功能全览
与界面逐项对应,各功能在源码中的落点如下:
| 界面功能 | 说明 | 源码依据 |
|---|---|---|
| 活跃规则数 | 顶部展示当前加载的阻断规则总量(getHostsFileRuleCount/getImportedHostsRuleCount) | HostsAdBlockScreen.kt#L119-L127 |
| Sources 管理 | 预设列表逐个下载;支持从文件导入(importHostsFromFile,系统文件选择器)、从 URL 导入(importHostsFromUrl)、删除源 | 同屏文件内filePickerLauncher/importSource回调 |
| 自定义源卡片 | 文件导入 / 自定义 URL 的源会获得显示名(优先取 ContentResolver 文件名或列表头! Title:),渲染为独立卡片,支持启用/禁用/删除 | getCustomHostsSources()(AdBlocker.kt#L1507-L1519) |
| 内置列表 | 20 个社区过滤列表(见下节) | getPopularHostsSources()(AdBlocker.kt#L219-L324) |
| Search | 按名称/描述/URL 过滤预设与自定义源;另有 All / Downloaded / Not downloaded 三档过滤器 | HostsFilter枚举与filteredSources(HostsAdBlockScreen.kt#L86-L190) |
| Clear | 清空全部已加载规则,带确认对话框(WtaAlertDialog) | 屏内showClearDialog状态 |
下载过程不是“点了就等”:importHostsFromUrl以 8KB 缓冲分块读取,进度回调被节流到每秒最多 6 次并始终发出最后一次,界面用LinearProgressIndicator展示字节数、速度与 ETA(AdBlocker.kt#L2200-L2302、DownloadProgress)。
内置列表源:20 个社区过滤列表
getPopularHostsSources()硬编码了 20 个预设源,覆盖 EasyList、uBlock Origin、AdGuard、AdAway 四大体系及 8 个语言/地区专属列表:
| 名称 | 说明 |
|---|---|
| EasyList | 国际主广告过滤列表(AdBlock 社区维护) |
| EasyPrivacy | 追踪器与分析阻断 |
| uBlock Filters | uBlock Origin 默认过滤,面向现代广告技术 |
| uBlock Privacy | uBlock Origin 隐私专用过滤 |
| AdGuard Base | 广覆盖的国际广告过滤 |
| AdGuard Tracking Protection | 分析与遥测阻断 |
| AdGuard Annoyances | Cookie 提示、订阅弹窗等打扰 |
| StevenBlack Hosts | 经典 hosts 文件源 |
| AdAway Default | AdAway 默认 hosts 列表 |
| Peter Lowe's List | 广告/追踪服务器黑名单 |
| 1Hosts Lite | 轻量 hosts 列表 |
| EasyList Germany | 德/奥/瑞域名 |
| Liste FR (France) | 法国域名 |
| EasyList Dutch | 荷/比域名 |
| EasyList Italy | 意大利域名 |
| EasyList Spanish | 西语/拉美域名 |
| EasyList Portuguese | 葡/巴域名 |
| AdGuard Japanese | 日语站点与日本广告网络 |
| EasyList China | 中文站点 |
| AdGuard DNS Filter | DNS 层面的过滤规则 |
每个源携带name/url/description三元组(HostsSource),部分描述通过 Strings.kt 做多语言化——getPopularHostsSources()因此可动态取文案,而旧的POPULAR_HOSTS_SOURCES常量已被标记@Deprecated。
规则导入:hosts 与 ABP 双格式自动识别
导入入口是parseFilterContent()(AdBlocker.kt#L2304-L2343)。它先扫描前 20 行判断内容属于 ABP 格式(出现[Adblock...]、!、||、@@、##、#@#等特征)还是传统 hosts 格式:
- hosts 格式走
parseHostLine():兼容0.0.0.0 example.com、127.0.0.1 example.com、::1、||example.com^以及裸域名行;随后经isValidHost()过滤 IP 地址、localhost系主机名、超长或含..的非法域名。 - ABP 格式逐行交给
parseAndAddRule(),按分隔符优先级(#@$#、#$#、#@?#、#?#、#@#、##)分派到四类解析器:- 网络规则(
||host/path$script,third-party、@@例外)→parseNetworkFilter() - 选择器隐藏(
##.ad-banner、##div[id^="taboola-"])→parseCosmeticFilter() - AdGuard CSS 注入(
domain#$#selector { ... })→parseCssInjection() - 脚本规则(
##+js(...)、#%#//scriptlet(...))→parseScriptletRule(),注意它们必须优先于选择器解析处理,否则会把+js(...)负载当成非法 CSS 污染隐藏批次。
- 网络规则(
解析过程中还有一个工程细节:捕获OutOfMemoryError后触发 GC 并保留已解析的部分规则集(低内存设备上导入超大列表的保底策略),源码注释明确提到 AdGuard Base 单文件约 6.4MB。
ABP 模式到正则的翻译与惰性编译
translateAbpPattern()(AdBlocker.kt#L2572-L2608)完成 ABP 通配到 Java 正则源串的翻译:^先转为字符类[^\\w%.\\-](必须先于锚点转换,否则会破坏||host/path规则中插入的[^/]类),再处理||(子域前缀匹配)、|首/尾锚点、*通配,/regex/原样保留。MAX_ABP_PATTERN_LEN = 1024上限与连续星号检测用于防御恶意/畸形规则。
更关键的是两段式编译:导入时只做便宜的字符串翻译,昂贵的Regex编译延迟到规则首次命中时执行(NetworkFilter.regex())。源码注释给出了理由:一个 10 万条规则的列表若在导入期全部编译会驻留约 100MB 的 Pattern 对象,足以让 256MB 堆的设备 OOM——而导出 APK 时还会构建第二台引擎,压力加倍。匹配结果另有 2048 条上限的 LRU 缓存(blockResultCache),缓存键包含 URL、页面主机、资源类型与三态标记,保证例外规则(@@...$domain=...)跨页面结果正确。
匹配判定链路
shouldBlock()(AdBlocker.kt#L872-L916)的判定顺序:
- 命中
SAFELIST_HOSTS(翻译、CDN、登录、支付、reCAPTCHA 等关键域白名单)直接放行; - DOCUMENT 与 FONT 永不拦截,first-party 样式表也不拦截;
- 命中
ESSENTIAL_RESOURCE_REGEX(jquery/react/api/login/checkout 等路径特征)放行; - 主机精确命中
exactHosts/hostsFileHosts后,再查例外规则决定是否放行; - 依次查网络例外 → 网络阻断(带 anchor 域倒排索引加速,
anchorDomainIndex+ 未锚定剩余索引); - 兜底启发式:first-party 请求若路径命中
FIRST_PARTY_AD_PATH_REGEX(/ads/、/doubleclick/、/ad-slot/等),按 script/xhr/subdocument 类型阻断。
资源类型可由调用方传入,也可从请求头推断(inferResourceTypeFromRequest()依据Accept头与扩展名),这使拦截器既能服务shouldBlock(url, pageHost, resourceType, isThirdParty)也能直接消费WebResourceRequest。
美化过滤(Cosmetic Filtering)
除 hosts 级阻断外,引擎内置一套 MutationObserver 驱动的元素隐藏机制(buildCosmeticRules())。每个页面的隐藏规则被按50 个选择器一批切分生成 CSS(display:none等六属性全!important),批次以getCosmeticHideBatches()暴露给页面内 DOM 观察脚本按批查询——这样单个非法选择器只会让它所在批次失效,不会连坐整条逗号拼接链(对应源码注释中的 issue #823 防护)。
CosmeticFilter数据结构(AdBlocker.kt#L86-L102)支持四种形态:
- 普通选择器:
##.ad-banner、###ad_top等,引擎还内置一批兜底选择器(ins.adsbygoogle、[id^="div-gpt-ad"]、[class*="taboola"]等); :style(...)重载(uBO 语法):只重排不隐藏,作为独立 CSS 规则输出,避免混入隐藏批次把“只想改颜色”的元素藏掉;#$#完整 CSS 注入(AdGuard 语法):作为整条规则直接拼接;- 过程式伪类
:has-text()/:upward()/:remove():编译为t:text、u:arg、r操作码序列,由页面侧 JS 逐条求值(getCosmeticProceduralRulesJs());嵌套伪类或无法识别的 uBO 专属伪类(:xpath、:watch-attr等 21 种)在解析期直接丢弃,防止非法 CSS 污染批次。
例外规则(#@#)按页面主机域匹配后从隐藏集合中按选择器精确抵消。所有 per-host 产物(CSS、反反拦截脚本、隐藏批次、过程式 JS)经cosmeticCache(128 条上限 + 版本号失效)按主机记忆化,规则变更统一走invalidateCache()/bumpCosmeticVersion()使缓存失效。
反反拦截(Anti-Adblock)脚本
buildAntiAdblockScript()(AdBlocker.kt#L1139-L1166)把页面命中的 scriptlet 与一段通用反检测脚本打包注入,通用部分(UNIVERSAL_ANTI_ADBLOCK_SCRIPT,AdBlocker.kt#L2627 起)覆盖五种常见检测手法:创建隐形诱饵广告元素、置位window.fuckAdBlock/google_ad_status等检测变量、用 MutationObserver 拆除 fixed/sticky 拦截墙并恢复被锁的滚动、代理getComputedStyle让诱饵元素“看起来可见”、拦截指向检测信标 URL 的 fetch/XHR 并伪造 200 响应。
scriptlet 支持abort-on-property-read/write、abort-current-inline-script、set-constant、remove-attr、remove-class、nano-setInterval/Timeout-booster、prevent-addEventListener、prevent-fetch、prevent-xhr等十余种,每种都内联生成独立 JS(generateScriptlet())。
订阅生命周期:注册表、缓存与自动刷新
过滤源的状态落盘在filesDir下的两份文件:
adblock_hosts_sources.txt(registry v3):每行URL \t 规则数 \t 启用标志 \t 显示名 \t 上次刷新时间戳 \t 刷新间隔(saveSourcesRegistry()),v2/v3 新增列均为可选,向前兼容;adblock_hosts.txt与编译态缓存(AdBlockFilterCache.saveCompiledState):重启后loadHostsRules()优先从编译态恢复,避免重新解析全部列表(AdBlocker.kt#L2411-L2433);仅配置 UI 需要的轻量元数据路径则走hydrateSourcesMetadata(),不加载完整过滤库,防止多张大列表耗尽堆内存。
自动刷新策略(AdBlocker.kt#L128-L145):
- 刷新间隔取自列表头的
! Expires: N days|hours,并被夹在 6 小时至 14 天之间,防止恶意或拼错的头造成热循环;无头则默认 24 小时; kickSourceRefresh()在后台 IO 协程中刷新所有到期源(本应用订阅 + Hosts Blocking 已启用源的并集),失败源进入 1 小时内存退避,失败时保留旧内容(fail-soft);- 内容变化后在引擎互斥锁内
rebuildEngine(),重建“与上次prepareRuntimeFilters完全相同的规则宇宙”,因此刷新不会与并发准备竞争; - 下载参数:15s 连接超时、30s 读取超时、
User-Agent: WebToApp/1.0,内容同时写入 24 小时 URL 缓存(AdBlockFilterCache)。
预览与导出:同一规则集的两种消费方式
文档 Privacy & Hardening 将广告拦截列为主题能力之一,按应用的开关与订阅在 Ad Blocking 配置卡(Edit Common Config → Privacy)中选定——选择器同时提供内置列表与用户导入的自定义源。
预览侧:WebViewActivity在页面导航前调用prepareRuntimeFilters(enabled, customRules, subscriptionUrls)(WebViewActivity.kt#L1504-L1561)装载规则;WebViewManager.kt 在请求拦截钩子中对每个子请求执行adBlocker.shouldBlock(url, pageHost, resType, isThirdParty)(L2012),命中后按资源类型返回伪造响应(脚本返回/* blocked */、图片返回 1x1 透明 GIF、XHR 返回{});页面注入阶段则拼接getCosmeticFilterCss(pageHost)(L2361)与getAntiAdblockScript(pageHost)(L5278-L5302)。
导出侧:构建 APK 时,config.adBlock.enabled为真则新建一台独立的AdBlocker实例,按该应用的订阅列表执行compileRulesText()(ApkBuilder.kt#L1576-L1594),把全部规则编译回 ABP 文本(||host^+ 原始 raw 规则行)后压缩写入assets/wta_adblock_compiled.txt,随包分发给导出应用的运行期引擎。这也解释了惰性编译注释中“导出会构建第二台引擎”的压力来源。
测试覆盖
该功能的单元测试集中在 app/src/test/java/com/webtoapp/core/adblock/:
- AdBlockerTest.kt — 规则解析与匹配主逻辑;
- AdBlockLazyRegexTest.kt — 验证正则延迟编译行为;
- AdBlockSubscriptionRefreshTest.kt — 订阅刷新间隔、退避与重建;
- AdBlockerCustomSourcesTest.kt — 自定义源显示名与卡片元数据;
- AdBlockerHostRuntimeTest.kt — 运行期主机规则装载。
小结
Hosts Ad Blocking 的设计可以用一句话概括:界面是订阅管理器,AdBlocker是规则编译器,WebView 注入与 APK 导出只是规则集的两个出口。它通过 hosts/ABP 双格式自动识别、惰性正则编译、anchor 域倒排索引与 LRU 结果缓存把百万级请求的判定成本压到可接受区间;又用注册表版本化、! Expires:夹取、失败退避保证订阅在低内存 Android 设备上的长期可用性。若你为自己的 web 应用开启广告拦截,只需在 Privacy → Ad Blocking 中勾选所需订阅,预览即时生效,导出时规则即被编译进 APK,无需额外配置。
【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考