Android监听用户行为操作(AccessibilityService)

前言

今天我们将使用AccessibilityService实现:

  1. 监听第三方程序的界面变化(监听第三方程序的启动的实现原理)。
  2. 模拟点击第三方应用的按钮(自动抢红包程序的实现原理)。
  3. 监听第三方程序的点击事件。

如果要测试的第三方应用不为自己的,则需要获取第三方应用的包名,当前Acvtivity等信息。可以参考:
Android获取第三方程序的包名

模拟程序

我们先写一个模拟程序,该模拟程序只有一个按钮用于模拟点击事件。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Toast.makeText(MainActivity.this, "我被点击了!!!", Toast.LENGTH_SHORT).show();
                Log.i(TAG, "onClick: 我被点击了!!!");
            }
        });
    }
}
<?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:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="模拟点击" />
</LinearLayout>

监听程序

AccessibilityService

代码具体的详情请看注释。

public class ListeningService extends AccessibilityService {
    private static final String TAG = "WindowChange";

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        //配置监听的事件类型为界面变化|点击事件
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_CLICKED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
        if (Build.VERSION.SDK_INT >= 16) {
            config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
        }
        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        AccessibilityNodeInfo nodeInfo = event.getSource();//当前界面的可访问节点信息
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {//界面变化事件
            ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString());
            ActivityInfo activityInfo = tryGetActivity(componentName);
            boolean isActivity = activityInfo != null;
            if (isActivity) {
                Log.i(TAG, componentName.flattenToShortString());
                //格式为:(包名/.+当前Activity所在包的类名)
                //如果是模拟程序的操作界面
                if (componentName.flattenToShortString().equals("com.demon.simulationclick/.MainActivity")) {
                    //当前是模拟程序的主页面,则模拟点击按钮
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                        //通过id寻找控件,id格式为:(包名:id/+制定控件的id)
                        //一般除非第三方应该是自己的,否则我们很难通过这种方式找到控件
                        //List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.demon.simulationclick:id/btn_click");
                        //通过控件的text寻找控件
                        List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("模拟点击");
                        if (list != null && list.size() > 0) {
                            list.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        }
                    }
                }
            }
        }
        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {//View点击事件
            //Log.i(TAG, "onAccessibilityEvent: " + nodeInfo.getText());
            if ((nodeInfo.getText() + "").equals("模拟点击")) {
                //Toast.makeText(this, "这是来自监听Service的响应!", Toast.LENGTH_SHORT).show();
                Log.i(TAG, "onAccessibilityEvent: 这是来自监听Service的响应!");
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {
    }

}

配置Service
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
···
<service
            android:name=".ListeningService"
            android:label="@string/app_name"
            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/accessibilityservice" />
        </service>
 ···
accessibilityservice.xml
方法说明
android:accessibilityEventTypes设置响应事件的类型typeAllMask,typeViewClicked,typeViewFocused,typeNotificationStateChanged,typeWindowStateChanged等
android:accessibilityFeedbackType设置反馈给用户的方式
android:canRetrieveWindowContent是否检索当前窗口的内容,即是否可以获取当前窗口的View
android:description服务申请的权限描述说明
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    tools:ignore="UnusedAttribute" />
MainActivity

使用AccessibilityService服务需要申请辅助功能(小米手机叫:无障碍)的服务支持,无法主动给与,需要到指定界面用户手动开启。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Intent intent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!isAccessibilitySettingsOn(MainActivity.this, ListeningService.class.getCanonicalName())) {
                    Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                    startActivity(intent);
                } else {
                    intent = new Intent(MainActivity.this, ListeningService.class);
                    startService(intent);
                }
            }
        });

        findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (intent != null) {
                    stopService(intent);
                }
            }
        });
    }


    /**
     * 检测辅助功能是否开启
     *
     * @param mContext
     * @return boolean
     */
    private boolean isAccessibilitySettingsOn(Context mContext, String serviceName) {
        int accessibilityEnabled = 0;
        // 对应的服务
        final String service = getPackageName() + "/" + serviceName;
        //Log.i(TAG, "service:" + service);
        try {
            accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
            Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
        } catch (Settings.SettingNotFoundException e) {
            Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.getMessage());
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

        if (accessibilityEnabled == 1) {
            Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
            String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();

                    Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
                        return true;
                    }
                }
            }
        } else {
            Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
        }
        return false;
    }
}
<?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:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开启监听服务" />

    <Button
        android:id="@+id/stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="关闭监听服务" />
</LinearLayout>
效果

运行监听程序,给与服务支持后,打开监听程序。
然后运行模拟程序,效果如下。

监听到com.demon.simulationclick/.MainActivity-------模拟点击按钮-----监听到按钮被点击------从监听Service发出响应
这里写图片描述

代码

https://github.com/MyAndroidDemo/ActivtyChange

参考

https://blog.csdn.net/c794904140/article/details/52153148

监听用户对某个app的操作行为,可以使用Python中的第三方库pyHook和pyWin32。这两个库可以帮助我们获取Windows操作系统中的键盘和鼠标事件,并且可以对指定的应用程序进行过滤。 具体实现步骤如下: 1. 安装pyHook和pyWin32库。 2. 编写Python脚本,导入需要的库。 ```python import pythoncom import pyHook import win32gui def onMouseEvent(event): # 处理鼠标事件 return True def onKeyboardEvent(event): # 处理键盘事件 return True # 创建钩子管理器对象 hm = pyHook.HookManager() # 监听所有鼠标事件 hm.MouseAll = onMouseEvent # 监听所有键盘事件 hm.KeyAll = onKeyboardEvent # 设置钩子 hm.HookMouse() hm.HookKeyboard() # 进入消息循环 pythoncom.PumpMessages() ``` 3. 在onMouseEvent和onKeyboardEvent函数中,可以根据需要对事件进行处理。例如,可以判断当前活动窗口是否为指定的应用程序,如果是,则记录下用户操作行为。 ```python def onMouseEvent(event): # 获取当前活动窗口句柄 hwnd = win32gui.GetForegroundWindow() # 获取窗口标题 title = win32gui.GetWindowText(hwnd) # 判断是否为指定的应用程序 if title == "MyApp": # 记录鼠标事件 print("Mouse event:", event.Message, event.Position) return True def onKeyboardEvent(event): # 获取当前活动窗口句柄 hwnd = win32gui.GetForegroundWindow() # 获取窗口标题 title = win32gui.GetWindowText(hwnd) # 判断是否为指定的应用程序 if title == "MyApp": # 记录键盘事件 print("Keyboard event:", event.Message, event.Key) return True ``` 需要注意的是,这种方法只能在Windows操作系统下使用,并且需要以管理员权限运行Python脚本。另外,使用钩子技术可能会引起一些安全问题,因此需要谨慎使用。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值