Android无障碍服务监听实现自动点击按钮

原理:

通过监听窗口改变事件,监听目标应用,通过视图ID(或文本、或描述、或其他如坐标之类的)找到目标视图,使用无障碍动作点击方法点击它

无障碍服务实现:

1、写一个自己的无障碍服务继承AccessibilityService

public class AppWindowChangeService extends AccessibilityService {
    private static final String TAG = "MyAppWindowChangeService";

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        if (accessibilityEvent == null || accessibilityEvent.getPackageName() == null) return;
        CharSequence packageName = accessibilityEvent.getPackageName();
        CharSequence className = accessibilityEvent.getClassName();

        //监听当前窗口变化,获取Package
        if(accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
             Log.i(TAG, "onAccessibilityEvent: packageName = "+packageName+", className = "+className);
        } 
    }


    @Override
    public void onInterrupt() {
        Log.e(TAG, "onInterrupt");
    }

}

2、AndroidManifest.xml声明这个服务:

<service
            android:name=".AppWindowChangeService"
            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|fontScale|locale|uiMode"
            android:enabled="true"
            android:exported="false"
            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/detection_service_config"/>
        </service>

3、在xml新建一个配置资源,做这个无障碍服务的相关配置:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged|typeViewClicked"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canRetrieveWindowContent="true"
    android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows" />

这里监听类型我还多加了一个typeViewClicked,后面可以用来找你点击的view的相关信息。

启用功能:

自行找到系统设置的无障碍服务功能界面,或者使用代码做跳转到无障碍服务界面:

Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.setPackage("com.android.settings");
            startActivity(intent);

开启自己app的无障碍服务开关,无障碍服务就会启动起来了。

实践:

举个栗子,监听美团外卖启动页的广告的跳过按钮:

import android.accessibilityservice.AccessibilityService;
import android.os.Handler;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;

public class AppWindowChangeService extends AccessibilityService {
    private static final String TAG = "MyAppWindowChangeService";

    private static final String TARGET_PACKAGE_NAME = "com.sankuai.meituan.takeoutnew";
    private static final String TARGET_VIEW_ID = "com.sankuai.meituan.takeoutnew:id/ll_skip";

    private final Runnable runnable = this::findAndClickTargetView;
    private final Handler handler = new Handler();

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        if (accessibilityEvent == null || accessibilityEvent.getPackageName() == null) return;
        CharSequence packageName = accessibilityEvent.getPackageName();
        CharSequence className = accessibilityEvent.getClassName();
        //监听当前窗口变化,获取Package
        if(accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
            if (TARGET_PACKAGE_NAME.equals(packageName)) {
                Log.i(TAG, "Target package opened: " + packageName);
                // 查找并点击目标视图
                handler.removeCallbacks(runnable);
                handler.postDelayed(runnable, 200);
            }
        } else if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
            Log.i(TAG, "onAccessibilityEvent: packageName = "+packageName+", className = "+className);
            Log.e(TAG, "onAccessibilityEvent: 点击view:" + className);
            AccessibilityNodeInfo source = accessibilityEvent.getSource();
            if (source != null) {
                String viewId = source.getViewIdResourceName();
                CharSequence text = source.getText();
                CharSequence contentDescription = source.getContentDescription();

                Log.e(TAG, "View ID: " + viewId);
                Log.e(TAG, "Text: " + text);
                Log.e(TAG, "Content Description: " + contentDescription);

                source.recycle(); // 释放资源
            }
        }
    }

    private void findAndClickTargetView() {
        AccessibilityNodeInfo rootNode = getRootInActiveWindow();
        Log.i(TAG, "findAndClickTargetView: rootNode == " + rootNode);
        if (rootNode != null) {
            AccessibilityNodeInfo targetNode = findNodeById(rootNode, TARGET_VIEW_ID);
            Log.i(TAG, "findNodeById: targetNode = "+targetNode);
            if (targetNode != null) {
                Log.i(TAG, "targetNode != null, start click");
                performClick(targetNode);
                targetNode.recycle();
            }
            rootNode.recycle();
        }
    }

    private AccessibilityNodeInfo findNodeById(AccessibilityNodeInfo rootNode, String viewId) {
        if (rootNode.getViewIdResourceName() != null && rootNode.getViewIdResourceName().equals(viewId)) {
            return AccessibilityNodeInfo.obtain(rootNode);
        }
        for (int i = 0; i < rootNode.getChildCount(); i++) {
            AccessibilityNodeInfo childNode = rootNode.getChild(i);
            if (childNode != null) {
                AccessibilityNodeInfo result = findNodeById(childNode, viewId);
                if (result != null) {
                    return result;
                }
                childNode.recycle();
            }
        }
        return null;
    }

    private void performClick(AccessibilityNodeInfo node) {
        if (node != null && node.isClickable()) {
            node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            Log.i(TAG, "Clicked on node: " + node);
            Toast.makeText(this, "自动点击", Toast.LENGTH_SHORT).show();
        } else {
            Log.i(TAG, "Node is not clickable: " + node);
        }
    }

    @Override
    public void onInterrupt() {
        Log.e(TAG, "onInterrupt");
    }

}

目标包名和目标viewID是我通过点击的时候输出打印看到的,给他倒推回去记录到代码当中。

查找view的动作需要做延时获取,实测马上去获取是获取不到的。

实际实现中,可以记录多个包名,以及对应的需要点击的视图,做李跳跳的效果。

Android高版本注意:如果打开其他App之后日志不打印,回到自己应用之后才会一次性把之前的动作日志打印出来的情况需要将应用的省电策略改为无限制!!!这个问题网上都没有提到,之前一直不生效困扰了我好久。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值