Android(安卓)AccessibilityService --- 小白视角
>>长路漫漫立志行高远,寒夜漆漆心勇敢独行。 --致青春
前言:
朋友的一个奇思妙想,促使我接触了 AccessibilityService ,这是个什么东西呢,我们看看申请权限时系统怎么提醒的
意思吧,很清楚,就是这项服务开启之后,您的屏幕操作动作,甚至是操作的屏幕上的视图的内容,这货都能获取到,发挥一下想象力,可以搞事情!(微信自动抢红包等自动化软件就是用的这货)。
正文:
STEP 1 Android studio 新建项目
首先,鉴于此功能一般有开发经验的人员才会想到或者用到,所以,android studio 那一堆新建项目之类的就直接跳过了。
STEP 2 项目配置
1、新建服务。比如:
public class AccessibilityServiceHelper extends AccessibilityService { private static final String TAG = "AccessibilityServiceHelper"; @Override public void onAccessibilityEvent(AccessibilityEvent event) { try { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { } else if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) { } else if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) { } } catch (Exception e) { e.printStackTrace(); } } @Override public void onInterrupt() { }}
根据本人测试的经验解释一下以上监听的三种状态(可监听的状态不止这三种,我这里只用到了这三种,具体可看AccessibilityEvent 类下的各个状态,基本上见名知意):
- event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:这种状态是页面切换才会触发,我测试出来的是,必须是Activity切换才会触发,如果Activity中包含ViewPager,ViewPager中是Fragment,那么切换Fragment是不会触发此状态的。
- event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:这种状态是监听窗口内容变化,看名字意思是窗口内容变化才会触发,但是我这里实测的是即使没看到变化也会触发,不知道是不是有肉眼看不到的变化。
- event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:通知状态变化,实测是当有弹窗和有通知栏消息时都会触发。
2、新建xml配置。app--res 下新建 xml 文件夹,xml文件夹下新建 accessibility_service_config.xml,这个文件名随意起。内容示例如下:
<?xml version="1.0" encoding="utf-8"?>
各个属性注释都有做解释。(此文件的配置也可在 AccessibilityServiceHelper 的 onServiceConnected 进行配置)
3、注册服务、配置服务。(AndroidManifest.xml 中注册服务就不用我说了吧) 示例内容如下:
CSDN这个代码编辑器太不智能了,都不会缩进,还得我一行一行调,心累。
注意:meta-data部分,android:resource 指向的就是上面 第 2 小步中的xml文件。
STEP 3 正主登场
1、判断当前服务开启状态。
/*** 检测辅助功能是否开启
*/public static boolean isAccessibilitySettingsOn(Context mContext) { int accessibilityEnabled = 0; final String service = mContext.getPackageName() + "/" + 替换为自定义的服务类名.class.getCanonicalName(); try { accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(), android.provider.Settings.Secure.ACCESSIBILITY_ENABLED); } catch (Settings.SettingNotFoundException e) { } TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':'); if (accessibilityEnabled == 1) { 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(); if (accessibilityService.equalsIgnoreCase(service)) { return true; } } } } else { Log.v(TAG, "***ACCESSIBILITY IS DISABLED***"); } return false;}
2、跳转至设置也开启服务。
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);startActivity(intent);
3、获取屏幕根节点。
在 AccessibilityServiceHelper 中的
@Overridepublic void onAccessibilityEvent(AccessibilityEvent event){ }
可以通过如下代码获取当前屏幕根节点:
AccessibilityNodeInfo accessibilityNodeInfo = accessibilityService.getRootInActiveWindow();
TIP 1: AccessibilityNodeInfo 有以下几个方法,用于组件的查找:
//通过屏幕显示的内容,比如:下一步、完成等字眼,获取包含该文本的所有节点List nodeInfoList = rootNode.findAccessibilityNodeInfosByText(text);//通过屏幕显示的view组件的id查找节点,不过前提是要知道该view的节点id,可以通过android SDK -- tools -- monitor.bat 启动 Android Device Monitor 通过捕获屏幕组件获取各个组件的信息,不过这家伙太TM不好用了,点100次也不一定能成功一次,所以建议在 AccessiblityServicerHelper 中遍历根节点,找到你要找的组件的节点,然后查看其信息,进行下一步操作List nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
有以下几个方法,用于组件的操作:
//对当前nodeInfo执行单击操作nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);//对当前nodeInfo执行长按操作nodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);//对当前nodeInfo执行复制操作nodeInfo.performAction(AccessibilityNodeInfo.ACTION_COPY);//对当前nodeInfo执行剪切操作nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CUT);//对当前nodeInfo执行粘贴操作nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
仅列出常用的几个,在 AccessibilityNodeInfo 中也可以找到更多操作指令。
TIP 2: AccessibilityService 也有一个本人比较常用的方法,用于执行全局返回操作:
//执行全局返回操作service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
在 AccessibilityService 中也可以找到更多操作指令。
4、通知内容的捕获,就是 event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED 时获取的内容 。
//捕获的信息都在集合里了,断点看一下就知道都有啥了,后续内容处理不再赘述List text = event.getText();
5、获取 WebView 内容。
@Overrideprotected void onServiceConnected() { super.onServiceConnected(); AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo(); serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; serviceInfo.packageNames = new String[]{"com.unionpay"};// 监控的app serviceInfo.notificationTimeout = 100; serviceInfo.flags = serviceInfo.flags | AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY; setServiceInfo(serviceInfo);}
重点:serviceInfo.flags = serviceInfo.flags | AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
这个设置可以让 AccessibilityService 能捕获到WebView,只有捕获到了才能拿里面的内容。
其他设置可以保持和 accessibility_service_config.xml 中的各项设置一致,不设置好像会让 accessibility_service_config.xml 对应属性失效,建议设置一遍。
下面是对获取到的WebView节点的实际操作:
//这里我是根据页面标题定位获取的,因为我不知道WebView的相关信息,但是我遍历根节点时拿到了页面标题的信息,然后通过节点的 getParent()方法往上找其父节点,一直找到页面根节点,然后再往下找各个子节点,直到找到需要的那个WebView节点AccessibilityNodeInfo text = AccessibilityUtils.findViewId(this, "com.xxx.xxx:id/tv_title").get(0);//找到父节点为一个FrameLayoutAccessibilityNodeInfo framelayout = text.getParent();//发现FrameLayout下的第三个字节点为WebView,但此WebView节点的子节点并不是我要的内容,继续往下找AccessibilityNodeInfo webview = framelayout.getChild(2);//又是一个WebView,仍不是目标viewAccessibilityNodeInfo textwebview = webview.getChild(0);//继续深入找AccessibilityNodeInfo textwebview1 = textwebview.getChild(0);//终于不是WebView了,而是一个View结合,其下有很多子view,而子view的信息正是我要找的AccessibilityNodeInfo views = textwebview1.getChild(0);//获取子view的内容String content = views.getChild(0).getContentDescription().toString();
注释已很详细,每个要监听的app页面内容都是不一样的,这里只是一个示例,不要死板的复制,这里只是一个查找思路和方法。
至此,AccessibilityService 的相关操作就结束了,可能还有没有说到的地方,但,我相信上面所讲的已经能满足大部分需求了。
也算是呕心沥血了,认真看完必有收获。
更多相关文章
- Android(安卓)系统Recovery工作原理
- Android之数据库Greedao的使用
- Android应用程序包解析过程浅析
- Android实现一键清除输入内容和震动效果的EditText
- Android来电和短信操作
- android 蓝牙隐藏对话框 后台配对
- android NoticificationManager状态栏操作
- Android(安卓)解决 HorizontalScrollView 里的内容滑动不全的问
- Android中EditText实现不可编辑解决办法