目录
前言
一、Android studio 新建项目
二、项目配置
1、新建服务:
2、新建xml配置。app--res 下新建 xml 文件夹,xml文件夹下新建 accessibility_service_config.xml,这个文件名随意起。内容示例如下:
3、注册服务、配置服务。(AndroidManifest.xml 中注册服务就不用我说了吧) 示例内容如下:
三、正主登场
1、判断当前服务开启状态。
2、跳转至设置也开启服务。
3、获取屏幕根节点。
4、通知内容的捕获,就是 event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED 时获取的内容 。
5、获取 WebView 内容。
朋友的一个奇思妙想,促使我接触了 AccessibilityService ,这是个什么东西呢,我们看看申请权限时系统怎么提醒的
意思吧,很清楚,就是这项服务开启之后,您的屏幕操作动作,甚至是操作的屏幕上的视图的内容,这货都能获取到,发挥一下想象力,可以搞事情!(微信自动抢红包等自动化软件就是用的这货)。
首先,鉴于此功能一般有开发经验的人员才会想到或者用到,所以,android studio 那一堆新建项目之类的就直接跳过了。
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 类下的各个状态,基本上见名知意):
各个属性注释都有做解释。(此文件的配置也可在 AccessibilityServiceHelper 的 onServiceConnected 进行配置)
CSDN这个代码编辑器太不智能了,都不会缩进,还得我一行一行调,心累。
注意:meta-data部分,android:resource 指向的就是上面 第 2 小步中的xml文件。
/**
* 检测辅助功能是否开启
*/
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;
}
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
在 AccessibilityServiceHelper 中的
@Override
public 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 中也可以找到更多操作指令。
//捕获的信息都在集合里了,断点看一下就知道都有啥了,后续内容处理不再赘述
List text = event.getText();
@Override
protected 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);
//找到父节点为一个FrameLayout
AccessibilityNodeInfo framelayout = text.getParent();
//发现FrameLayout下的第三个字节点为WebView,但此WebView节点的子节点并不是我要的内容,继续往下找
AccessibilityNodeInfo webview = framelayout.getChild(2);
//又是一个WebView,仍不是目标view
AccessibilityNodeInfo 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 的相关操作就结束了,可能还有没有说到的地方,但,我相信上面所讲的已经能满足大部分需求了。
也算是呕心沥血了,认真看完必有收获。