前言
现在手机的开屏广告还是挺多的,还有应用内弹出广告,青少年模式等,市面上很多跳过广告app下架了,我利用工作闲暇时间开发了自己用的app,不传播,分享知识!
实现思路
利用手机的无障碍服务,该服务可以监听屏幕窗口的layout树,通过遍历窗口的View,检测到包含“跳过”、“关闭”等按钮,执行自动化点击事件,大部分跳过广告的原理都是这样的,根据弹窗规则匹配的JSON文件里面定义的上百种APP的弹窗规则,可以跳过大部分弹窗。
应用截图
代码结构
包括两个读取规则实体类,一个无障碍服务类,一个Activity类和一个规则JSON文件。
主要代码
一、activity类
package com.lsl.adskip;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import com.lsl.adskip.service.MyService;
import java.time.LocalDateTime;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ImageButton imageBtn;
private TextView tipView;
private Switch switchButton;
private Switch dialogSwitch;
private Switch superModeBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
imageBtn = findViewById(R.id.imageBtn);
tipView = findViewById(R.id.tip);
imageBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
});
switchButton = findViewById(R.id.switch_button);
SharedPreferences sharedPreferences = getSharedPreferences("serviceState", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
switchButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// 状态改变时的逻辑处理
if (isChecked) {
// 开启屏蔽广告
if (MyService.isServiceEnable()) {
MyService.serviceState = true;
switchButton.setChecked(true);
editor.putBoolean("serviceState", true);
} else {
switchButton.setChecked(false);
showDialog();
}
} else {
// 开关关闭时的操作
MyService.serviceState = false;
switchButton.setChecked(false);
editor.putBoolean("serviceState", false);
}
editor.apply();
}
});
dialogSwitch = findViewById(R.id.switch_dialog);
SharedPreferences dialogState = getSharedPreferences("dialogAD", Context.MODE_PRIVATE);
SharedPreferences.Editor adEditor = dialogState.edit();
dialogSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
if (MyService.isServiceEnable()) {
adEditor.putBoolean("dialogAD", true);
MyService.dialogAD = true;
dialogSwitch.setChecked(true);
} else {
dialogSwitch.setChecked(false);
showDialog();
}
} else {
adEditor.putBoolean("dialogAD", false);
MyService.dialogAD = false;
dialogSwitch.setChecked(false);
}
adEditor.apply();
}
});
superModeBtn = findViewById(R.id.superMode);
SharedPreferences superSwitch = getSharedPreferences("superSwitch",Context.MODE_PRIVATE);
SharedPreferences.Editor superEditor = superSwitch.edit();
superModeBtn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked){
if (MyService.isServiceEnable()){
MyService.superMode = true;
superEditor.putBoolean("superSwitch",true);
showSuperTip();
}else{
superModeBtn.setChecked(false);
showDialog();
}
}else{
superEditor.putBoolean("superSwitch",false);
MyService.superMode = false;
}
superEditor.apply();
}
});
}
@Override
public void onPause(){
Log.d("应用","暂停");
super.onPause();
}
@Override
public void onResume() {
if (MyService.isServiceEnable()) {
tipView.setText("无障碍服务已启动");
imageBtn.setImageResource(R.drawable.laugh);
SharedPreferences sharedPreferences = getSharedPreferences("serviceState", Context.MODE_PRIVATE);
boolean state = sharedPreferences.getBoolean("serviceState", false);
MyService.serviceState = state;
boolean dialogState = getSharedPreferences("dialogAD", Context.MODE_PRIVATE).getBoolean("dialogAD", false);
MyService.dialogAD = dialogState;
dialogSwitch.setChecked(dialogState);
switchButton.setChecked(state);
boolean superModeState = getSharedPreferences("superSwitch",Context.MODE_PRIVATE).getBoolean("superSwitch",false);
superModeBtn.setChecked(superModeState);
MyService.superMode = superModeState;
} else {
tipView.setText(R.string.tip);
imageBtn.setImageResource(R.drawable.cray);
dialogSwitch.setChecked(false);
switchButton.setChecked(false);
MyService.dialogAD = false;
MyService.serviceState = false;
showDialog();
}
super.onResume();
}
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("服务状态");
builder.setMessage("无障碍服务未开启,无法使用,请进入系统无障碍服务中找到本APP,开启服务。温馨提示:本应用没有网络功能,也无需授权手机隐私相关权限,您的隐私是百分百安全的,请放心使用!");
builder.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast toast = Toast.makeText(MainActivity.this, "您已取消,如需正常使用,请点击熊猫头", Toast.LENGTH_SHORT);
toast.show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
private void showSuperTip(){
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("服务说明");
builder.setMessage("你已开启超级模式,该模式收录了几百种APP的弹窗匹配规则,原理是监听弹窗类型,并自动帮你点击对应按钮,如你在使用其他App过程中出现了意料之外的效果,请关闭该模式");
builder.setNegativeButton("我已知晓", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
public void onDestroy(){
super.onDestroy();
Log.d("应用","重启");
//startAllApp();
}
}
2、MyService类
package com.lsl.adskip.service;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.lsl.adskip.MainActivity;
import com.lsl.adskip.R;
import com.lsl.adskip.entity.RuleDetail;
import com.lsl.adskip.entity.RuleEntity;
import com.lsl.adskip.util.ToastUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyService extends AccessibilityService {
private static MyService instance;
public static boolean serviceState = false;
public static boolean dialogAD = false;
public static boolean superMode = false;
private ExecutorService executor = Executors.newFixedThreadPool(4);
private List<RuleEntity> reluList;
public static boolean isServiceEnable() {
return instance != null;
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (serviceState) {
if (event != null) {
//跳过广告逻辑
Log.d("监听中", "开屏广告");
AccessibilityNodeInfo currentNodeInfo = getCurrentRootNode();
if (currentNodeInfo != null) {
List<AccessibilityNodeInfo> skipBtnNodes = currentNodeInfo.findAccessibilityNodeInfosByText("跳过");
List<AccessibilityNodeInfo> closeBtnNodes = currentNodeInfo.findAccessibilityNodeInfosByText("关闭");
if (closeBtnNodes != null && !closeBtnNodes.isEmpty()) {
AccessibilityNodeInfo ad = closeBtnNodes.get(0);
Log.d("检测到广告节点:", String.valueOf(ad));
ad.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
if (skipBtnNodes != null && !skipBtnNodes.isEmpty()) {
AccessibilityNodeInfo ac = skipBtnNodes.get(0);
Log.d("检测到广告节点:", String.valueOf(ac));
ac.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
//监听弹窗广告
if (dialogAD) {
if (event != null) {
Log.d("监听中", "弹窗广告");
AccessibilityNodeInfo dialogNodeInfo = getCurrentRootNode();
if (dialogNodeInfo != null) {
String viewId = dialogNodeInfo.getPackageName() + ":id/" + "close";
List<AccessibilityNodeInfo> closeNodes = dialogNodeInfo.findAccessibilityNodeInfosByViewId(viewId);
if (closeNodes != null && !closeNodes.isEmpty()) {
AccessibilityNodeInfo ac = closeNodes.get(0);
ac.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
if (event != null && superMode) {
executor.execute(new Runnable() {
@Override
public void run() {
if (reluList != null) {
for (RuleEntity re : reluList) {
for (RuleDetail rd : re.getRules()) {
if (searchNode(rd.getId()) != null) {
AccessibilityNodeInfo node = searchNode(rd.getAction());
if (node != null) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.d("模式", "超级模式:" + String.valueOf(node));
}
}
}
}
}
}
});
}
}
@Override
public void onInterrupt() {
Log.d("服务状态", "断开连接");
}
@Override
public void onServiceConnected() {
super.onServiceConnected();
instance = this;
Log.d("服务状态", "连接成功");
ToastUtil.showLongToast(this, "服务已连接");
//startForeNotification();
executor.execute(new Runnable() {
@Override
public void run() {
reluList = readJsonToRuleList();
Log.d("加载", "自定义规则已加载");
}
});
}
@Override
public void onCreate() {
super.onCreate();
Log.d("服务状态", "服务重启");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
Log.d("生命周期", "destroy");
ToastUtil.showLongToast(this, "屏蔽广告服务已被销毁");
instance = null;
executor.shutdown();
}
private AccessibilityNodeInfo getCurrentRootNode() {
try {
return getRootInActiveWindow();
} catch (Exception e) {
if (e.getMessage() != null) {
Log.e("根节点异常", e.getMessage());
}
return null;
}
}
/**
* 读取自定义广告类型JSON文件生成规则实体列表
*/
public List<RuleEntity> readJsonToRuleList() {
List<RuleEntity> ruleEntityList = new ArrayList<>();
try {
InputStream inputStream = getResources().openRawResource(R.raw.all_rules);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
inputStream.close();
JSONArray jsonArray = new JSONArray(sb.toString());
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
Iterator<String> keys = jsonObject.keys();
while (keys.hasNext()) {
String key = keys.next();
String value = jsonObject.getString(key);
JSONObject ruleEntityJson = new JSONObject(value);
JSONArray popupRules = ruleEntityJson.getJSONArray("popup_rules");
RuleEntity ruleEntity = new RuleEntity();
ArrayList<RuleDetail> ruleDetails = new ArrayList<>();
for (int j = 0; j < popupRules.length(); j++) {
JSONObject ruleObject = popupRules.getJSONObject(j);
RuleDetail ruleDetail = new RuleDetail(ruleObject.getString("id"),
ruleObject.getString("action"));
ruleDetails.add(ruleDetail);
}
ruleEntity.setRules(ruleDetails);
ruleEntityList.add(ruleEntity);
}
}
return ruleEntityList;
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return ruleEntityList;
}
/**
* 匹配广告节点
* @param filter
* @return
*/
private AccessibilityNodeInfo searchNode(String filter) {
AccessibilityNodeInfo rootNode = getCurrentRootNode();
if (rootNode != null) {
List<AccessibilityNodeInfo> nodeInfosByText = rootNode.findAccessibilityNodeInfosByText(filter);
if (!nodeInfosByText.isEmpty()) {
return nodeInfosByText.get(0);
}
String viewId = rootNode.getPackageName() + ":id/" + filter;
List<AccessibilityNodeInfo> nodeInfosByViewId = rootNode.findAccessibilityNodeInfosByViewId(viewId);
if (!nodeInfosByViewId.isEmpty()) {
return nodeInfosByViewId.get(0);
}
}
return null;
}
}
3、完整代码
完整代码已经上传到gitee里面了,可以移步下载,源码链接:广告滚犊子