0x1、瞎聊
好久没更文,诈尸水一篇,上星期四看到许多群在转发 《大小姐李跳跳:无限期停止更新公告》简述下内容:
一个 没联网也没盈利 的 使用开屏广告越过APP 的开发者,收到了 “南山必胜客” 发的 律师函,说他的APP可用于屏蔽、过滤XX阅读器的广告服务,构成 不正当竞争,并最终导致 “用户福祉的减损“。
2333,本来 看广告 是 用户福祉 …
除李跳跳外,其它几款比较有名的同类型APP也不谋而合也收到律师函,如:叮小跳、大圣净化、轻发动等。
有重视了李跳跳的公号的应该都知道,它被搞不是一天两天了,之前就经历过 酷安下架 和 国产手机体系安装报毒:
从酷安小编的一席话不难看出被搞的本质原因:
- 1、断人财源,究竟 开屏广告 已经成为 许多App的首要变现手段,据央视财经频道报导,某些手机App经过开屏弹窗广告取得的收益,占其总广告收入高达80%。
- 2、影响力大,看下这篇文章《谈谈我的观点》,阅览量10w+,8.2w+点赞,用户量不得有个几十万?
所以,被搞是 情理之中,即使南山必胜客不站出来,也会有其它利益受损方站出来,仅仅迟早的问题~
别的,还有一点 广告收入是要纳税的,现在广告收入少了,也导致… 懂得都懂~
还记得不久前的 多多提权坚强用户 事情吗?Google Play 以 “恶意软件” 为由将其下架,并向已下载该APP的用户宣布警告,提示卸载。反观国内,屁事没有,许多人乃至不知道这件事。
所以,这种大环境下的 为众人报薪者,必冻毙于风雪。综上,李跳跳停更是 无法之举,感谢开发者一直 用爱发电 默默更新。
虽然停更,可是仍是能搜索下载到APP的,鱼龙混杂,各位读者下载安装时 注意甄别!!!比如这种厌恶盗版:
当然,也可以尝试其它平替,假如觉得代码闭源不放心,可在Github搜下关键字 “广告越过” 。
也可以在了解完越过原理后,自己动手写一个,不过还请切记 “闷声发大财“~
0x2、Android广告越过原理
Android中的广告越过原理有两派:
- 使用手机体系提供的 威屁恩 接口完成 本地署理,接收使用的网络恳求,配合对应的阻拦规矩(DNS、主机域名) 来完成广告阻拦。
- 使用Android AccessibilityService无障碍服务 来辨认广告越过按钮,然后模仿点击,一般针对App开屏广告,国内绝大部分广告越过APP都是这种。
0x3、简单七步完成开屏广告越过
第一种计划自己完成的本钱比较高,直接介绍下有哪些软件支持,按需安装即可:
- AdGuard:支持APP中的广告阻拦、自定义规矩和过滤器,完整功用要钱,3台设备一年40+。
- Adblock:支持阅读器阅读网页时的广告屏蔽。不过许多阅读器都内置了广告屏蔽功用,而且能直接订阅第三方规矩,如Via、X阅读器等。乃至笔者用的IDM+自带阅读器都有这个功用:
假如确实有爱好想自己折腾,可以参阅下这两个开源库:
- AdAway
- NetBare-Android
第二种计划就十分简单了,完全可以自己写一个耍耍,根底知识就不展开讲了,不了解的读者可以移步至《杰哥带你玩转Android自动化-AccessibilityService根底》大概了解下前置知识。
完成越过开屏广告的中心点就三步:重视Event → 查找结点 → 点击结点,创立一个新的Android项目后,直接开整~
1、自定义AccessibilityService
class ADGunService : AccessibilityService() {
companion object {
var instance: ADGunService? = null // 单例
val isServiceEnable: Boolean get() = instance != null // 判别无障碍服务是否可用
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event?.let {
// 在这儿写越过广告的逻辑
log.d(TAG, "$it")
}
}
override fun onInterrupt() {}
override fun onServiceConnected() {
super.onServiceConnected()
instance = this
}
override fun onDestroy() {
super.onDestroy()
instance = null
}
}
2、在res/xml目录下新建服务配置文件
ad_gun_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_description"
android:accessibilityEventTypes="typeAllMask"
android:canPerformGestures="true"
android:accessibilityFeedbackType="feedbackSpoken"
android:canRetrieveWindowContent="true"
android:accessibilityFlags="flagRetrieveInteractiveWindows"
android:notificationTimeout="1000"/>
3、AndroidManifest.xml中对服务进行声明
<service
android:name=".ADGunService"
android:exported="false"
android:label="AD 滚犊子!!!"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/ad_gun_service_config" />
</service>
4、写个简单的页面xml
显现服务敞开状况的文本和一个去敞开的按钮(activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_service_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="越过广告服务状况:" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bt_open_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="去敞开" />
</LinearLayout>
5、控件绑定并设置UI和点击事情
加个设置无障碍服务的状况UI的办法,一个点击跳转无障碍服务设置页(MainActivity.kt)
class MainActivity : AppCompatActivity() {
private lateinit var mServiceStatusTv: TextView
private lateinit var mToOpenBt: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mServiceStatusTv = findViewById(R.id.tv_service_status)
mToOpenBt = findViewById<Button>(R.id.bt_open_service).apply {
setOnClickListener { jumpAccessibilityServiceSettings() }
}
}
override fun onResume() {
super.onResume()
refreshServiceStatusUI()
}
/**
* 刷新无障碍服务状况的UI
* */
private fun refreshServiceStatusUI() {
if (ADGunService.isServiceEnable) {
mServiceStatusTv.text = "越过广告服务状况:已敞开"
mToOpenBt.visibility = View.GONE
} else {
mServiceStatusTv.text = "越过广告服务状况:未敞开"
mToOpenBt.visibility = View.VISIBLE
}
}
}
运转后,点击去敞开按钮,如下图依次敞开无障碍服务
此时打开其它APP,可以看到Logcat输出的Event信息:
6、查找越过广告结点
得益于市场监管总局修订发布的《互联网广告管理办法》
查找越过广告结点变得简单多了,想当年,假按钮,极小点击区域等骗点击的手段层出不穷。这儿只需要遍历页面结点,查找包含”越过”的结点即可~
/**
* 取得当时视图根节点
* */
private fun getCurrentRootNode() = try {
rootInActiveWindow
} catch (e: Exception) {
e.message?.let { Log.e(TAG, it) }
null
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event?.let {
// 假如查找包含越过按钮的结点列表不为空,取第一个,然后输出
getCurrentRootNode()?.findAccessibilityNodeInfosByText("越过").takeUnless { it.isNullOrEmpty() }?.get(0)?.let {
logD("检测到越过广告结点:$it")
}
}
}
运转后随便打开一个有开屏广告的APP,可以看到控制台输出的结点信息:
7、点击越过广告结点
有《互联网广告管理办法》这份文件在,大部分APP应该不会知法犯法,结点一般是支持直接点击的:
所以直接performAction()触发结点点击:
运转看看越过效果:
牛批,有些广告还没看清直接就越过了,我们经过简单七步就快速完成了一个广告越过APP。
当然,要投入真正使用还得完善一些细节,比如 前台服务保活,引进线程池/协程解析结点防止阻塞主线程,监听特定Event进步结点查找功率 等 , 本文仅仅抛砖引玉,对Android自动化感爱好的童鞋,可以移步至《杰哥带你玩转Android自动化》自行学习~
*8、补充:自定义规矩过滤
除了开屏广告外,还有一种很烦人的弹窗:
越过广告类APP里的自定义规矩过滤就是针对这种,这种规矩只能靠人力来堆,众人拾柴火焰高,找到一个比较全的:LiTiaotiao-Custom-Rules,直接仿制全部规矩的json保存到res/raw文件夹下,截取其中一段:
{
"-1606001344": "{"popup_rules":[{"id":"playing_tv_redeem_title","action":"playing_ic_close"}]}"
},
不难发现结构规矩,id → 匹配结点的id或文本,action → 点击结点的id或文本,key值是随机变化的字符串,直接使用Java自带抠脚Json来解析,先定义两个实体类存数据:
data class RuleEntity(
val popupRules: ArrayList<RuleDetail>
)
data class RuleDetail(
val id: String,
val action: String
)
接着整个线程池用来读取解析Json文件,以及解析结点,耗时操作防止阻塞主线程~
var executor: ExecutorService = Executors.newFixedThreadPool(4) // 执行任务的线程池
接着编写解析json文件的办法,返回规矩列表:
/**
* 读取json文件生成规矩实体列表
* */
private fun readJsonToRuleList(): List<RuleEntity>? {
try {
val inputStream = resources.openRawResource(R.raw.custom_rules)
val reader = BufferedReader(InputStreamReader(inputStream))
val sb = StringBuilder()
reader.use {
var line: String? = it.readLine()
while (line != null) {
sb.append(line)
line = it.readLine()
}
}
val ruleEntityList = arrayListOf<RuleEntity>()
val jsonArray = JSONArray(sb.toString())
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val keys = jsonObject.keys()
while (keys.hasNext()) {
val key = keys.next()
val value = jsonObject.getString(key)
val ruleEntityJson = JSONObject(value)
val popupRules = ruleEntityJson.getJSONArray("popup_rules")
val ruleEntity = RuleEntity(arrayListOf())
for (j in 0 until popupRules.length()) {
val ruleObject = popupRules.getJSONObject(j)
val ruleDetail = RuleDetail(ruleObject.getString("id"), ruleObject.getString("action"))
ruleEntity.popupRules.add(ruleDetail)
}
ruleEntityList.add(ruleEntity)
}
}
return ruleEntityList
} catch (e: IOException) {
e.printStackTrace()
} catch (e: JSONException) {
e.printStackTrace()
}
return null
}
接着在onServiceConnected()时调用此办法,即无障碍服务发动时读取初始化:
override fun onServiceConnected() {
super.onServiceConnected()
executor.execute {
ruleList = readJsonToRuleList()
ruleList?.forEach { logD("$it") }
logD("自定义规矩列表已加载...")
}
instance = this
}
运转后可以看到规矩列表已加载:
接着编写匹配文字和id结点的办法:
/**
* 递归遍历查找匹配文本或id结点
* 结点id的构造规矩:包名:id/详细id
* */
private fun searchNode(filter: String): AccessibilityNodeInfo? {
val rootNode = getCurrentRootNode()
if (rootNode != null) {
rootNode.findAccessibilityNodeInfosByText(filter).takeUnless { it.isNullOrEmpty() }?.let { return it[0] }
if (!rootNode.packageName.isNullOrBlank()) {
rootNode.findAccessibilityNodeInfosByViewId("${rootNode.packageName}:id/$filter")
.takeUnless { it.isNullOrEmpty() }?.let { return it[0] }
}
}
return null
}
接着在onAccessibilityEvent()遍历自定义规矩列表,批量调用
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event?.let {
executor.execute {
searchNode("越过")?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
ruleList?.forEach {
it.popupRules.forEach { rule ->
// 假如定位到匹配结点,查找要点击的结点并点击
if (searchNode(rule.id) != null) searchNode(rule.action)?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
}
}
接着可以找 LiTiaotiao-Custom-Rules 提到APP去验证,反正笔者试了下网易云的更新弹窗可以自动点击关闭~
参阅文献:
- 《“李跳跳”为何成了大厂公敌?》
- 《“腾讯是一个伟大的企业”,知名李跳跳软件完结了》
- 《被控涉嫌“不正当竞争”,开屏广告越过 App“李跳跳”将无限期停更》