news 2026/10/22 14:05:05

SKIP 应用白名单:让指定应用完全免疫自动点击的功能指南与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SKIP 应用白名单:让指定应用完全免疫自动点击的功能指南与源码解析
  • 移动开发
  • 原生移动
  • GUI 自动化

【免费下载链接】SKIP

自动跳过APP开屏广告

项目地址:https://gitcode.com/gh_mirrors/ski/SKIP
点击查看免费下载

本文围绕 SKIP 的「应用白名单」功能展开:它用于列出设备上所有已安装的应用,并允许用户把不想被 SKIP 干预的应用加入白名单。读完本文,你不仅能掌握白名单的操作方式(含「显示系统应用」开关),还能理解它从 DataStore 持久化到无障碍服务点击拦截的完整实现链路。

功能概述:白名单解决什么问题

SKIP 的核心职责是通过无障碍服务自动点击目标控件来跳过应用开屏广告。但自动点击并不总是安全的——对某些应用(例如支付类、系统类或需要人工交互的应用),SKIP 的自动点击可能造成干扰。应用白名单功能就是为此设计的隔离开关,其行为规则为:

  • 白名单页面会列出所有已经安装的应用;
  • 页面右上角菜单中提供「显示系统应用」选项,由于系统应用数量较多,该选项默认不勾选;
  • 当把一个应用加入白名单(即把它的开关打开)后,SKIP 就不会对该应用执行任何「点击」动作,无论该应用是否在配置文件中定义了跳过规则;
  • 因此白名单可以用来防止 SKIP 对某些应用的干扰。

也就是说,白名单的优先级高于配置规则:即使配置文件中为某个应用写好了skipIds/skipTexts规则,只要该应用的包名出现在白名单中,SKIP 的自动点击逻辑就会整体跳过它。

功能入口与页面结构

白名单入口位于主界面,MainActivity.kt 中的WhiteListButton通过startActivity跳转到WhiteListActivity。

WhiteListActivity.kt 是一个基于 Jetpack Compose 的页面,整体结构分为两部分:

  1. 内容区(Column):上方是搜索栏SearchTextField,下方是应用列表AppListColumn;
  2. 右上角菜单:包含「功能介绍」和「显示系统应用」两个菜单项。

对应地,该页面注入了 5 个 ViewModel,各自承担独立职责:

ViewModel职责源码位置
AppListViewModel应用列表的分页加载与刷新AppListViewModel.kt
ShowSystemViewModel「显示系统应用」勾选状态ShowSystemViewModel.kt
WhiteListViewModel白名单开关的变更与持久化WhiteListViewModel.kt
SwitchThemeViewModel全局主题切换位于ui/settings/theme目录
SearchViewModel搜索关键字状态SearchViewModel.kt

在onCreate中,Activity 建立了两处状态联动(见 WhiteListActivity.kt):

  • showSystemViewModel.isShowSystem变化时,调用appListViewModel.reloadData(it, searchViewModel.getQuery())重新加载列表;
  • searchViewModel.query变化时,调用appListViewModel.reloadData(showSystemViewModel.getIsShowSystem(), it)按新关键字刷新。

因此无论用户勾选「显示系统应用」还是输入搜索词,列表都会以两者组合作为条件整体重建。菜单中的「显示系统应用」由 ShowSystemButton.kt 渲染为一个Checkbox,其默认值在ShowSystemViewModel中初始化为false——这与文档中「因为系统应用较多,所以默认是不列出的」的描述完全一致。

此外,菜单里的「功能介绍」项会打开WebViewActivity加载官方文档页面,字符串常量定义在 strings.xml:whitelist(应用白名单)、whitelist_show_system(显示系统应用)、whitelist_dot(存储键前缀WHITELIST_DOT)等。

应用列表:系统应用过滤、搜索与分页

列表数据的获取实现在 AppListRepository.kt,其getData(currentPage, pageSize, isShowSystem, query)方法分三步处理:

第一步:读取全部已安装应用

private val appInfos: MutableList<ApplicationInfo> by lazy { MyApp.context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) }

应用信息只通过PackageManager枚举一次并缓存在内存中(by lazy),后续过滤、搜索、分页都在这份缓存上进行。

第二步:按「是否显示系统应用」过滤

val apps = if (isShowSystem) { appInfos } else { appInfos.filter { (it.flags and ApplicationInfo.FLAG_SYSTEM) == 0 } }

从源码结构看,这里判断的是ApplicationInfo.FLAG_SYSTEM标志位:默认(未勾选「显示系统应用」)时,只要应用带有系统应用标志就被过滤掉;勾选后则展示完整列表。

第三步:按关键字过滤并分页截取

val filteredApps = if (query.isNotEmpty()) { apps.filter { it.loadLabel(pkgManager).toString().contains(query, ignoreCase = true) } } else { apps } val fromIndex = currentPage * pageSize val toIndex = minOf(fromIndex + pageSize, filteredApps.size) return filteredApps.subList(fromIndex, toIndex) .map { AppListItem( it.loadLabel(pkgManager).toString(), // 应用名 it.packageName, // 包名(白名单的判定依据) it.loadIcon(pkgManager), // 图标 whiteListRepository.isAppInWhiteList(it.packageName) // 白名单状态 ) }

搜索是按应用显示名(loadLabel)做忽略大小写的子串匹配,且每行都会实时查询该包名是否已在白名单中(isAppInWhiteList),用于渲染开关的初始状态。数据模型见 AppListItem.kt,包含appName、packageName、appIcon、checked四个字段。

分页由 Paging3 驱动:AppListViewModel.kt 中PagingConfig设置pageSize = 20且enablePlaceholders = false;reloadData会先invalidate()旧的AppListPagingSource,再创建携带新isShowSystem/query参数的新Pager。翻页逻辑在 AppListPagingSource.kt 中:以页码为 key,返回空页时nextKey置null表示没有更多数据。列表 UI(AppListColumn.kt)用LazyColumn+collectAsLazyPagingItems渲染,并处理了加载中、「加载更多...」与加载失败三种LoadState。搜索框 SearchTextField.kt 是一个 56dp 高、圆角 16dp 的OutlinedTextField,输入即触发SearchViewModel.changeQuery。

白名单状态的存储:DataStore 中的布尔键值对

白名单的持久化是整个功能中最值得注意的部分。每个应用的白名单状态并不是存在一个数组里,而是以「前缀 + 包名」为键的独立布尔键写入 Preferences DataStore:

WhiteListViewModel.kt 中,开关变更时执行:

fun updateWhiteList(packageName: String, checked: Boolean) { whiteListRepository.updateWhiteList(packageName, checked) // 1. 先更新内存态 viewModelScope.launch { withContext(Dispatchers.IO) { DataStoreUtils.putData(getString(R.string.whitelist_dot) + packageName, checked) // 2. 再落盘 } } }

即最终写入的键形如WHITELIST_DOTcom.jd.app.reader(前缀常量WHITELIST_DOT定义在 strings.xml),值为true/false,由 DataStoreUtils.kt 的saveBooleanData写入名为SKIPAPPDataStore的 Preferences 文件(见 DataStoreUtils.kt)。

读取侧在 WhiteListRepository.kt 中,它是一个@Singleton,进程内只初始化一次:

@Singleton class WhiteListRepository @Inject constructor() { private val _whiteList: MutableList<String> by lazy { runBlocking { initWhitelist().toMutableList() } } private suspend fun initWhitelist() = withContext(Dispatchers.IO) { val allPreferences = MyApp.context.dataStore.data.first() allPreferences.asMap() .filter { it.key.name.startsWith(getString(R.string.whitelist_dot)) && it.value == true }.keys.map { it.name.substringAfter(getString(R.string.whitelist_dot)) } } // ... }

其初始化逻辑是:把 DataStore 中所有键名以WHITELIST_DOT开头且值为true的键筛选出来,去掉前缀后即得到已加入白名单的包名集合,缓存为内存中的_whiteList。之后:

  • updateWhiteList(packageName, checked):勾选时向列表添加包名,取消勾选时移除(仅改内存态,落盘由 ViewModel 负责);
  • isAppInWhiteList(packageName):通过indexOf(packageName) != -1判断包名是否在白名单中。

这种「前缀键 + 布尔值」的设计让每个应用的白名单状态可以独立读写、互不干扰,新增/移除应用也无需重建整个存储结构。

拦截机制:白名单应用如何被「完全放行」

白名单真正生效的位置在无障碍服务的事件回调中。MyAccessibilityService.kt 的onAccessibilityEvent是 SKIP 每次屏幕事件到达时的入口,其中第 95 行是整个自动点击流程的总闸:

val rootNodePackageName = rootNode.packageName.toString() if (rootNodePackageName != appPackageName) { scanTimes = 0 clickedRect.clear() appPackageName = rootNodePackageName } if (!whiteListRepository.isAppInWhiteList(rootNodePackageName) && (isStrict || scanTimes < 50)) { // 通过配置查找目标控件 rect,找到后 dispatchGesture 执行点击 ... }

可以确认以下几点:

  1. 白名单检查发生在最外层:!whiteListRepository.isAppInWhiteList(rootNodePackageName)为假时,整段「查配置 → 找 rect → 发点击手势」的逻辑直接不执行。这与文档中「不会对这个应用执行任何点击的动作(无论是否在配置中定义)」的语义一致——白名单优先级高于配置文件中packageName/skipIds/skipTexts/skipBounds的规则(配置示例见 skip_config_v2.yaml)。
  2. 判定依据是当前焦点窗口的根节点包名:SKIP 以rootInActiveWindow的packageName识别「当前前台应用」,前台应用切换时会重置scanTimes并清空已点击区域缓存clickedRect,保证白名单判定始终针对当前所在应用。
  3. 点击动作本身通过无障碍手势派发:命中目标后会构造一条 100ms 的GestureDescription,用dispatchGesture完成模拟点击(见 MyAccessibilityService.kt)。白名单应用因为走不到这一步,天然免疫所有此类点击。

另外,从源码结构看,isStrict(严格模式)与scanTimes < 50(扫描次数上限)是与白名单并列的两道控制条件,但它们只影响「是否继续尝试匹配点击」,而白名单是唯一能彻底关闭某应用点击行为的用户级开关。

小结:相关文件索引

关注点文件
功能说明文档docs/guide/white-list/intro.md
白名单页面(搜索栏 + 列表 + 菜单)app/src/main/java/com/android/skip/ui/whitelist/WhiteListActivity.kt
白名单内存态 + DataStore 键解析app/src/main/java/com/android/skip/ui/whitelist/WhiteListRepository.kt
开关持久化写入app/src/main/java/com/android/skip/ui/whitelist/WhiteListViewModel.kt
已安装应用枚举、系统应用过滤、搜索、分页app/src/main/java/com/android/skip/ui/whitelist/list/AppListRepository.kt
Paging3 分页配置app/src/main/java/com/android/skip/ui/whitelist/list/AppListViewModel.kt
DataStore 工具与SKIPAPPDataStoreapp/src/main/java/com/android/skip/util/DataStoreUtils.kt
点击拦截入口(白名单判定)app/src/main/java/com/android/skip/service/MyAccessibilityService.kt
字符串常量(WHITELIST_DOT等)app/src/main/res/values/strings.xml

总结来说:SKIP 的白名单是一个「按包名一票否决」的隔离机制——UI 层通过PackageManager枚举应用并用 Paging3 分页展示,状态层用WHITELIST_DOT前缀布尔键写入 Preferences DataStore,执行层则在无障碍事件回调的最外圈用isAppInWhiteList短路整个点击流程,从而保证被加入白名单的应用永远不会收到 SKIP 发出的任何自动点击。

  • 移动开发
  • 原生移动
  • GUI 自动化

【免费下载链接】SKIP

自动跳过APP开屏广告

项目地址:https://gitcode.com/gh_mirrors/ski/SKIP
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 8:05:21

Flutter电商App 6MB安装包实现搜索购物车订单全链路离线化

简介&#xff1a;这是一份面向前端初学者与Vue2进阶开发者的移动端电商实战项目资源&#xff0c;聚焦智慧商城场景&#xff0c;系统覆盖响应式布局、状态管理、网络请求与路由控制等核心前端工程能力。资源包含2002个文件&#xff0c;以708个JavaScript源码文件&#xff08;含V…

作者头像 李华
网站建设 2026/10/21 11:13:46

复杂Agent工作流:并行、协调与评估优化的工程实践

这几年做大模型应用&#xff0c;我最大的体会是&#xff1a;真正能在生产环境稳定跑通的方案&#xff0c;几乎没有一个是靠单体 Agent 单打独斗完成的。大家最后都会走到同一个方向上——把任务拆成多节点、多角色协作的复杂 Agent 工作流&#xff0c;再围绕并行、协调、评估优…

作者头像 李华
网站建设 2026/10/21 5:39:50

STM32环境温湿度监测系统设计:从选型到低功耗的工程实践

简介&#xff1a;这份资源面向学习STM32嵌入式开发的学生与电子爱好者&#xff0c;提供一套完整的环境温湿度监测系统设计方案&#xff0c;可用于课程设计、毕业设计或单片机实训。系统以STM32为核心&#xff0c;通过DS18B20采集温度、DHT11采集湿度&#xff0c;将数据实时显示…

作者头像 李华
网站建设 2026/10/22 4:26:18

去除AI写作痕迹的实战指南:从检测原理到五步去痕法

1. 为什么你的AI初稿总有一股“塑料感”1.1 从两个真实场景说起先看两个场景。第一个场景&#xff1a;某内容团队用AI生成了一篇产品测评初稿&#xff0c;通篇结构工整、逻辑清晰&#xff0c;但发布后阅读完成率不到15%&#xff0c;评论区第一条是“这文章怎么一股机器人味儿”…

作者头像 李华
网站建设 2026/10/21 9:38:08

昇腾训推一体化实战:从模型微调到推理部署的完整指南

作为常年泡在大模型训练和推理一线的人&#xff0c;我这两年听到最多的吐槽&#xff0c;除了“显卡涨价”“数据清洗烦人”&#xff0c;就是“训练好好的&#xff0c;一到推理上线就各种水土不服”。模型在训练环境里跑得飞快&#xff0c;换到推理环境却要么精度对不上&#xf…

作者头像 李华
网站建设 2026/10/21 11:03:22

Spring Boot Admin实战踩坑:注册、告警与持久化完整排查指南

最近把 Spring Boot Admin 重新捡起来&#xff0c;给一套微服务环境补上监控面板。弄之前我以为这就是个“加依赖、写配置、看页面”的活&#xff0c;结果真正落地时踩了一串坑&#xff1a;注册不上、健康信息缺失、告警时灵时不灵、服务端一重启实例全部失联。整理下来发现&am…

作者头像 李华