从零开始安卓无障碍服务Accessibility
Posted 至尊小毛毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始安卓无障碍服务Accessibility相关的知识,希望对你有一定的参考价值。
从零开始无障碍服务
文章目录
前言
以前安卓root权限很容易获取的时候,可以写一些日常工作批处理的助手工具,而现在的安卓手机权限管理越来越严,root权限越来越难获取,于是就开始使用安卓自带的无障碍服务来实现自己的操作了,虽然也有限制,但是总体的操作也是很符合预期。本文将从零编写一个无障碍服务的工具,不适合安卓初学者,请小白自行百度详细。
一、新建项目-选择Empty Activity
Minimun SDK我直接选择API 26,省去一些杂七杂八的问题,读者有更低版本兼容需求的话,请自行填坑。
二、新建BaseService类和AccessService类
1. BaseService类
BaseService继承AccessibilityService,里面封装了一些常用的查找、定位、手势方法。
代码如下:
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.GestureDescription;
import android.content.Context;
import android.content.Intent;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/*
* 基类,封装了查找定位、点击、手势方法
* */
public class BaseService extends AccessibilityService {
private AccessibilityManager mAccessibilityManager;
private Context mContext;
private static BaseService mInstance;
public void init(Context context) {
mContext = context.getApplicationContext();
mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
public static BaseService getInstance() {
if (mInstance == null) {
mInstance = new BaseService();
}
return mInstance;
}
/**
* Check当前辅助服务是否启用
*
* @param serviceName serviceName
* @return 是否启用
*/
public boolean checkAccessibilityEnabled(String serviceName) {
List<AccessibilityServiceInfo> accessibilityServices =
mAccessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().equals(serviceName)) {
return true;
}
}
return false;
}
/**
* 前往开启辅助服务界面
*/
public void goAccess() {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
/**
* 模拟点击事件,如果该node不能点击,则点击父node,将点击事件一直向父级传递,直至到根node或者找到一个可以点击的node
*
* @param nodeInfo nodeInfo
*/
public void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
while (nodeInfo != null) {
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
nodeInfo = nodeInfo.getParent();
}
}
/**
* 模拟返回操作
*/
public void performBackClick() {
performGlobalAction(GLOBAL_ACTION_BACK);
}
/**
* 模拟下滑操作
*/
public void performScrollBackward() {
performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
/**
* 模拟上滑操作
*/
public void performScrollForward() {
performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
/**
* 查找对应文本的View,无论该node能不能点击
*
* @param text text
* @return View
*/
public AccessibilityNodeInfo findViewByText(String text) {
AccessibilityNodeInfo viewByText = findViewByText(text, true);
if (viewByText == null) {
viewByText = findViewByText(text, false);
}
return viewByText;
}
/**
* 查找对应文本的View
*
* @param text text
* @param clickable 该View是否可以点击
* @return View
*/
public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return null;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {
return nodeInfo;
}
}
}
return null;
}
/**
* 查找对应ID的View
*
* @param id id
* @return View
*/
public AccessibilityNodeInfo findViewByID(String id) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return null;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
Log.d("dd", "findViewByID: " + nodeInfoList.size());
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
Log.d("dd", "findViewByID: " + nodeInfo.toString());
return nodeInfo;
}
}
}
return null;
}
/**
* 点击对应文本的一个view,前提是这个view能够点击,即 clickable == true,
*
* @param text 要查找的文本
*/
public void clickViewByText(String text) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
performViewClick(nodeInfo);
break;
}
}
}
}
/**
* 点击对应id的一个view,前提是这个view能够点击,即 clickable == true,
*
* @param id 要查找的id
*/
public void clickViewByID(String id) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
performViewClick(nodeInfo);
break;
}
}
}
}
/**
* 递归遍历node及其子node,点击文本相同的节点,全点击
*
* @param text
* @param parentNode
*/
public void clickNodesByText(String text, AccessibilityNodeInfo parentNode) {
if (parentNode == null) {
return;
}
int childCount = parentNode.getChildCount();
if (childCount == 0) { //叶节点
if (parentNode.getText() == null) {
return;
}
if (!text.equals(parentNode.getText().toString())) {
return;
}
Rect rect = new Rect();
parentNode.getBoundsInScreen(rect);
int moveToX = (rect.left + rect.right) / 2;
int moveToY = (rect.top + rect.bottom) / 2;
int lineToX = (rect.left + rect.right) / 2;
int lineToY = (rect.top + rect.bottom) / 2;
gesture(moveToX, moveToY, lineToX, lineToY, 100L, 400L);
return;
}
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo child = parentNode.getChild(i);
clickNodesByText(text, child);
}
}
/**
* 根据文本查找节点
*
* @param text 要查找的文本
* @return 与文本相同的节点列表,找不到则返回空
*/
public List<AccessibilityNodeInfo> findNodesByText(String text) {
List<AccessibilityNodeInfo> accessibilityNodeInfos = new ArrayList<>();
Stack<AccessibilityNodeInfo> nodeStack = new Stack<>();
AccessibilityNodeInfo node = getRootInActiveWindow();
nodeStack.add(node);
while (!nodeStack.isEmpty()) {
node = nodeStack.pop();
if (node != null && node.getText() != null && node.getText().toString().equals(text)) {
accessibilityNodeInfos.add(node);
}
if (node == null || node.getChildCount() == 0) {
continue;
}
//获得节点的子节点,对于二叉树就是获得节点的左子结点和右子节点
int childCount = node.getChildCount();
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
nodeStack.push(child);
}
}
}
if (accessibilityNodeInfos.size() > 0) {
return accessibilityNodeInfos;
} else {
return null;
}
}
/**
* 模拟输入,低版本的输入有所不同,读者请自行百度
*
* @param nodeInfo nodeInfo
* @param text text
*/
public void inputText(AccessibilityNodeInfo nodeInfo, String text) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
/**
* 手势操作,因为path不能小于0,因此小于则直接返回,不操作,另外如果有需求,可以自行修改小于则设置为0或者屏幕的宽高
*
* @param moveToX
* @param moveToY
* @param lineToX
* @param lineToY
* @param startTime
* @param duration
*/
public void gesture(int moveToX, int moveToY, int lineToX, int lineToY, long startTime, long duration) {
if (moveToX < 0 || moveToY < 0 || lineToX < 0 || lineToY < 0) {
Log.e("path", "path nagative");
return;
}
GestureDescription.Builder builder = new GestureDescription.Builder();
Path path = new Path();
path.moveTo(moveToX, moveToY);
path.lineTo(lineToX, lineToY);
GestureDescription gestureDescription = builder
.addStroke(new GestureDescription.StrokeDescription(path, startTime, duration, false))
.build();
dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
}
}, new Handler(Looper.getMainLooper()));
}
protected void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) { }
@Override
public void onInterrupt() { }
@Override
protected void onServiceConnected() { super.onServiceConnected(); }
}
2. AccessService类
AccessService则继承BaseService,具体的无障碍处理逻辑都在这个类里面实现。
代码如下:
import android.view.accessibility.AccessibilityEvent;
/**
* 操作类,在这里实现具体逻辑
*/
public class AccessService extends BaseService {
private String appPackageName = "xxx.xxx.xxx";
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
String packageName = event.getPackageName() == null ? "" : event.getPackageName().toString();
if (!packageName以上是关于从零开始安卓无障碍服务Accessibility的主要内容,如果未能解决你的问题,请参考以下文章
Android Accessibility无障碍服务安全性浅析
Android Accessibility无障碍服务安全性浅析
UiAutomator2.0 - 与AccessibilityService的关联