发布于 14 天前  40 次阅读



广告拦截模块开发避坑指南

环境背景


一、Kotlin 协程兼容性问题

现象

编译报错:Suspension functions can be called only within coroutine body

原因

Kotlin 编译器版本较低,对 AlertDialog 按钮回调的 lambda 类型识别不准确。即使在 lifecycleScope.launch 内部,某些嵌套 lambda 也不会被自动识别为挂起上下文。

解决方案

  1. 用显式接口替代 lambda:

// ❌ 可能报错 .setPositiveButton("确定") { _, _ -> withContext(Dispatchers.IO) { ... } // 挂起函数调用 } // ✅ 稳妥写法 val listener = android.content.DialogInterface.OnClickListener { _, _ -> lifecycleScope.launch { withContext(Dispatchers.IO) { ... } } } .setPositiveButton("确定", listener)

  1. 用 Handler 替代 withContext(Dispatchers.Main):
    在非挂起上下文中需要更新 UI 时,用 Handler(Looper.getMainLooper()).post { … } 代替 withContext(Dispatchers.Main)。
  2. 将挂起操作封装成普通函数:

// ✅ 普通函数,内部启动协程 private fun doAsyncWork() { lifecycleScope.launch { withContext(Dispatchers.IO) { ... } } }


二、# 号过滤规则导致的 CSS 规则丢失

现象

· CSS 隐藏规则(##.ad-banner)无法通过快速添加、导入、订阅更新写入
· 在规则列表里看不到已添加的 CSS 规则

原因

多处代码使用 !it.startsWith("#") 过滤注释行,但 CSS 规则以 ## 开头,被误判为注释直接丢弃。

解决方案

将所有 !it.startsWith("#") 改为允许 ## 开头的判断:// ❌ 错误 .filter { it.isNotEmpty() && !it.startsWith("#") } // ✅ 正确 .filter { line -> line.isNotEmpty() && (!line.startsWith("#") || line.startsWith("##")) }

需要修改的文件

· AdBlocker.kt — loadRulesFromFile、loadCssRulesFromFile
· AdBlockUpdateWorker.kt — 规则过滤
· AdBlockActivity.kt — showBatchAddBottomSheet、importRulesFromContent


三、动态注入 JS/CSS 影响自身页面

现象

· 首页滑动失效
· 元素探测工具用不了

原因

广告拦截的静态 CSS 和动态 JS 对所有页面生效,包括自己的本地首页(file:///android_asset/…)。

解决方案

在注入前加 URL 判断,只对在线网页生效:if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) { // 注入 CSS/JS }


四、URL 匹配失败导致删除/同步异常

现象

· 取消收藏后状态不刷新
· 云端同步后本地数据不一致

原因

URL 末尾斜杠差异(https://xxx.com vs https://xxx.com/)、编码差异等导致字符串精确匹配失败。

解决方案

所有 URL 比较统一使用标准化处理:fun normalizeUrl(url: String) = url.trimEnd('/').lowercase() // 比较时 list.removeAll { normalizeUrl(it.url) == normalizeUrl(item.url) }

需要修改的文件

· FavoriteManager.kt — add、remove、isFavorite
· FavoriteRepository.kt — remove
· FavoriteDialogHelper.kt — existingFavorite 的查找


五、云端数据截断问题

现象

· 备份后恢复报错:Unterminated array
· 规则数量多时同步失败

原因

  1. 旧方案用 API 传 JSON,受 PHP/WordPress 表单大小限制
  2. JSON 中特殊字符未转义导致数据损坏

解决方案

改为逐条存储(每条规则一条数据库记录),避免大 JSON 传输:// 服务端:逐条存储 foreach ($rules as $rule) { add_user_meta($user_id, 'meow_adblock_rule', $rule); }


六、检测失效链接误判

现象

很多能正常访问的网站被判定为失效。

原因

只用 HEAD 请求检测,部分网站拒绝 HEAD 请求,直接判为失效。

解决方案

双重验证:HEAD 失败后再用 GET 请求(只请求 1 字节)确认一次:// 1. HEAD 请求 val headOk = try { ... } catch { false } if (headOk) return false // HEAD 成功,肯定没失效 // 2. GET 请求确认 val getOk = try { request.header("Range", "bytes=0-0").get() ... } catch { false } // 两次都失败才算失效 return !getOk


  1. 功能隔离:新增拦截功能时,先用 URL 判断限制生效范围,避免影响自身页面
  2. 编译测试:每改完一个文件就编译一次,不要攒到最后
  3. 兼容性优先:在 AndroidIDE 环境下,尽量用显式接口、Handler、普通函数,避免依赖 Kotlin 高级语法
  4. URL 处理:统一使用标准化函数处理 URL 比较,避免大小写、斜杠、编码差异
  5. 数据存储:大数据量用逐条存储代替大 JSON,避免截断

以后遇到类似问题,对照这篇文章快速定位即可。