☰
SKIP 设置功能详解:六大设置项的配置逻辑与源码实现原理
2026/10/14 0:16:01 网站建设 项目流程
  • 移动开发
  • 原生移动
  • GUI 自动化

【免费下载链接】SKIP

自动跳过APP开屏广告

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

本文基于 SKIP(自动跳过 APP 开屏广告)官方文档的「设置功能介绍」页面展开,完整覆盖设置页提供的六项功能:自动更新、后台任务隐藏、是否允许提示、是否使用严格模式、自定义配置、切换主题。除逐条说明每个开关的行为与使用方式外,还会结合 SettingsActivity 等源码,讲清每个设置项在 DataStore 中对应的存储键、默认值,以及它如何最终影响无障碍服务的点击逻辑与配置加载链路。读完后,你可以准确理解每个开关的生效范围与适用场景,并能通过源码定位验证其行为。

设置页面整体结构

设置页由 SettingsActivity 承载,采用 Jetpack Compose 构建。onCreate中通过ScaffoldPage依次装配六个组件:

ScaffoldPage(R.string.settings, { finish() }, { AutoUpdateButton(autoUpdateViewModel) RecentButton(recentViewModel) TipButton(tipViewModel) StrictButton(strictViewModel) CustomButton(configViewModel) { /* 打开自定义配置文档 */ } SwitchThemeButton(switchThemeViewModel) NotificationDialog(notificationDialogViewModel) { ... } })

每个设置项均遵循「Button(Compose 组件)+ ViewModel(状态中转)+ Repository(可选,单例状态源)」的 MVVM 分层,设置值统一通过 DataStoreUtils 持久化到 DataStore。对应的存储键定义在 strings.xml 中:

设置项DataStore 键默认值
自动更新AUTO_UPDATEfalse
后台任务隐藏EXCLUDE_FROM_RECENTfalse
是否允许提示SKIP_TIPfalse
是否使用严格模式STRICT_MODEfalse
自定义配置CUSTOM_CONFIG默认配置 URL
切换主题CURRENT_THEMEUI_AUTO(跟随系统)

此外,设置页右上角菜单中有一项「功能介绍」,点击后通过WebViewActivity打开本页文档对应的在线地址,即 strings.xml 中的settings_function_intro_url。

自动更新

开启「自动更新」后,应用会在后台每隔 12 小时左右检查:

  • 「应用版本号」是否有新版本,如果有,在「关于」页面会显示提示;
  • 「配置版本号」是否有新版本,如果有,会主动更新当前的配置文件。

源码实现:WorkManager 周期任务

开关状态由 AutoUpdateViewModel 维护,读写 DataStore 键AUTO_UPDATE,默认false。真正调度逻辑在 SettingsActivity 中:

val periodicWorkRequest = PeriodicWorkRequestBuilder<SyncWorker>(12, TimeUnit.HOURS).build() val workManager = WorkManager.getInstance(this) autoUpdateViewModel.autoUpdate.observe(this) { when (it) { true -> workManager.enqueueUniquePeriodicWork( getString(R.string.worker_sync), // "MySyncWorker" ExistingPeriodicWorkPolicy.UPDATE, periodicWorkRequest ) false -> workManager.cancelUniqueWork(getString(R.string.worker_sync)) } }
  • 开启时以唯一工作名MySyncWorker注册一个 12 小时周期的 SyncWorker;
  • 关闭时取消该唯一工作,即立即停止周期性检查。

SyncWorker.doWork()每次执行两件事:

val configPostSchema = configReadRepository.readConfig() configReadRepository.changeConfigPostState(configPostSchema) versionRepository.checkVersion()
  1. 调用 ConfigReadRepository.readConfig() 重新读取并解析当前配置(包括自定义配置源),刷新配置状态——对应文档中「配置版本号有新版本时主动更新配置文件」;
  2. 调用 ApkVersionRepository 的checkVersion()检查应用新版本——对应「关于」页面的版本提示。

需要注意的适用前提:WorkManager 的周期任务受系统策略约束(如 Doze 模式、省电策略),实际触发时刻会有延迟,因此文档表述为「12 小时左右」。

后台任务隐藏

开启「后台任务隐藏」后,在后台任务窗口中,「SKIP」应用窗口会被隐藏。

该项由 RecentButton 展示,副标题为「在『最近任务』列表隐藏应用」(见settings_background_task_subtitle)。状态存于 DataStore 键EXCLUDE_FROM_RECENT,默认false。

源码实现:setExcludeFromRecents

实际生效逻辑在 MainActivity 中:

recentViewModel.excludeFromRecent.observe(this) { exclude -> (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).let { manager -> manager.appTasks.forEach { task -> task?.setExcludeFromRecents(exclude) } } }

即监听开关变化后,遍历当前应用的所有ApplicationTask并调用setExcludeFromRecents(exclude)。这是 Android 提供的标准 API,效果是在最近任务列表中隐藏该应用的卡片,从而避免 SKIP 在后台任务窗口中暴露,起到一定的「隐形」效果。从源码结构看,该逻辑只在MainActivity前台可见时触发,系统会在任务存在时应用该标记。

是否允许提示

该功能需要开启「允许通知」。开启「是否允许提示」后,当成功执行「点击」操作时,会显示提示「已为您跳过广告」。

提示的触发链路:无障碍手势回调

提示开关由 TipViewModel / TipRepository(@Singleton)管理,存于 DataStore 键SKIP_TIP,默认false。UI 上开/关会显示「需要显示 / 不要显示」(settings_tip_yes/settings_tip_no),副标题为「执行『跳过』时%1$s提示」。

实际提示发生在 MyAccessibilityService 中。服务连接时通过observeForever订阅开关:

private val tipObserver = Observer<Boolean> { isShowTip = it } // onServiceConnected 中: tipRepository.enable.observeForever(tipObserver)

当无障碍服务通过dispatchGesture完成一次点击手势后,在GestureResultCallback.onCompleted中判断并弹出 Toast:

accessibilityService.dispatchGesture( gesture, object : GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription) { super.onCompleted(gestureDescription) if (isShowTip) { MyToast.show(R.string.toast_skip_tip) // "已为您跳过广告" } } }, null )

提示文案toast_skip_tip即 strings.xml 中的「已为您跳过广告」。

为什么依赖「允许通知」

从源码结构看,提示采用 Toast/悬浮方式展示,其权限基础与通知权限关联。SettingsActivity 中有显式约束:

tipViewModel.enable.observe(this) { if (it && !NotificationManagerCompat.from(MyApp.context).areNotificationsEnabled()) { notificationDialogViewModel.changeDialogState(true) } }

即:在系统通知权限未开启时尝试打开提示,会弹出 NotificationDialog,引导用户跳转系统通知设置页(Settings.ACTION_APP_NOTIFICATION_SETTINGS);同时该回调会调用tipViewModel.changeEnable(false)强制关闭提示开关。另外在onResume中,若通知仍未开启,也会自动将开关复位为关闭。因此文档中「该功能需要开启『允许通知』」这一前置条件在实现上是硬约束。

是否使用严格模式

  • 开启「严格模式」:SKIP 会完全遵循配置文件中定义的规则,因此如果配置文件中没有定义规则的应用,就不会执行「点击」操作;
  • 关闭「严格模式」:SKIP 会默认搜索界面中的「跳过」关键字,如果找到就会执行「点击」操作。因此即使没有在配置文件中定义规则的应用,也可以实现「跳过广告」功能,但同时也会存在误触发的情况。

开关由 StrictButton 展示,文案分别为「完全遵循配置规则」(开启)与「默认匹配『跳过』关键字」(关闭),存于 DataStore 键STRICT_MODE,默认false。

源码实现:默认「跳过」规则注入

严格模式的核心差异在 ConfigLoadRepository.getTargetRect():

suspend fun getTargetRect( rootNode: AccessibilityNodeInfo, activityName: String?, isStrict: Boolean? ): Rect? { val targetAppPackage = rootNode.packageName.toString() var targetConfig = configLoadSchemaMap[targetAppPackage] if (isStrict != true) { val defaultSkipTexts = listOf( LoadSkipText(text = getString(R.string.settings_strict_skip)) // "跳过" ) if (targetConfig == null) { targetConfig = ConfigLoadSchema(targetAppPackage, defaultSkipTexts) } else if (targetConfig.skipTexts == null) { targetConfig.skipTexts = defaultSkipTexts } } ... }

这段代码精确对应文档的两种行为:

  1. 非严格模式(isStrict != true):若配置文件中没有该应用(targetConfig == null),会自动构造一个只包含skipTexts: [{text: "跳过"}]的默认规则,即对任何应用默认搜索界面文本「跳过」并点击;若应用已有配置但没有定义skipTexts,也会补上这个默认关键字。这就是「未定义规则的应用也能跳过广告,但可能误触发」的来源——任何界面中出现「跳过」文案的节点(如「跳过引导」按钮、非广告场景的跳过入口)都可能被点击。
  2. 严格模式:不做上述注入,只按配置文件中packageName命中的skipTexts/skipIds/skipBounds三类规则查找目标节点,未配置的应用不会有任何点击行为。

isStrict值来源于 MyAccessibilityService 对StrictRepository.enable的observeForever订阅,在onServiceConnected时注册、onDestroy时移除。

扫描频率上的附带差异

MyAccessibilityService.onAccessibilityEvent中还有一处与严格模式相关的性能保护:

if (!whiteListRepository.isAppInWhiteList(rootNodePackageName) && (isStrict || scanTimes < 50)) { // ... 查找目标节点并点击 }

从源码结构看,在非严格模式下,同一应用最多只处理前 50 次无障碍事件(scanTimes在应用包名切换时清零),这与「开屏广告只出现在启动初期」的场景吻合,也降低了持续全文检索的开销;严格模式下则不做该次数限制,持续按规则匹配。

自定义配置

点击「自定义配置」,可以输入自定义配置规则,支持输入纯文本的 YAML 格式或 JSON 格式,或可以返回 JSON 或 YAML 文本的 URL 链接(副标题即「URL链接 / YAML文本 / JSON文本」)。配置规则参考自定义配置文档。

源码实现:编辑对话框与解析链

「自定义配置」行由 CustomButton 实现。点击后弹出Dialog,内含一个多行OutlinedTextField,初始值从 DataStore 键CUSTOM_CONFIG读取;点击帮助图标会通过WebViewActivity打开自定义配置文档页(settings_custom_config_url)。点击「确定」时:

if (text.isEmpty()) { DataStoreUtils.removeSync(getString(R.string.store_custom_config)) // 清空则删除键 } else { DataStoreUtils.putSyncData(getString(R.string.store_custom_config), text) } configViewModel.readConfig() // 立即重新加载配置

即:清空输入框并确定等于恢复默认配置;否则保存后立即触发 ConfigViewModel.readConfig() 重新解析,无需重启应用。

解析链:URL → YAML → JSON

真正的解析在 ConfigReadRepository,按「URL 拉取 → YAML 转 JSON → JSON 反序列化」三级容错链处理:

suspend fun readConfig() = withContext(Dispatchers.IO) { val customContent = DataStoreUtils.getSyncData( getString(R.string.store_custom_config), getString(R.string.store_default_config) // 默认配置 URL ) getFromJson(getFromYaml(getFromUrl(customContent))) }
  1. getFromUrl:用java.net.URL解析输入内容,若协议为http/https则通过网络请求抓取文本(myApiNetwork.fetchConfigFromUrl),否则原样返回——因此纯文本 YAML/JSON 会自然落入下一级处理;
  2. getFromYaml:尝试用 SnakeYAML 将内容按List<ConfigReadSchema>解析,成功则经 Gson 序列化为 JSON 字符串;解析失败原样返回,兼容本身就是 JSON 的输入;
  3. getFromJson:用 Gson 将最终内容反序列化为List<ConfigReadSchema>(即 ConfigReadSchema 列表,每项含packageName、skipTexts、skipIds、skipBounds),成功时生成ConfigPostSchema(SUCCESS, md5, list),并对内容计算 MD5 作为配置指纹,供版本比对使用;任一步失败则返回ConfigPostSchema(FAIL, "未检测到有效的配置", null)。

解析成功后由 ConfigViewModel.loadConfig() 调用handleConfig()把每条规则转换为内存中的 ConfigLoadSchema(click的"x,y"坐标、bound的"l,t,r,b"字符串都会被转换为Rect),再交给configLoadRepository.loadConfig()供无障碍服务逐事件查询。仓库中的 skip_config_v3.yaml 等 assets 文件展示了配置的实际结构,更完整的字段说明见 自定义配置完整参考。

切换主题

点击「切换主题」,可以选择「浅色主题」、「深色主题」、「跟随系统」。

源码实现:ThemeMode 三态

该功能由 SwitchThemeButton 以FlatButtonMenu下拉菜单实现,三个选项分别携带 theme_light、theme_dark、theme_auto 图标,当前选中项文字以主题色高亮。

主题状态封装在 SwitchThemeCarrier 中,三态枚举为:

enum class ThemeMode { UI_LIGHT, UI_DARK, UI_AUTO } data class SwitchThemeCarrier(val themeMode: ThemeMode, val themeName: Int, val themeIcon: Int)

SwitchThemeRepository(@Singleton)从 DataStore 键CURRENT_THEME读取字符串(默认UI_AUTO),经getThemeCarrier()映射为三态载体;ViewModel 将选择写回 DataStore。

最终渲染在 AppTheme:

val darkTheme = when(currentTheme.value?.themeMode) { ThemeMode.UI_LIGHT -> false ThemeMode.UI_DARK -> true else -> isSystemInDarkTheme() // UI_AUTO 跟随系统 }

即浅色/深色强制指定配色方案,「跟随系统」则委托 Compose 的isSystemInDarkTheme()。AppTheme作为各页面的外层 Composable,使一次切换即可全局生效;同时通过SideEffect同步更新状态栏颜色与浅色/深色状态栏图标外观。

小结

SKIP 的设置页六项功能可归纳为两条主线:

  • 行为控制类(严格模式、提示、后台任务隐藏):直接改变无障碍服务的点击策略或应用可见性,其中严格模式决定「是否注入默认『跳过』关键字规则」,是准确性与覆盖率的权衡开关;
  • 配置与更新类(自动更新、自定义配置、切换主题):前者决定规则来源与新鲜度(URL / YAML / JSON 三级解析链 + 12 小时周期任务),后者决定界面呈现。

所有设置项默认均为「关闭 / 跟随系统」的保守取值,且统一经由 DataStore 持久化、LiveData驱动 UI 刷新,源码路径均在 ui/settings 目录下,可按本文所列文件逐一对照验证。

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

【免费下载链接】SKIP

自动跳过APP开屏广告

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

相关推荐

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询