自动跳过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() {}.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 {
    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单独设置自己的规则,设置内容如下

自动跳过APP启动页广告_第1张图片

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

适配

  • 【铁路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

启动优化
自动跳过APP启动页广告_第2张图片
内存优化
自动跳过APP启动页广告_第3张图片
UI优化
自动跳过APP启动页广告_第4张图片
网络优化
自动跳过APP启动页广告_第5张图片
Bitmap优化与图片压缩优化https://qr18.cn/FVlo89
自动跳过APP启动页广告_第6张图片
多线程并发优化与数据传输效率优化
自动跳过APP启动页广告_第7张图片
体积包优化
自动跳过APP启动页广告_第8张图片

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

自动跳过APP启动页广告_第9张图片

《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 源码分析实战

自动跳过APP启动页广告_第10张图片

你可能感兴趣的:(Framework,Android,性能优化,gitee,Android,移动开发,启动优化,性能优化,framework)