前言

现在手机的开屏广告还是挺多的,还有应用内弹出广告,青少年模式等,市面上很多跳过广告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里面了,可以移步下载,源码链接:广告滚犊子
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐