• 【安卓】开发跳过广告app,具备“李跳跳”app的部分功能


    前言

    现在手机的开屏广告还是挺多的,还有应用内弹出广告,青少年模式等,市面上很多跳过广告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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232

    3、完整代码

    完整代码已经上传到gitee里面了,可以移步下载,源码链接:广告滚犊子
    在这里插入图片描述

  • 相关阅读:
    密码学入门——环游密码世界
    大数据计算,如何优化SQL?
    医院住院管理系统(Java+Web+JSP+MySQL)
    Java正则表达式之账号检验与判断基础
    uni-app 之 NoticeBar 滚动通知,横向滚动,竖/纵向滚动
    苍穹外卖项目(黑马)学习笔记DAY10
    前端开发遇到问题整理
    第二十三章 Classes - 属性
    python四六级英语在线考试系统django337
    设计模式之模板方法模式C++实现
  • 原文地址:https://blog.csdn.net/m0_47798608/article/details/133315224