自动跳过APP启动页广告

背景

  • 我们都知道现在随便打开一个APP,启动页都是充斥着各种各样的广告,一般都是要等三到五秒钟才会自己关闭,或者用户手动点击跳过按钮直接进入首页,其实这件事在以前更为泛滥,已经到了无法忍受的地步,不知道是哪位变态产品想出来的摇一摇进入广告,问题的关键在于TMD的设置的传感器参数非常灵敏,手机稍微动一下就触发打开广告的操作了,简直没人性

  • 其实网上已经有很多人写过类似的文章了,那么为什么我还要重复写一遍呐,虽然实现的整体思路大家都基本一致,但是具体的实现细节或多或少的有些不一样的,我也是抱着学习的态度去实现一遍,算是是对之前写的自动化系列文章的一种延续吧。

实现思路

  • 所有的实现方案都是基于Android无障碍模式做的,加上我们之前已经对微信的各种自动化方案已经很熟悉了,所以这里实现起来也就顺手拈来了。

  • 我们要想自动点击跳过按钮就要找到触发的时机,一般APP启动的时候都会先进入启动页广告,然后在进入首页,所以我们的问题就变成怎么判断APP启动。

  • 我们知道在onAccessibilityEvent方法的回调中是可以看到当前event的一些具体信息的,比如包名,类名等。大致内容如下:

EventType: TYPE_WINDOW_STATE_CHANGED; EventTime: 333079282; PackageName: com.lygttpod.android.auto;WindowChangeTypes: [] [ ClassName: com.lygttpod.android.auto.MainActivity; Text: [Android自动化]; 

我们只需要找到需要的包名,然后去遍历当前页面的元素,找到带有跳过或者关闭字样的节点然后触发点击事件即可。

经过我们的大量尝试后会发现其实只需要监听typeWindowStateChanged就可以了,这样不仅可以减少频繁触发的次数也会过滤掉很多无用的event

  • 怎么根据当前事件的包名去找到我们需要的呐,这里我们可以定义一个配置文件fuck_ad_app_config.json,文件中添加我们需要跳过广告的APP信息,然后在服务链接的时候解析文件取出来数据进行匹配就ok了。
{  
    "fuckAd":{  
        "apps":[  
            {  
                "appName":"XX财富",  
                "packageName":"com.eastmoney.android.berlin",  
                "launcher":"com.eastmoney.android.berlin.activity.MainActivity",  
                "adNodes":[  
                    {  
                        "action":"跳过"  
                    }  
                ]  
            }  
        ]  
    }  
}
  • 看到了包名和启动页就相当于检测到APP启动了,然后用我们之前写好的方法遍历找到指定节点触发点击就好了。
    private fun loadAdConfig(): FuckAdApps? {
        return try {
            val json = AppContext.loadAsset("fuck_ad_app_config.json")
            if (json.isNullOrEmpty()) {
                null
            } else {
                val data: FuckAdApps =
                    Gson().fromJson(json, object : TypeToken<FuckAdApps>() {}.type)
                data
            }
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }
  • 拿到配置文件中的数据后就判断触发event的应用是否在配置文件中,是的话就触发点击跳过操作,大致代码如下
    fun fuckAD(event: AccessibilityEvent) {
        fuckADTaskScope.launch {
            val packageName = event.packageName.default()
            val className = event.className.default()
            val adApp =
                fuckAdApps?.fuckAd?.apps?.find { it.packageName == packageName && it.launcher == className }
            adApp?.let {
                Log.d("FuckADTask", "打开了【${it.appName}】的【${it.launcher}】")
                skipAd(it)
            }
        }
    }
    
    
    private suspend fun skipAd(adApp: AdApp) {
            val acc = FuckADAccessibility.fuckADAccessibility ?: return
            val actions = adApp.adNode.action.split(",")
                val skipResult = actions.find { acc.clickByText(it) } != null
                if (skipResult) {
                    withContext(Dispatchers.Main) {
                        Log.d("FuckADTask", "自动跳过广告啦")
                    }
                }
    }
  • 这样我们就实现了自动点击【跳过】的功能了,但是这样写的会有很大的局限性,就是我们必须先获取到所有需要跳过广告的APP的包名和启动页类名,这是一件非常麻烦的事情,当然我也尝试这么做了,首先我下载了应用市场top50的APP,一个一个的测试,在打印的log中寻找对应的信息,在我尝试找了二十多个APP的时候我放弃了,因为我发现这是一件非常笨重的做法,即使我把下载的这50个APP都配置好了,也不能保证别人能下载和我一模一样的APP啊,市场上APP那么多怎么可能搞得完。

优化

  • 既然每个用户手机中安装的APP都不一样,我们能不能先获取手机中已安装的APP呐,然后再对其处理不就好了么,这样就可以针对当前手机设备中的APP进行操作了,不仅减少了非自己安装的APP遍历过程,也可以覆盖到每个用户的设备。而且Android也提供的有相应的API去获取应用列表。于是就有了如下代码
fun Context.queryAllInstallApp(): MutableList<AdApp> {
    val packageManager = this.packageManager
    // 创建一个 Intent,用于查询所有启动的应用程序
    val intent = Intent(Intent.ACTION_MAIN, null)
    intent.addCategory(Intent.CATEGORY_LAUNCHER)
    // 使用 queryIntentActivities 获取所有匹配的应用列表
    val appInfoList =
        packageManager.queryIntentActivities(intent, 0)
            .filterNot { isSystemApp(it) || isSelf(it) }
            .map {
                val appName = it.loadLabel(packageManager).toString()
                val packageName = it.activityInfo.packageName
                val className = it.activityInfo.name
                AdApp(appName, packageName, className)
            }
            .toMutableList()

    return appInfoList
}
  • 这样就可以全面覆盖到每个用户安装的APP了,但是在测试中会发现还会有点问题,就是手机系统中内置的APP也会在里边,有很多其实我们是不需要去监听的,于是我们就把系统内置APP给过滤掉,顺手再把自己的APP也过滤了。当然其实想要做的更好就在添加一个配置菜单,让用户自己去选择需要过滤的APP也是一种不错的选择(后边有需要再加吧),这里我就先自动过滤系统内置了
// 检查应用是否为系统应用
private fun isSystemApp(resolveInfo: ResolveInfo): Boolean {
    val regex = "com\.android\.[^.]+" // 匹配系统应用 com.android. 后面跟着任意不包含 . 的字符
    val result = resolveInfo.activityInfo.packageName.matches(Regex(regex))
    Log.d("isSystemApp", "isSystemApp: ${resolveInfo.activityInfo.packageName} : $result")
    return result
}
  • 这样我们就真正实现了自动跳过自己设备安装的非系统APP的启动页广告了,让我们变一下看一下效果吧。
打开了【知乎】的【com.zhihu.android.app.ui.activity.LauncherActivity】
自动跳过【知乎】的广告啦
打开了【知乎】的【com.zhihu.android.app.ui.activity.LauncherActivity】
isSkipped: 5秒内已经成功跳过一次广告,无需重复检测
打开了【网易云音乐】的【com.netease.cloudmusic.activity.IconChangeDefaultAlias】
自动跳过【网易云音乐】的广告啦

进阶

在我自己使用的过程中遇到误判的情况,比如某个页面的文本中带有跳过的字眼也会触发,所以我们还需要对误判的情况处理一下。这里我分了几种情况。

1、action的长度

  • 广告页的跳过有一定的规则,一般都会有字数限制,比如跳过关闭跳过(5)5s | 跳过等等,我们这里可以添加一个最大字数长度限制的判断,大部分字数都在五个字以内,所谓我们设置默认最大长度是5,个别APP字数长的可以单独在进行设置。

2、action所在类的类型

  • 广告页的跳过一般都是文本消息,如果如你在EditText中输入了跳过两个字,按照原来的判断逻辑也会触发我们的判断,所以我们只需要TextView类型的文本。当然还有极个别APP的广告跳过两个字是图片,真TM想方设法的搞事哦,遇到这种情况可以单独设置id也能做兼容,有的APP是没有text值的,只有id(12306APP就是只有id),如果连ID都TM没有那就直接向工信部举报吧(哈哈,开玩笑。。。)。

3、action是否在列表内的内容

  • 大部分情况下广告页的内容不会是在列表中展示的,为了避免某些内容中的有跳过字眼触发误判,还可以加上判断是否在ListView或RecyclerView中等等,这样就避免了大部分内容区域带有关键字眼导致的误判

经过上面这些判断后就会大大降低的误触的概率,当然有遇到特殊的情况可以继续加判断

val skipResult = acc.clickByCustomRule {
    if (id.isNotBlank()) {
        it.viewIdResourceName == id
    } else {
        if (it.isTextView()) {
            val text = it.text.default()
            text.length <= adApp.actionMaxLength()
                    && actions.find { action -> text.contains(action) } != null
                    && it.inListView().not()
        } else {
            false
        }
    }
}
if (skipResult) {
    withContext(Dispatchers.Main) {
        Log.d("FuckADTask", "自动跳过【${adApp.appName}】的广告啦")
    }
}
  • 当然也可以针对每个APP单独设置自己的规则,设置内容如下

  • 至此自动跳过启动页广告的自动化工具算是完成了,中间其实有很多优化的细节就不在文章中展开讲解了,需要详细了解的可以阅读一下源码

适配

  • 【铁路12306】获取到的text是空,所以就设置ID为com.MobileTicket:id/tv_skip 进行适配,className = android.widget.TextView → id = com.MobileTicket:id/tv_skip → isClickable = true
+--- className = android.widget.FrameLayout
|  --- className = android.widget.LinearLayout
|    --- className = android.widget.FrameLayout → id = android:id/content
|      --- className = android.widget.LinearLayout → id = com.MobileTicket:id/ll_splash_ad_container
|        +--- className = android.widget.FrameLayout → id = com.MobileTicket:id/fl_adContent_container
|        |  +--- className = android.widget.ImageView → id = com.MobileTicket:id/img_adContent → description = 广告 → isClickable = true
|        |  +--- className = android.widget.FrameLayout → id = com.MobileTicket:id/fl_skip_wrong → isClickable = true
|        |  |  --- className = android.widget.TextView → id = com.MobileTicket:id/tv_skip → isClickable = true
|        |  --- className = android.view.ViewGroup
|        |    +--- className = android.widget.ImageView → id = com.MobileTicket:id/lottie_scroll_view
|        |    --- className = android.view.ViewGroup → id = com.MobileTicket:id/cl_button_container → isClickable = true
|        |      +--- className = android.widget.ImageView → id = com.MobileTicket:id/lottie_button_view
|        |      +--- className = android.widget.TextView → text = 上滑或点击跳转至详情页 → id = com.MobileTicket:id/tv_button_text
|        |      --- className = android.widget.ImageView → id = com.MobileTicket:id/iv_right_arrow
|   
  • 【中国联通】获取到的文本是5s | 跳过,action长度是7,所以设置actionMaxLength为7即可
+--- className = android.widget.FrameLayout
|  --- className = android.widget.LinearLayout
|    --- className = android.widget.FrameLayout
|      --- className = android.widget.LinearLayout → id = com.sinovatech.unicom.ui:id/action_bar_root
|        --- className = android.widget.FrameLayout → id = android:id/content
|          --- className = android.widget.RelativeLayout
|            +--- className = android.widget.FrameLayout → id = com.sinovatech.unicom.ui:id/splash_container2
|            |  --- className = android.widget.RelativeLayout
|            |    +--- className = android.widget.ImageView → id = com.sinovatech.unicom.ui:id/welcome_bg
|            |    +--- className = android.widget.TextView → text = 5s | 跳过 → id = com.sinovatech.unicom.ui:id/welcome_advertise_close → isClickable = true
|            |    --- className = android.widget.ImageView → id = com.sinovatech.unicom.ui:id/adv_bottom_detail → isClickable = true
|            --- className = android.widget.ImageView → id = com.sinovatech.unicom.ui:id/welcome_bottom_logo

在使用过程中如有发现其他特殊的APP欢迎提 issues 或者打在评论区,我们一起去维护,造福更多人。

最后

  • 在具体的代码实现中已经做了很多优化工作,不会影响APP正常使用,也不会导致耗电增多或导致系统卡顿之类的问题,完全可以放心使用。

  • 为了防止我们的广告服务被系统杀死,可以设置我们的APP为系统白名单或者允许后台运行等一系列保活方法让它多活一会吧。

为了帮助到大家更好的全面清晰的掌握好启动优化,准备了性能优化相关的核心笔记(包含了启动优化、内存优化、网络优化……等,还该底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

Android 性能监控框架》:https://qr18.cn/FVlo89

Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战


http://www.niftyadmin.cn/n/5121077.html

相关文章

bootstrap_study

<meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name"viewport" content"widthdevice-width, initial-scale1"> <!-- 新 Bootstrap 核心 CSS 文件 --> <link href"https://cdn.staticfile.org/…

Git 常用基本命令

一.基础命令 1&#xff09;vim命令&#xff1a; ESC ——回到命令模式&#xff08;默认&#xff09; 按下i&#xff0c;a等键 ——进入编辑模式 命令模式&#xff1a; yy ——复制 p ——粘贴 末行模式&#xff1a; :w ——保存 :q ——退出 :wq ——保存退出 编辑模式&#…

哪些车企AEB标配率「不及格」

对于汽车智能化来说&#xff0c;基础安全不分高低配。但实际情况&#xff0c;却是另一番景象。 在全球范围&#xff0c;目前不少国家及地区的监管机构正在考虑将AEB&#xff08;紧急制动系统&#xff09;作为乘用车的标配纳入法规&#xff0c;“这是道路安全向前迈出了重要的一…

一张Lambda大数据架构架构图

架构图 大数据的架构包括了Lambda架构和Kappa架构&#xff0c;Lambda架构分解为三层&#xff1a;即批处理层、加速层和服务层&#xff1b;Kappa架构不同于Lambda同时计算流计算和批计算并合并视图&#xff0c;Kappa只会通过流计算一条的数据链路计算并产生视图。 该系统的大数…

AIR101 LuatOS LVGL 显示多个标签例程

屏幕资料 AIR101与屏幕连接 PC端仿真环境合宙官方PC端版本环境搭建教程 PC电脑仿真 -- sys库是标配 _G.sys require("sys") sys.taskInit(function()local cnt0lvgl.init(480,320)--lvgl初始化local cont lvgl.cont_create(nil, nil);-- lvgl.cont_set_fit(cont, …

Harmony 个人中心(页面交互、跳转、导航、容器组件)

个人中心 前言正文一、创建工程二、登录① 更换启动页面② 拓展修饰符③ 页面跳转④ 等待进度条 三、导航栏四、首页① 轮播图② 网格列表 五、我的① 带参数跳转 六、源码 前言 今天是1024&#xff0c;祝各位程序员们&#xff0c;钱多事少离家近&#xff0c;不秃也强bug黄。在…

库龄管理,卖不动的货如同收不回的款?(ODOO15/16)

在当今一切都强调【快速】的经营背景之下&#xff0c;库存的管理往往决定了企业的效益&#xff0c;存货在财务报表里是【资产】&#xff0c;但是在老板的心目中&#xff0c;那就是风险&#xff0c;除非具有收藏价值或随时间能增值的存货才是真正的资产&#xff0c;其他类型的存…

shell获取字符串长度,字符串截取

shell 获取字符串的长度可以使用 # 或者使用expr计算 expr 不熟悉的可以查看这篇文章 获取字符串长度 #!/bin/bash str"helloworld" #打印str长度 echo ${#str} #或者使用expr expr length $str 打印结果为10 需要注意的地方就是str“xxx” 字符串等号前面不要有…