我们都知道现在随便打开一个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":"跳过"
}
]
}
]
}
}
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
}
}
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", "自动跳过广告啦")
}
}
}
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
}
// 检查应用是否为系统应用
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
}
打开了【知乎】的【com.zhihu.android.app.ui.activity.LauncherActivity】
自动跳过【知乎】的广告啦
打开了【知乎】的【com.zhihu.android.app.ui.activity.LauncherActivity】
isSkipped: 5秒内已经成功跳过一次广告,无需重复检测
打开了【网易云音乐】的【com.netease.cloudmusic.activity.IconChangeDefaultAlias】
自动跳过【网易云音乐】的广告啦
在我自己使用的过程中遇到误判的情况,比如某个页面的文本中带有跳过的字眼也会触发,所以我们还需要对误判的情况处理一下。这里我分了几种情况。
跳过
、关闭
、跳过(5)
、5s | 跳过
等等,我们这里可以添加一个最大字数长度限制的判断,大部分字数都在五个字以内,所谓我们设置默认最大长度是5,个别APP字数长的可以单独在进行设置。EditText
中输入了跳过
两个字,按照原来的判断逻辑也会触发我们的判断,所以我们只需要TextView类型的文本。当然还有极个别APP的广告跳过两个字是图片,真TM想方设法的搞事哦,遇到这种情况可以单独设置id也能做兼容,有的APP是没有text值的,只有id(12306APP
就是只有id),如果连ID都TM没有那就直接向工信部举报吧(哈哈,开玩笑。。。)。经过上面这些判断后就会大大降低的误触的概率,当然有遇到特殊的情况可以继续加判断
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}】的广告啦")
}
}
【铁路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
多线程并发优化与数据传输效率优化
体积包优化
https://qr18.cn/FVlo89
https://qr18.cn/AQpN4J